Compare commits

...

353 Commits

Author SHA1 Message Date
Adam Stankiewicz
6614a43658 Bump to 1.6.5 2015-11-14 15:56:13 +01:00
Adam Stankiewicz
88fd65ae34 Merge pull request #1988 from faceleg/travis-node-versions
Run travis tests in all versions of node
2015-11-14 15:52:47 +01:00
Adam Stankiewicz
83da088024 Update CONTRIBUTING.md 2015-11-14 15:39:41 +01:00
Michael Robinson
0aefe8fc0e Be more specific with node version 5 2015-11-05 10:47:39 +13:00
Michael Robinson
2845984169 Use environment vars for node versions because OS X 2015-11-05 10:47:24 +13:00
Michael Robinson
2a91dc5fb1 Updated travis tests to run in supported node versions 2015-11-05 10:43:02 +13:00
Michael Robinson
5eca9274ee Added language to travis.yml 2015-11-05 10:40:44 +13:00
Michael Robinson
d57d81ca85 Run travis tests in all versions of node 2015-11-05 10:38:15 +13:00
Adam Stankiewicz
a5dcf9cc24 Merge pull request #1973 from gronke/remove-insecure-suggestion-from-readme
remove hint to use --allow-root flag from readme
2015-10-26 10:30:59 +01:00
Stefan Grönke
484c8985ed remove hint to use --allow-root flag from readme 2015-10-26 01:14:09 +01:00
Adam Stankiewicz
bfd1e93325 Install stable version on npm on appveyor 2015-10-24 12:01:59 +02:00
Adam Stankiewicz
bf23751549 Bump to 1.6.4 2015-10-24 11:51:50 +02:00
Adam Stankiewicz
b79034fbb9 Merge pull request #1956 from bower/strip-trailing-slash
strip trailing slash in urlResolver
2015-10-24 11:09:32 +02:00
Adam Stankiewicz
eaa05ac6c1 Merge pull request #1960 from bower/add-user-agent
add User-Agent to downloadd
2015-10-24 11:08:31 +02:00
Adam Stankiewicz
8f0a3d727e Merge pull request #1969 from chrisjons/svn_improvements
use --non-interactive when running svn client
2015-10-24 10:41:21 +02:00
Adam Stankiewicz
b6a524e6b4 Merge pull request #1970 from twalpole/ignore2
fix issue #1962 - ignoredDependenices multiple install run
2015-10-24 10:39:20 +02:00
Thomas Walpole
43d00deb88 fix issue #1962 - ignoredDependenices multiple install run 2015-10-23 11:52:40 -07:00
Christian Jönsson
4836a0cae9 use --non-interactive when running svn client
If there is a problem in connecting to the Subversion server,
e.g. credential or certificate related, the CLI client will pause to
ask the user for input on the console. This is not handled by bower
Subversion Resolver, causing the install process to hang forever
with no message printed to the bower user.

Running the Subversion CLI client with the option --non-interactive
will instead cause the client to exit upon such an error.This is
detected by the bower resolver and the message printed to the user,
making it much easier to investigate and solve the underlying problem.
2015-10-23 13:05:16 +02:00
Adam Stankiewicz
302c4ade51 Leave link only to Support Declaration 2015-10-22 12:19:42 +02:00
Adam Stankiewicz
75d80e014a Mention about Support Declaration and BountySource 2015-10-22 12:15:50 +02:00
Patrick Kettner
0f790f4293 add User-Agent to downloadd 2015-10-20 13:51:50 -07:00
Patrick Kettner
452217e9fa strip trailing slash in urlResolver 2015-10-17 21:48:10 -05:00
Adam Stankiewicz
f66c0cfe5c Update npm to 2.0.0 on travis 2015-10-16 12:05:36 +02:00
Adam Stankiewicz
30898c13d3 Update changelog 2015-10-16 12:01:25 +02:00
Adam Stankiewicz
2330d59ffa Bump to 1.6.3 2015-10-16 11:49:04 +02:00
Adam Stankiewicz
1316be57dc Update bower-config to 1.2.2 2015-10-16 11:46:51 +02:00
Adam Stankiewicz
ff0f2a8f83 Use stat instead of lstat for checking if something is directory, fixes #1951 2015-10-16 11:17:03 +02:00
Adam Stankiewicz
7e5184d342 Merge pull request #1954 from scottaddie/master
Replace README file msysgit references with Git for Windows
2015-10-16 11:05:35 +02:00
Scott Addie
8fa1fd55e9 Replace README file msysgit references with Git for Windows 2015-10-15 22:18:30 -05:00
Adam Stankiewicz
52463dea09 Bump and update npm-shrinkwrap.json for bower 1.6.2 2015-10-15 16:15:51 +02:00
Adam Stankiewicz
8cf09f5444 Revert "Bump to 1.6.1 and update npm-shrinkwrap.json"
This reverts commit 20a6190ed8.
2015-10-15 16:12:42 +02:00
Adam Stankiewicz
20a6190ed8 Bump to 1.6.1 and update npm-shrinkwrap.json 2015-10-15 15:56:23 +02:00
Adam Stankiewicz
402a9f3017 Add node-uuid back to npm-shrinkwrap 2015-10-15 14:10:25 +02:00
Adam Stankiewicz
af09872fba Set default bower.config again 2015-10-15 13:50:36 +02:00
Adam Stankiewicz
2311d7dc44 Update npm-shrinkwrap.json with npm-shrinkwrap tool 2015-10-15 13:29:56 +02:00
Adam Stankiewicz
b261bf8a76 Update npm-shrinkwrap.json for bower 1.6.0 2015-10-15 13:13:21 +02:00
Adam Stankiewicz
140c6d963f Update changelog and bump 2015-10-15 13:03:30 +02:00
Adam Stankiewicz
0c5e457359 Merge pull request #1948 from bower/feature/bundle
Shrinkwrap all dependencies and add them to bundledDependencies
2015-10-15 13:01:13 +02:00
Adam Stankiewicz
bfa4295606 Try to use container-based infrastructure on Travis 2015-10-15 12:53:18 +02:00
Adam Stankiewicz
8ac68ede5d Shrinkwrap all dependencies and add them to bundledDependencies 2015-10-15 12:45:38 +02:00
Adam Stankiewicz
a019f887e9 Update chmodr to fix chmod behavior on windows 2015-10-15 11:14:49 +02:00
Adam Stankiewicz
eba2c69308 Update bower-config to fix proxy behavior on windows 2015-10-15 11:14:35 +02:00
Adam Stankiewicz
0bb1536c99 Better manage file handles, properly close streams 2015-10-14 19:31:53 +02:00
Adam Stankiewicz
64eb7d598a Use lstat instead of stat for rimraf util 2015-10-14 17:24:54 +02:00
Adam Stankiewicz
df8e5a16be Fix readdir call on Windows
Sometimes it return ENOENT instead of ENODIR for normal files.

This broke code paths in few places. Also, see:
https://github.com/isaacs/chmodr/pull/8
2015-10-14 16:09:38 +02:00
Adam Stankiewicz
3ce2dd3989 Replace all fs with graceful-fs 2015-10-14 14:31:13 +02:00
Adam Stankiewicz
99105fbb57 Try chmod 777 + rimraf as fallback on rimraf in all places 2015-10-14 12:50:41 +02:00
Adam Stankiewicz
96f1e98859 test: Fix url for js-event-emitter 2015-10-13 17:56:42 +02:00
Adam Stankiewicz
d614b057c9 fix: Set environment variable as string, not number 2015-10-13 17:27:27 +02:00
Adam Stankiewicz
aafe02f3e9 Update bower-registry-client and bower-config 2015-09-28 19:42:35 +02:00
Adam Stankiewicz
123779bbd1 Remove client-side check for register command, closes bower/registry#23 2015-09-28 18:01:46 +02:00
Adam Stankiewicz
5365c7b428 Disable client-side check for registering git:// endpoints 2015-09-28 17:46:58 +02:00
Adam Stankiewicz
da961a9c42 Pass proxy configuration to request.js via env variables 2015-09-28 17:34:06 +02:00
de Winter, Anton
8ff0e0e2d1 Components that are not installed by bower should be left alone
Rework of commit by @wibblymat for PR #1363
Make compatible with existing tests
Uses fs.readdirSync() (once!) instead of async method.
Added new test
2015-09-28 17:18:36 +02:00
Adam Stankiewicz
ffde6bd228 Remove version generation from "bower init" command, closes #1343 2015-09-28 17:07:29 +02:00
JD Isaacks
498fe84b99 Use values from package.json as defaults if exists 2015-09-27 19:39:46 +02:00
Adam Stankiewicz
9b8b66ed83 Merge pull request #1838 from yasinkocak/patch-1
Added keywords tag
2015-09-27 18:22:51 +02:00
Adam Stankiewicz
9cc3dd4c92 Pass certificate for GitHubResolver and UrlResolver, closes #1869 2015-09-27 18:18:11 +02:00
Adam Stankiewicz
bf23d81c9e Update bower-config and fix tests for ignoreDependenies 2015-09-27 18:18:11 +02:00
Adam Stankiewicz
6e1a994c26 Merge pull request #1922 from pwang2/feature/allow-resolver-passed-via-cli
allow --config.resolvers={required package}
2015-09-27 12:31:37 +02:00
Thomas Walpole
2ccc05cb98 get ignoredDependencies from .bowerrc instead of bower.json 2015-09-27 12:27:39 +02:00
Thomas Walpole
e7d22ffb11 install and update tests for ignoredDependencies 2015-09-27 12:27:39 +02:00
SignalDancer
4805c3615b Allow Ignore For Child Dependencies
Fix for bug #927. Ignore package if name matches specified in bower.json.
Permit fetch if package is explicitly declared in dependencies or devDependencies.
Designed for users who need tighter control of versions due to corporate liscence restrictions or other reasons.

Code Review Comments
2015-09-27 12:27:38 +02:00
Adam Stankiewicz
44a5260050 test: Clear analytics config for test environment 2015-09-27 12:22:40 +02:00
Adam Stankiewicz
d72d01823d Bump inquirer to 0.10, closes #1883 2015-09-27 12:03:25 +02:00
Adam Stankiewicz
1e166189ba Merge pull request #1917 from lukemelia/improve-version-failure-message
Include package name in error info when a version is not found.
2015-09-24 14:17:13 +02:00
Adam Stankiewicz
66310523d1 Bump to 1.5.3 2015-09-24 13:58:05 +02:00
Adam Stankiewicz
87a041a212 Fix --save-exact feature for github endpoints, fixes #1925 2015-09-24 13:34:49 +02:00
Adam Stankiewicz
9c52ec2751 Revert "Auto-sort bower.json dependencies alphabetically, fixes #1373"
This reverts commit 9dd79a8061.
2015-09-24 11:06:55 +02:00
Adam Stankiewicz
9010269236 Merge pull request #1923 from mex/feature/insight-prompt
Bumps Insight to reduce prompt friction
2015-09-18 12:11:36 +02:00
Michael Storgaard
aac254d275 Bumped Insight to reduce prompt friction
- Insight now checks for `process.env.CI` before prompting
 - Insight now times out after 30 seconds of no response to prompt
2015-09-18 11:18:08 +02:00
Peng Wang
889b54f309 allow --config.resolvers={required package} 2015-09-17 16:25:05 -05:00
Luke Melia
5d13ffda09 Include package name in error info when a version is not found.
Prior to this commit, when specifying a package version that cannot
be resolved, but other versions are available, the error message
did not state which what package failed, allowing for confusion.

For example, you might see:

```
bower ENORESTARGET  No tag found that was able to satisfy 1.0.2

Additional error details:
Available versions: 1.0.1, 1.0.0
```

After this change, in the above situation the svn and git resolvers
will produce the following:

```
bower ENORESTARGET  No tag found that was able to satisfy 1.0.2

Additional error details:
Available versions in somelib: 1.0.1, 1.0.0
```
2015-09-12 21:10:01 -04:00
Adam Stankiewicz
31b6d5971c Merge pull request #1819 from bak/init_private
Allow boolean values in bower.json file created by `bower init`
2015-09-11 22:55:52 +02:00
Ben Cullen-Kerney
304b6393d4 Allow whitelisted values in bower.json file created by bower init
A change in the behavior of `mout.lang.isEmpty()` in Mout v0.10.0
introduced a bug in `bower init`, preventing it from including Boolean
values in "bower.json".

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

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

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

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

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

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

If the `stderr` output contains the desired header

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

then the server supports shallow cloning.

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

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

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

Added test cases to verify the new functionality.
2015-03-24 17:13:18 +01:00
Sam Saccone
3838a3b4fb Merge pull request #1740 from kytwb/master
Fix broken link to npm completion doc
2015-03-13 09:57:33 -04:00
Amine Mouafik
7d748ae15e Fix broken link to npm completion doc 2015-03-13 11:41:36 +07:00
Adam Stankiewicz
9dab389b50 Merge pull request #1722 from kant/patch-1
Update year of copyright
2015-03-06 21:05:03 -08:00
Darío Hereñú
260b4adb8c Update year of copyright 2015-03-06 14:30:00 -03:00
Adam Stankiewicz
182d92f9bd test: Fix SvnResolver tests on all platforms 2015-02-28 23:56:17 -08:00
Adam Stankiewicz
61a68a9e38 test: Replace dejavu repo with pure (dejavu moved) 2015-02-28 19:52:40 -08:00
Sindre Sorhus
b6e33d70c8 Update README.md 2015-03-01 02:58:15 +08:00
Sindre Sorhus
7a26bf1a10 bump chalk
https://github.com/sindresorhus/chalk/releases/tag/v1.0.0
2015-02-23 15:07:56 +07:00
Sindre Sorhus
df1a87eb4e Merge pull request #1672 from therewasaguy/master
Update License Year to 2015
2015-01-21 16:37:52 +08:00
Jason Sigal
e203b1aa9a Update License Year to 2015
Happy new year!
2015-01-20 17:31:41 -05:00
Adam Stankiewicz
f458114c5b Enable OSX Travis build 2015-01-12 17:51:40 +01:00
Paul Irish
8db09d2fed Close #1655 PR: extend mocha timeout to 30s for travis.. 2015-01-12 16:02:05 +08:00
Sindre Sorhus
0ce7053598 bump dev deps 2015-01-12 10:09:33 +08:00
Sindre Sorhus
6a96815c44 bump insight 2015-01-12 10:04:25 +08:00
Sindre Sorhus
d7b0db41f4 bump update-notifier 2015-01-12 08:27:39 +08:00
Adam Stankiewicz
9c9f3e7055 Merge pull request #1654 from bower/alexanderGugel-save-exact
Add --save-exact flag
2015-01-11 17:32:53 +01:00
Alexander Gugel
2f6d680b6c Add --save-exact flag 2015-01-11 17:12:19 +01:00
Adam Stankiewicz
639f7939a3 Add info about Windows git configuration for testing 2015-01-11 03:33:23 +01:00
Adam Stankiewicz
95ce9cbf0c Remove node@0.11 from Windows builds 2015-01-11 03:28:12 +01:00
Adam Stankiewicz
1e0ef941c7 Add link to AppVeyor badge 2015-01-11 03:24:18 +01:00
Adam Stankiewicz
7e5bd64885 Fix AppVeyor badge 2015-01-11 03:15:33 +01:00
Adam Stankiewicz
de023bc6ea Update badges 2015-01-11 03:13:01 +01:00
Adam Stankiewicz
c7df6f50ca Add AppVeyor config for Windows CI 2015-01-11 03:08:43 +01:00
Adam Stankiewicz
df71d251f0 Disable SVN testing for unsuppoted hosts (e.g. AppVeyor) 2015-01-11 03:08:41 +01:00
Adam Stankiewicz
0b9acc18cc Test reading commands arguments 2015-01-10 12:56:15 +01:00
Adam Stankiewicz
58a7de3136 Prevent loading cli module in headless mode 2015-01-10 05:53:21 +01:00
Adam Stankiewicz
5bb77b1e03 [refactor] Prepare command argv readers for testing 2015-01-10 05:49:42 +01:00
Adam Stankiewicz
e351322ce4 More tests for lookup command and little refactor 2015-01-10 04:47:43 +01:00
Adam Stankiewicz
6b53ccc8bd Add tests for init command and fix it
In non-interactive mode error has been thrown in wrong way.
2015-01-09 03:18:53 +01:00
Adam Stankiewicz
33842b6f92 Remove dummy completion command 2015-01-09 02:29:18 +01:00
Adam Stankiewicz
5e747b2cfd Add tests for search and lookup commands 2015-01-09 02:22:05 +01:00
Adam Stankiewicz
3f8de0efb9 Add tests for cache clean command 2015-01-09 01:47:45 +01:00
Adam Stankiewicz
63da3e4595 Add tests for cache list command 2015-01-08 09:44:06 +01:00
Adam Stankiewicz
a04b69dc6a Test and fix link command 2015-01-08 05:09:32 +01:00
Adam Stankiewicz
3be4764d54 Add tests for prune command 2015-01-07 03:32:55 +01:00
Adam Stankiewicz
2d4fec01b1 Refactor some tests to promise style 2015-01-07 02:04:34 +01:00
Adam Stankiewicz
85c50eb542 Inject credentials to ENV for Travis 2015-01-06 20:56:27 +01:00
Adam Stankiewicz
ee62d00c96 Add tests for "version" command 2015-01-06 20:49:17 +01:00
Adam Stankiewicz
52a32f0887 Add tests for JsonRenderer 2015-01-06 17:59:45 +01:00
Adam Stankiewicz
dd30be90ad Fix StandardRenderer tests so they work on Travis 2015-01-06 16:03:58 +01:00
Adam Stankiewicz
4cb027eb73 Add tests for StandardRenderer 2015-01-06 15:56:10 +01:00
Adam Stankiewicz
8e0b8f2faf [test] Force monochrome color for test cases 2015-01-06 03:12:32 +01:00
Adam Stankiewicz
39491b78b1 Test register prompt as on dumb terminal (fixes travis) 2015-01-06 02:24:39 +01:00
Adam Stankiewicz
7c82da8389 Add tests for register command 2015-01-06 02:13:30 +01:00
Adam Stankiewicz
d5c13603a0 Merge pull request #1644 from pkollitsch/typo
Fix for #1639, missing line break in CLI input
2015-01-05 22:45:15 +01:00
Patrick Kollitsch
aad253bfad Fix for #1639, missing line break in CLI input 2015-01-05 12:52:31 +07:00
Adam Stankiewicz
dac055e2ef Add timeout in hope to catch stdout on travis 2015-01-05 06:51:09 +01:00
Adam Stankiewicz
85df5b9983 Add test for bower binary nad standard renderer 2015-01-05 06:47:01 +01:00
Adam Stankiewicz
537cd42097 Add tests for info command 2015-01-05 06:07:01 +01:00
Adam Stankiewicz
9d06fce5f9 Add few more tests for home command 2015-01-05 05:31:12 +01:00
Adam Stankiewicz
c029e1005f Add tests for "bower home" command 2015-01-05 05:19:54 +01:00
Adam Stankiewicz
b245a3d611 Remove unnecessary asserts from list test 2015-01-05 04:00:51 +01:00
Adam Stankiewicz
11d89c4268 fix: Tests helper regression 2015-01-05 03:48:59 +01:00
Adam Stankiewicz
6be84ab93e Add test case for #1584 2015-01-05 03:40:34 +01:00
Adam Stankiewicz
2f2c4d6740 Add tests for help command and fix it 2015-01-05 03:31:28 +01:00
Sindre Sorhus
06f4d0c117 package.json - use caret version specifier 2015-01-05 00:01:29 +07:00
Sindre Sorhus
b5e557ffb0 bump decompress-zip 2015-01-05 00:00:05 +07:00
Sindre Sorhus
8bd6c4a335 Merge pull request #1640 from laurelnaiad/update-deps
update package.json dependencies
2015-01-04 23:58:41 +07:00
Sindre Sorhus
29eaff9edc Merge pull request #1614 from joneshf/answer-colon
Remove erroneous colon.
2015-01-04 23:56:01 +07:00
Sindre Sorhus
08afaf7fa5 Merge pull request #1584 from vlajos/typofixes20141102
typo fixes
2015-01-04 23:54:32 +07:00
Daphne Maddox
45bab9fe71 update package.json dependencies
Update dependencies in package.json to (mostly) latest available
versions.

Updating these dependencies removes warnings when bower is installed
as a devDependency in other packages and keep the code fresh.

Methodology:

- `npm install`
- `npm test` (all tests pass)
- `npm-check-updates -u`
- `npm test` (find errors, suspect semver, back off to `~2.3.0` in
package.json)
- `rm -rf node_modules` (and .gitignored files)
- `npm install`
- `npm test` (all pass, deem updated version compatible)
- revert to package.json from `master`
- `npm install` (install the old dependencies)
- switch back to updated package.json
- `npm install` (upgrade dependencies in place)
- `npm test` (all tests still pass, deem upgrades safe)

As to the `semver` dependency, it seems that updating to 4.x requires
handling breaking changes, which is TODO.

Closes #1622
2015-01-03 11:24:21 -08:00
Sindre Sorhus
514eb8f0e3 better homedir detection 2014-12-22 22:56:27 +07:00
Ricardo Polo
a7baa58c22 Close #1620 PR: Showing --no-color in help. Fixes #78 2014-12-10 21:09:27 +07:00
Adam Stankiewicz
e548d8b1a5 Merge pull request #1618 from fracmak/bower_list_tests
Added unit tests of bower.commands.list()
2014-12-05 11:58:49 +01:00
Merrifield, Jay
92ff0fe624 Added unit tests of bower.commands.list() 2014-12-04 23:29:03 -05:00
joneshf
a464f5a88e Remove erroneous colon. 2014-11-29 12:22:58 -08:00
Veres Lajos
962a565d30 typo fixes 2014-11-02 22:40:58 +00:00
Sindre Sorhus
7db50391f2 Merge pull request #1576 from darabos/patch-1
Add extra newline at the end of help-cache.std
2014-10-27 23:07:41 +07:00
Daniel Darabos
8b0d55a729 Add extra newline at the end of help-cache.std
Without this newline, the template is rendered without a newline at the end. So when you run `grunt help cache`, the output does not have a newline at the end and the shell prompt will be printed at the end of the last line.
2014-10-27 14:00:13 +01:00
Sindre Sorhus
de6f341f41 bump to latest chalk 2014-10-16 11:52:46 +02:00
Adam Stankiewicz
c9fb530dbf Merge pull request #1557 from ISNIT0/master
Added return character
2014-10-06 14:38:33 +02:00
ISNIT0
836bcd09ec Added return character
https://github.com/bower/bower/issues/1554
2014-10-06 12:58:04 +01:00
Sindre Sorhus
52a6836872 Merge pull request #1548 from bower/fix/readable
[fix] Ensure extracted files are readable, update tar-fs
2014-09-28 23:40:29 +02:00
Adam Stankiewicz
c00cadb37a [fix] Ensure extracted files are readable, update tar-fs 2014-09-28 18:21:24 +02:00
Adam Stankiewicz
b26c072f0d Bump version to 1.3.12 and update changelog 2014-09-28 17:37:40 +02:00
Adam Stankiewicz
c99482f59d [doc] Explain why env in test/helpers.js is needed 2014-09-28 15:58:59 +02:00
Adam Stankiewicz
4aa0f567c3 Merge pull request #1529 from bower/analytics_fix
fix broken analytics tracking introduced in #1507 & changed default
2014-09-27 09:57:29 +02:00
Ray Shan
4656021902 fix broken analytics tracking introduced in #1507 2014-09-26 22:33:43 +02:00
Adam Stankiewicz
3df6144b77 Downgrade mout to 0.9.0, closes #1525 2014-09-23 20:12:19 +02:00
Adam Stankiewicz
c620004168 Update tar-fs version, fixes #1537 2014-09-23 16:26:26 +02:00
Sindre Sorhus
893ff3e9d7 Merge pull request #1535 from zhiyelee/linebreak
add linebreak
2014-09-22 11:59:24 +02:00
zhiyelee
eec9a3cb8f add linebreak 2014-09-22 11:23:31 +08:00
Sindre Sorhus
d3c8042102 Merge pull request #1532 from BiAiB/fix-zero-major-dependencies
Fix 0.x dependencies to prevent breaking API updates
2014-09-19 14:14:05 +02:00
Rodolphe Gohard
e590b44c77 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-09-19 12:04:41 +02:00
Adam Stankiewicz
e97bf479fb Merge pull request #1527 from LaurentGoderre/v1.3.11
Bump and update changelog
2014-09-18 02:26:50 +02:00
Laurent Goderre
fe2f71c9b8 Bump and update changelog 2014-09-17 11:12:17 -04:00
Adam Stankiewicz
ee6c483dd4 Merge pull request #1519 from LaurentGoderre/fix-1518
Fix update command not installing missing packages
2014-09-16 22:39:49 +02:00
Laurent Goderre
e83ab86f1f Added update test cases 2014-09-16 16:23:38 -04:00
Laurent Goderre
ef237fc521 Added the missing install command to the update task
Fixes #1518
2014-09-16 16:23:25 -04:00
Adam Stankiewicz
fd4d68038b Bump and update changelog 2014-09-13 16:39:35 +02:00
Adam Stankiewicz
8e458c21e9 [test] Make sure install ends before running next test 2014-09-13 16:12:06 +02:00
Adam Stankiewicz
4ab36cb3bc [test] Use new temp location per process 2014-09-13 16:04:02 +02:00
Adam Stankiewicz
2d149f3a09 Merge pull request #1507 from bower/fix/skip-prompt
Fix/skip prompt
2014-09-13 01:25:20 +02:00
Adam Stankiewicz
dfd2c7a3d2 Add tests to analytics and fix them 2014-09-12 21:28:44 +02:00
John Schulz
6637762aec "analytics" value in \.bowerrc\ removes need to prompt user 2014-09-12 00:26:41 +02:00
Laurent Goderre
615eba3b40 Update p-throttle to allow displaying the output of hooks
Fixes #1484
2014-09-12 00:04:19 +02:00
Adam Stankiewicz
dc183125fc [test] Show coverage status for master branch 2014-09-08 03:12:36 +02:00
Adam Stankiewicz
c3b69d1201 [test] Provide git credentials for tests on CI 2014-09-08 03:10:02 +02:00
Adam Stankiewicz
3e1a50ab9a [test] Add tests for git repository install 2014-09-08 03:02:57 +02:00
Adam Stankiewicz
7565c71eb2 Warn about unnecessary resolution, #1061 2014-09-08 01:13:55 +02:00
Adam Stankiewicz
d743352bc0 Disable removing unnecessary resolutions, #1061 2014-09-08 01:02:31 +02:00
Sindre Sorhus
75ca72e3a8 Merge pull request #1508 from bower/fix/head-pipe
Prevent error when piping bower output to head, fixes #1396
2014-09-08 00:56:48 +02:00
Adam Stankiewicz
59d26c6825 Prevent error when piping bower output to head, fixes #1396 2014-09-07 23:46:20 +02:00
Adam Stankiewicz
5eed363e10 Merge pull request #1393 from Lukeas14/disable-shallow-clone-except-github
Disable shallow clones for all repos except those from github.com
2014-09-07 23:29:24 +02:00
Sindre Sorhus
49b2fdbde9 Merge pull request #1504 from bower/fix/hook-order
Run posthook after saving bower.json, fixes #1471
2014-09-07 20:06:28 +02:00
Burak Yigit Kaya
623f6e9542 Use tar-fs instead of tar for faster TAR extraction 2014-09-07 19:29:37 +02:00
Adam Stankiewicz
32356f23d3 Run posthook after saving bower.json, fixes #1471 2014-09-07 19:14:30 +02:00
Sindre Sorhus
321ddabfd5 Merge pull request #1502 from bower/fix/bowerrc-cwd
Read .bowerrc from specified cwd, fixes #1301
2014-09-06 18:15:55 +02:00
Sindre Sorhus
bf93a6a1ab make bower list --paths a bit prettier 2014-09-06 17:58:47 +02:00
Sindre Sorhus
91aa5dc6dd bump dependencies 2014-09-06 17:20:32 +02:00
Sindre Sorhus
56ed46b99a Merge pull request #1503 from bower/fix/windows-local-caching
Disable caching for windows absolute path, fixes #1416
2014-09-06 17:08:47 +02:00
Adam Stankiewicz
daaf21a4fe Merge pull request #1495 from mitogh/link-issues
Update the references to the issues and pulls in the CHANGELOG.md file
2014-09-06 14:40:55 +02:00
Adam Stankiewicz
06a8f2afab Read .bowerrc from specified cwd, fixes #1301 2014-09-06 02:38:40 +02:00
Adam Stankiewicz
cc04530c4a Merge pull request #1495 from mitogh/link-issues
Update the references to the issues and pulls in the CHANGELOG.md file
2014-09-04 23:14:21 +02:00
Crisoforo Gaspar Hernandez
0b6f62977a Update the references to the issues and pulls in the CHANGELOG.md file 2014-09-04 14:18:00 -05:00
Ben Schwarz
2a2996c22b Merge pull request #1483 from benschwarz/concurrency-reduction
Back concurrency down to 5 (from 50!)
2014-08-28 22:02:07 +10:00
Ben Schwarz
254aba0995 Back concurrency down to 5 (from 50!) 2014-08-28 20:28:22 +10:00
Ben Schwarz
23a81b3121 Merge pull request #1478 from fsmanuel/expose-version
expose bower version
2014-08-20 08:01:56 -07:00
Manuel Wiedenmann
c4659f816f expose bower version 2014-08-20 14:26:42 +02:00
Ben Schwarz
d1427e7d2e Merge pull request #1473 from bower/docs_update
Further simplify readme.md
2014-08-16 09:47:17 +10:00
Ray Shan
5584d1062e Further simplify readme.md 2014-08-15 16:26:23 -07:00
Sindre Sorhus
8cb41fe5fb Merge pull request #1464 from thorn0/patch-1
Mention updates in the main help
2014-08-11 18:35:42 +02:00
thorn0
cd893fec15 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-08-11 17:07:07 +03:00
Sindre Sorhus
2a01f178da Update changelog 2014-08-06 21:41:46 +02:00
Sindre Sorhus
a069d1e07d 1.3.9 2014-08-06 21:39:31 +02:00
Sindre Sorhus
76fa7f5200 Merge pull request #1434 from pornel/master
Fix "TypeError: Arguments to path.join must be strings" in GitHubResolver
2014-08-06 21:35:41 +02:00
Kornel Lesiński
7a0a86d51c When tmp.js returns cleanup callback along with the tmp dir path, nfcall changes return type to an array 2014-08-06 14:28:04 +01:00
Sindre Sorhus
8e283e43db Merge pull request #1442 from luckysw/master
Added .zip MIME type (e.g. the default served by Windows/IIS)
2014-07-30 08:38:23 -07:00
MadLux
794744d5a3 Added .zip MIME type (e.g. the default served by Windows/IIS) 2014-07-30 11:41:21 +02:00
Sindre Sorhus
5c3e69b045 Merge pull request #1436 from rayshan/bump_insight
analytics - bump insight, supports OS/node/CLI version tracking
2014-07-28 04:16:02 -07:00
Ray Shan
9cbd595cfd analytics - bump insight to 0.4.1, supports OS/node/CLI tool version tracking 2014-07-27 21:42:15 -07:00
Adam Stankiewicz
a5074eca7d [doc] Encourage to always use --save option 2014-07-15 14:11:29 +02:00
Sindre Sorhus
9386b117c1 Merge pull request #1415 from bower/simple-readme
Remove content that can be now be found on bower.io
2014-07-15 01:58:10 +02:00
Sindre Sorhus
7738248230 lock down 0.0.x versions
as `~` causes unexpected and non-semver behaviour on them.

https://github.com/isaacs/node-semver
2014-07-15 01:50:32 +02:00
David DeSandro
85f1f808d3 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-07-14 17:45:24 -04:00
Ben Schwarz
6dbafa22bb Merge pull request #1412 from chibicode/master
Fix typo on help-uninstall.json
2014-07-13 19:17:16 +10:00
Shu Uesugi
cadcd37681 Fix typo on help-uninstall.json 2014-07-13 16:36:41 +09:00
Paul Irish
a45ea6e1d8 Update changelog 2014-07-11 17:02:35 -07:00
Paul Irish
187f24de81 Bump to 1.3.8 2014-07-11 17:02:35 -07:00
Paul Irish
8df1f48226 Merge pull request #1407 from vladikoff/tmp-dep-fix
Fixes node-tmp update issue
2014-07-11 13:44:04 -07:00
vladikoff
7dbf332a94 Fixes node-tmp update issue 2014-07-11 13:40:58 -07:00
Sindre Sorhus
c09ddf6de1 Merge pull request #1395 from steveyken/master
Fixed typo
2014-07-09 23:48:49 +02:00
Sindre Sorhus
9b5fa99d6a bump update-notifier and simplify 2014-07-09 16:15:47 +02:00
Steve Kenworthy
b9abf32007 Fixed typo 2014-07-09 10:50:43 +08:00
Lucas, Justin
09ecb80625 Disable shallow clones for all repos except github.com 2014-07-08 11:39:14 -07:00
Sindre Sorhus
c4e9a0e340 bump deps 2014-07-05 00:26:46 +02:00
Adam Stankiewicz
b6cf4e1826 Update changelog 2014-07-04 13:02:12 +01:00
Adam Stankiewicz
ecb1619399 Bump to 1.3.7 2014-07-04 12:58:03 +01:00
Vladimir Alaev
83f4b7b699 Fix callstack error when processing installed packages with circular dependencies 2014-07-04 12:53:50 +01:00
insanehong
9b81ddf4d5 It should shows an error when bower install failed, fixes #922
- reason no bower.json in current directory
2014-07-03 15:10:38 +01:00
insanehong
4af17f0e40 Fixed bower list --paths` fails with TypeError, fixes #1383 2014-07-03 15:00:53 +01:00
Adam Stankiewicz
2fbc036e69 Update the changelog 2014-07-02 14:04:01 +01:00
Adam Stankiewicz
d236a12b8f Bump version to 1.3.6 2014-07-02 12:28:48 +01:00
Adam Stankiewicz
232be333ad Add tests for removeIgnores utility 2014-07-02 11:37:56 +01:00
Tim Monks
192e5af797 Add integration tests for bower commands 2014-07-02 11:16:12 +01:00
Tim Monks
deb39b8f34 Publish to coveralls.io from travis 2014-07-02 11:16:10 +01:00
Adam Stankiewicz
197b41d97a Merge pull request #1366 from sheerun/fix/local
[#1356] Disable caching for local resources
2014-07-02 10:31:30 +01:00
Adam Stankiewicz
7b11a57c6f Merge pull request #1365 from sheerun/fix/force
[#931] Make --force always re-run installation
2014-07-02 10:26:50 +01:00
Adam Stankiewicz
eac7945fae Merge pull request #1372 from incompl/master
Add --version to --help
2014-06-27 23:03:35 +01:00
Greg
1177d2263f Add --version to --help 2014-06-27 11:08:09 -04:00
Ben Schwarz
b9478a1f65 Merge pull request #1367 from abutcher/bower-install-save-readme
Add bower install --save/--save-dev blurb to README.md. Closes #1321
2014-06-25 08:03:45 +10:00
Andrew Butcher
025cf91679 README.md --save rewording suggested by @benschwarz for #1321 2014-06-24 09:33:45 -04:00
Adam Stankiewicz
442aa72ccc [fixes 5c839724] Prevent command like "bower info" from crashing on render 2014-06-24 11:39:44 +02:00
Adam Stankiewicz
1c09f9c82d Bump bower-config and endpoint-parser versions 2014-06-24 11:16:07 +02:00
Andrew Butcher
c6d2f633ea Add bower install --save/--save-dev blurb to README.md. Closes #1321 2014-06-24 00:37:57 -04:00
Adam Stankiewicz
17bd60e3e9 [#1356] Disable caching for local resources 2014-06-22 22:05:13 +02:00
Adam Stankiewicz
1d24e82276 [#931] Make --force always re-run installation 2014-06-21 00:33:51 +02:00
Adam Stankiewicz
e42d3d5620 [#1329] Check if pkgMeta is undefined 2014-06-20 21:57:41 +02:00
Thomas Scholtes
5c83972401 Use promise interface for commands
This commit changes the interface of the command functions exported by the
files in `lib/commands`. The functions now return a promise and accept a logger
as the first argument. This has several advantages

* The promise style is consistent with the rest of the code.
* It removes a lot of duplicate code.
* The command factory does not need to proxy the logger object.
2014-06-20 21:14:26 +02:00
Martin Hansen
b8df18481f Ignore does now not apply to any file in main #547 2014-06-20 19:48:00 +02:00
Andrew Eisenberg
1690dd4728 Warn users when installing package with missing properties.
Fix for issue #694. Test case included.
2014-06-20 19:00:19 +02:00
Sindre Sorhus
a86087ed7c Update README.md 2014-06-13 23:32:05 +02:00
Ray Shan
745060f0b5 Close GH-1348: Updated Analytics section of readme.md with link to stats.bower.io. 2014-06-13 23:30:12 +02:00
Sindre Sorhus
0a0a490a38 bump deps 2014-06-10 22:18:26 +02:00
Adam Stankiewicz
f816a5b0da Bump tmp version to 0.0.23, #988 2014-06-09 03:09:22 +02:00
Adam Stankiewicz
2f02e49716 Always ignore bower.json as said in spec 2014-06-08 18:46:39 +02:00
Mat Scales
a98a0b1ac2 Release 1.3.5 2014-06-06 11:31:37 +01:00
Adam Stankiewicz
034326c984 Add some tests and documentation for #1310 2014-06-05 23:52:48 +02:00
Sergey Tatarintsev
a965b05400 Search compatible versions in fetching packages
Previous version of code reported endpoints that does not have pckMeta
(not downloaded) yet and does not have exactly equal targets as
incompatible. This caused some package to be downloaded multiple times
and caused conflict errors (#1147).

This patch adds proper code for searching compatible version in
currently fetching packages basing on targets.

It also simplifies check for case when we have resolvedVersion and
candidate's target is range.
2014-06-05 19:41:37 +02:00
Mat Scales
6c2de4a359 1.3.4 2014-06-02 15:22:18 +01:00
Mat Scales
e9588279c8 Updated changelog for v1.3.4 2014-06-02 15:21:04 +01:00
Mat Scales
efe3a78499 Merge pull request #1278 from jfsiii/patch-1
Mirror `npm version` format for git tags
2014-05-27 19:37:53 +01:00
Adam Stankiewicz
31969a4939 Merge pull request #1306 from wibblymat/dependency-loop
Resolve a situation in which the install process gets into an infinite loop
2014-05-21 13:50:03 +02:00
Adam Stankiewicz
7a00c5ac71 Merge pull request #1307 from thomasmulvaney/patch-1
Fixed typo in README.md
2014-05-20 16:33:19 +02:00
Mat Scales
23fbbb5191 Resolve a situation in which the install process gets into an infinite loop 2014-05-20 14:46:41 +01:00
thomasmulvaney
a58b1ccfa5 Update README.md
Fixed sentence which had 'set manually' accidentally repeated.
2014-05-20 15:26:40 +02:00
Ben Schwarz
37b0a5c04c Merge pull request #1291 from danfinnie/patch-1
Fix Markdown typo in CONTRIBUTING.md
2014-05-12 13:12:38 +10:00
Daniel Finnie
3fb4f60192 Fix Markdown typo in CONTRIBUTING.md 2014-05-11 23:06:46 -04:00
Ben Schwarz
635fa84731 Merge pull request #1284 from niksy/improve-cli-color-output
Improve CLI output for conflicts
2014-05-08 08:14:13 +10:00
Ivan Nikolić
410bf4f0cd Improve CLI output for conflicts
Replace white color with green.
2014-05-07 19:27:44 +02:00
John Schulz
eebe115b78 Mirror npm version format for git tags
See https://github.com/bower/bower/pull/961#issuecomment-33981712 and  https://github.com/bower/bower/pull/961#issuecomment-35591108 for more context.
2014-05-04 15:11:47 -07:00
Sindre Sorhus
70eb9f0678 Merge pull request #1277 from benschwarz/analytics-statement-and-disable
Description of Analytics usage & how to disable
2014-05-04 06:03:26 +02:00
Ben Schwarz
b9de179fe8 Add notes about what analytics are used for, who has access and how to disable 2014-05-04 13:17:10 +10:00
Adam Stankiewicz
ac29e24c1b Allow for short commit SHA, closes #990 2014-04-25 02:09:10 +02:00
Adam Stankiewicz
d83572803d Bump, update changelog, update contributors 2014-04-24 22:35:22 +02:00
Adam Stankiewicz
87faa4f108 Do not cache moving targets like branches, fixes #1242 2014-04-24 20:31:36 +02:00
Sindre Sorhus
ea3a0d64c1 make jshint :) 2014-04-22 16:01:24 +02:00
Sindre Sorhus
8288d2f38b readme improvements 2014-04-22 15:28:29 +02:00
Sindre Sorhus
ff817dad0d various tweaks 2014-04-22 15:16:58 +02:00
Sindre Sorhus
ba554d5f45 use opn instead of open
`open` is buggy on Linux

https://github.com/sindresorhus/opn
2014-04-22 15:07:33 +02:00
Sindre Sorhus
90922a0ce0 bump deps 2014-04-22 15:04:42 +02:00
Sindre Sorhus
a91cb546f9 let npm sort dependencies 2014-04-22 14:57:38 +02:00
Adam Stankiewicz
3cb21d128f Forward all events from wrapped command, fixes #1232 2014-04-18 14:41:27 +02:00
Paul Irish
1b8d5d0648 Merge pull request #1234 from ahmadnassri/svn-export
using 'svn export' for efficiency. Closes #1224
2014-04-14 12:47:02 -07:00
Adam Stankiewicz
03f035cdf0 Merge pull request #1239 from bower/docs/usage
Discourage using bower components statically, closes #1210
2014-04-13 01:55:07 +02:00
Adam Stankiewicz
ac95654409 Discourage using bower components statically, closes #1210 2014-04-13 01:53:12 +02:00
Adam Stankiewicz
ba33bb6aa4 Cache clean and cache list work again, fixes #1232 2014-04-13 01:45:56 +02:00
Sindre Sorhus
2898c0507e Merge pull request #1237 from bower/fix/bower-list
Fix considering extended directory using .bowerrc
2014-04-12 23:41:32 +02:00
Sindre Sorhus
d59edd6cca Merge pull request #1238 from bower/feature/performance
Improve noninteractive loading performance 2x, fixes #1221
2014-04-12 23:33:49 +02:00
Adam Stankiewicz
74d568b1e7 Improve noninteractive loading performance 2x, fixes #1221 2014-04-12 21:11:17 +02:00
toshipon
0316fedd4c Fix considering extended directory using .bowerrc 2014-04-12 14:40:45 +02:00
Ahmad Nassri
e8c071304c using 'svn export' for efficiency. Closes #1224 2014-04-11 09:00:41 -04:00
124 changed files with 8622 additions and 1950 deletions

View File

@@ -13,3 +13,7 @@ trim_trailing_whitespace = false
[**.std]
insert_final_newline = false
[{package,bower}.json]
indent_style = space
indent_size = 2

5
.gitignore vendored
View File

@@ -1,11 +1,10 @@
/node_modules
/npm-debug.log
/test/assets/temp
/test/assets/temp2
/test/assets/temp-resolve-cache
/test/assets/package-*/
/test/assets/temp-*/
/test/reports
/test/tmp/
/bower.json
/component.json

View File

@@ -1,9 +1,34 @@
language: node_js
node_js:
- "0.10"
- "0.11"
sudo: false
env:
- NODE_VERSION=0.10
- NODE_VERSION=0.11
- NODE_VERSION=0.12
- NODE_VERSION=4.0
- NODE_VERSION=4.1
- NODE_VERSION=4.2
- NODE_VERSION=5.0
- NODE_VERSION=iojs
install:
- test $TRAVIS_OS_NAME = "osx" && brew install nvm && source $(brew --prefix nvm)/nvm.sh || test $TRAVIS_OS_NAME = "linux"
- nvm install $NODE_VERSION
- npm install -g npm@^2.0.0
- node --version
- npm --version
- git --version
- svn --version | head -n 1
- npm install -g grunt-cli
- npm install
os:
- osx
- linux
matrix:
allow_failures:
- node_js: "0.11"
before_script:
- npm install grunt-cli -g
- os: osx
- env: "NODE_VERSION=0.11"
script:
- grunt travis

View File

@@ -1,13 +1,169 @@
# Changelog
## 1.6.4 - 2015-10-24
- Fix ignoring dependencies on multiple install run ([#1970](https://github.com/bower/bower/pull/1970))
- Use --non-interactive when running svn client ([#1969](https://github.com/bower/bower/pull/1969))
- Fix downloading of URLs ending with slash ([#1956](https://github.com/bower/bower/pull/1956))
- Add user-agent field for downloads by Bower ([#1960](https://github.com/bower/bower/pull/1960))
## 1.6.3 - 2015-10-16
Fixes regression issues introduced with 1.6.2, specifically:
- Allow for bower_components to be a symlink
- Allow setting custom registry in .bowerrc
## 1.6.2 - 2015-10-15
Fix dependency issues of 1.6.1. First published release of 1.6.x.
## 1.6.1 - 2015-10-15
Fix dependency issues of 1.6.0. Reverted release.
## 1.6.0 - 2015-10-15
- Shrinkwrap all dependencies and add them to bundledDependencies ([#1948](https://github.com/bower/bower/pull/1948))
- Allow for ignoring of child dependencies ([#1394](https://github.com/bower/bower/pull/1394))
- Allow passing `--config.resolvers` through CLI ([#1922](https://github.com/bower/bower/pull/1922))
- Use defaults values from package.json if it exists (bower init) ([#1731](https://github.com/bower/bower/issues/1731))
- Properly use cerificates set in .bowerrc ([#1869](https://github.com/bower/bower/pull/1869))
- Include package name when version conflict occurs ([#1917](https://github.com/bower/bower/pull/1917))
- Add timeout for permission check ([yeoman/insight#35](https://github.com/yeoman/insight/pull/35))
- Close file-handles when possible. Prevents all sorts of permission issues on Windows ([0bb1536](https://github.com/bower/bower/commit/0bb1536c9972e13f3be06bea9a8619632966c664))
- Prevent ENOENT error on Windows when in VM environment ([isaacs/chmodr#8](https://github.com/isaacs/chmodr/pull/8))
Reverted release.
## 1.5.3 - 2015-09-24
- Revert auto sorting of bower dependencies, fixes ([#1897](https://github.com/bower/bower/issues/1897))
- Fix --save-exact feature for github endpoints, fixes ([#1925](https://github.com/bower/bower/issues/1925))
- Fix `bower init` to support private flag again ([#1819](https://github.com/bower/bower/pull/1819))
- Bump insight dependency to support prompt timeout ([#1102](https://github.com/bower/bower/issues/1102))
## 1.5.2 - 2015-08-25
- Revert update semver version from 2.x to 5.x, fixes ([#1896](https://github.com/bower/bower/issues/1896))
- Make bower commands work from subdirectories, fixes ([#1893](https://github.com/bower/bower/issues/1893))
- Put auto shallow cloning for git behind a flag, fixes ([#1764](https://github.com/bower/bower/issues/1764))
## 1.5.1 - 2015-08-24
- If cwd provided explicitly, force using it, fixes #1866
## 1.5.0 - 2015-08-24
- Pluggable Resolvers! http://bower.io/docs/pluggable-resolvers/
- Update semver version from 2.x to 5.x ([#1852](https://github.com/bower/bower/issues/1852))
- Auto-sort dependencies alphabetically ([#1381](https://github.com/bower/bower/issues/1381))
- Make bower commands work from subdirectories ([#1866](https://github.com/bower/bower/issues/1866))
- No longer prefer installing bower as global module ([#1865](https://github.com/bower/bower/issues/1865))
## 1.4.1 - 2015-04-01
- [fix] Reading .bowerrc upwards directory tree ([#1763](https://github.com/bower/bower/issues/1763))
- [fix] Update bower-registry-client so it uses the same bower-config as bower
## 1.4.0 - 2015-03-30
- Add login and unregister commands ([#1719](https://github.com/bower/bower/issues/1719))
- Automatically detecting smart Git hosts ([#1628](https://github.com/bower/bower/issues/1628))
- [bower/config#23] Allow npm config variables ([#1711](https://github.com/bower/bower/issues/1711))
- [bower/config#24] Merge .bowerrc files upwards directory tree ([#1689](https://github.com/bower/bower/issues/1689))
- Better homedir detection (514eb8f)
- Add --save-exact flag ([#1654](https://github.com/bower/bower/issues/1654))
- Ensure extracted files are readable (tar-fs) ([#1548](https://github.com/bower/bower/issues/1548))
- The version command in the programmatic API now returns the new version ([#1755](https://github.com/bower/bower/issues/1755))
- Some minor fixes: #1639, #1620, #1576, #1557, 962a565, a464f5a
- Improved Windows support (AppVeyor CI, tests actually passing on Windows)
- OSX testing enabled on TravisCI
It also includes improved test coverage (~60% -> ~85%) and many refactors.
## 1.3.12 - 2014-09-28
- [stability] Fix versions for unstable dependencies ([#1532](https://github.com/bower/bower/pull/1532))
- [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))
## 1.3.4 - 2014-06-02
- Resolve a situation in which the install process gets into an infinite loop ([#1169](https://github.com/bower/bower/issues/1169))
- Improved CLI output for conflicts ([#1284](https://github.com/bower/bower/issues/1284))
- Changed `bower version` to mirror the tag format of `npm version` ([#1278](https://github.com/bower/bower/issues/1278))
- Allow short commit SHAs to be used ([#990](https://github.com/bower/bower/issues/990))
## 1.3.3 - 2014-04-24
- Do not cache moving targets like branches ([#1242](https://github.com/bower/bower/issues/1242))
- Suppress output if --quiet option is specified ([#1124](https://github.com/bower/bower/pull/1124))
- Use "svn export" for efficiency ([#1224](https://github.com/bower/bower/pull/1224))
- Prevent loading insights and analytics on CI ([#1221](https://github.com/bower/bower/issues/1221))
- Make "bower list" respect custom components directory ([#1237](https://github.com/bower/bower/issues/1237))
- Improve non-interactive loading performance 2x ([#1238](https://github.com/bower/bower/issues/1238))
- Load commands only on demand, improving performance ([#1232](https://github.com/bower/bower/pull/1232))
## 1.3.2 - 2014-04-05
- Added yui moduleType [PR #1129](https://github.com/bower/bower/pull/1129)
- Fixes for concurrency issues [PR #1211](https://github.com/bower/bower/pull/1211)
- `link` now installs package dependencies [PR #891](https://github.com/bower/bower/pull/891)
- Improved conflict installation message [Commit](https://github.com/bower/bower/commit/bea533acf87903d4b411bfbaa7df93f852ef46a3)
- Add --production switch to "prune" command [PR #1168](https://github.com/bower/bower/pull/1168)
## 1.3.1 - 2014-03-10
- No longer ask for permission to gather analytics when running on in a CI environment.

View File

@@ -1,46 +1,33 @@
# Contributing to Bower
Bower is a large community project with many different developers contributing at all levels to the project. We're **actively** looking for more contributors right now. (Jan 2014)
Bower is a large community project with many different developers contributing at all levels to the project. We're **actively** looking for more contributors right now. If you're interested in becoming Bower maintainer or supporting in in any way, please full the following form: http://goo.gl/forms/P1ndzCNoiG
## Casual Involvement
* Improve the bower.io site ([tickets](https://github.com/bower/bower.github.io/issues))
* Move forward [bower.io redesign](https://github.com/bower/bower.github.io/issues/7)
* Attend team meetings
* Comment on issues and drive to resolution
## High-impact Involvement
* Maintaining the bower client.
* [Authoring client tests](https://github.com/bower/bower/issues/801)
* Read [Architecture doc](https://github.com/bower/bower/wiki/Rewrite-architecture)
* Triage, close, fix and resolve [issues](https://github.com/bower/bower/issues)
* Developing the [new registry server](https://github.com/bower/registry/tree/node_rewrite)
* Hooking in to Elastic Search rather than the in-memory search
* Getting bower/registry-client to talk to the new server without breaking backwards compatibility
* DevOps for the server
## Team Meetings
We meet on Monday at 1:00pm PST, 9:00pm UTC in #bower on Freenode. [The meeting notes](http://goo.gl/NJZ1o2).
<hr>
Following these guidelines helps to communicate that you respect the time of
the developers managing and developing this open source project. In return,
they should reciprocate that respect in addressing your issue, assessing
changes, and helping you finalize your pull requests.
We communicate through a channel on slack: https://gitter.im/bower
If you'd like to attend the meetings, please fill the [support form](http://goo.gl/forms/P1ndzCNoiG), and you'll get an invite.
## Using the issue tracker
*
The issue tracker is the preferred channel for [bug reports](#bugs),
[features requests](#features) and [submitting pull
requests](#pull-requests), but please respect the following restrictions:
* Please **do not** use the issue tracker for personal support requests. Use
[Stack Overflow](http://stackoverflow.com/questions/tagged/bower), our
[Stack Overflow](http://stackoverflow.com/questions/tagged/bower)
[Gitter Channel](https://gitter.im/bower/bower)
[Mailing List](http://groups.google.com/group/twitter-bower)
(twitter-bower@googlegroups.com), or
[#bower](http://webchat.freenode.net/?channels=bower) on Freenode.
@@ -48,7 +35,6 @@ requests](#pull-requests), but please respect the following restrictions:
* Please **do not** derail or troll issues. Keep the discussion on topic and
respect the opinions of others.
<a name="bugs"></a>
## Bug reports

View File

@@ -1,5 +1,5 @@
'use strict';
module.exports = function (grunt) {
require('load-grunt-tasks')(grunt);
grunt.initConfig({
@@ -7,14 +7,24 @@ module.exports = function (grunt) {
options: {
jshintrc: '.jshintrc'
},
files: ['Gruntfile.js', 'bin/*', 'lib/**/*.js', 'test/**/*.js', '!test/assets/**/*', '!test/reports/**/*']
files: [
'Gruntfile.js',
'bin/*',
'lib/**/*.js',
'test/**/*.js',
'!test/assets/**/*',
'!test/reports/**/*',
'!test/tmp/**/*'
]
},
simplemocha: {
options: {
reporter: 'spec',
timeout: '5000'
timeout: '15000'
},
full: {
src: ['test/test.js']
},
full: { src: ['test/test.js'] },
short: {
options: {
reporter: 'dot'
@@ -30,7 +40,10 @@ module.exports = function (grunt) {
command: 'node test/packages.js --force && node test/packages-svn.js --force'
},
cover: {
command: 'node node_modules/istanbul/lib/cli.js cover --dir ./test/reports node_modules/mocha/bin/_mocha -- -R dot test/test.js'
command: 'STRICT_REQUIRE=1 node node_modules/istanbul/lib/cli.js cover --dir ./test/reports node_modules/mocha/bin/_mocha -- --timeout 30000 -R dot test/test.js'
},
coveralls: {
command: 'node node_modules/.bin/coveralls < test/reports/lcov.info'
}
},
watch: {
@@ -39,9 +52,9 @@ module.exports = function (grunt) {
}
});
grunt.registerTask('assets', ['exec:assets-force']);
grunt.registerTask('test', ['jshint', 'exec:assets', 'simplemocha:full']);
grunt.registerTask('cover', 'exec:cover');
grunt.registerTask('travis', ['jshint', 'exec:assets', 'exec:cover', 'exec:coveralls']);
grunt.registerTask('default', 'test');
};

View File

@@ -1,4 +1,4 @@
Copyright (c) 2014 Twitter and other contributors
Copyright (c) 2015 Twitter and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

397
README.md
View File

@@ -1,366 +1,110 @@
# Bower
[![Build Status](https://secure.travis-ci.org/bower/bower.svg?branch=master)](http://travis-ci.org/bower/bower)
Bower needs resources for its maintenance. Please fill [<strong>Support Declaration</strong>](http://goo.gl/forms/P1ndzCNoiG) if you think you can help.
[![Build Status](https://travis-ci.org/bower/bower.svg?branch=master)](https://travis-ci.org/bower/bower) [![Windows Build](https://ci.appveyor.com/api/projects/status/jr6vfra8w84plh2g/branch/master?svg=true)](https://ci.appveyor.com/project/sheerun/bower/history) [![Coverage Status](https://img.shields.io/coveralls/bower/bower.svg)](https://coveralls.io/r/bower/bower?branch=master)
<img align="right" height="300" src="http://bower.io/img/bower-logo.png">
Bower is 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 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.).
> A package manager for the web
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 complete docs on [bower.io](http://bower.io)**
[View all packages available through Bower's registry](http://bower.io/search/).
## Install
## Installing Bower
Bower depends on [Node](http://nodejs.org/) and [npm](http://npmjs.org/). It's
installed globally using npm:
```
npm install -g bower
```sh
$ npm install -g bower
```
Also make sure that [git](http://git-scm.com/) is installed as some bower
Bower depends on [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/). Also make sure that [git](http://git-scm.com/) is installed as some bower
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:
```sh
# install dependencies listed in bower.json
$ bower install
#####Using the dependencies listed in the current directory's bower.json
# 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
```
bower install
```
##### Using a local or remote package
```
bower install <package>
```
##### Using a specific version of a package
```
bower install <package>#<version>
```
##### Using a different name and a specific version of a package
```
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": "public/bower_components"
}
```
### Finding packages
To search for packages registered with Bower:
```
bower search [<name>]
```
Using just `bower search` will list all packages in the registry.
### Using packages
The easiest approach is to use Bower statically, just reference the package's
installed components manually using a `script` tag:
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).
```html
<script src="/bower_components/jquery/jquery.js"></script>
```
For more complex projects, you'll probably want to concatenate your scripts or
use a module loader. Bower is just a package manager, but there are plenty of
other tools -- such as [Sprockets](https://github.com/sstephenson/sprockets)
and [RequireJS](http://requirejs.org/) -- that will help you do this.
The best approach is to process components installed by bower with build tool (like [Grunt](http://gruntjs.com/) or [gulp](http://gulpjs.com/)), and serve them concatenated or using module loader (like [RequireJS](http://requirejs.org/)).
### Uninstalling packages
To uninstall a locally installed package:
```
bower uninstall <package-name>
```sh
$ 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
### Never run Bower 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
option shown below:
[Git for Windows](http://git-for-windows.github.io/) correctly. Be sure to check the
options shown below:
![msysgit](http://f.cl.ly/items/2V2O3i1p3R2F1r2v0a12/mysgit.png)
<img src="https://cloud.githubusercontent.com/assets/10702007/10532690/d2e8991a-7386-11e5-9a57-613c7f92e84e.png" width="534" height="418" alt="Git for Windows" />
<img src="https://cloud.githubusercontent.com/assets/10702007/10532694/dbe8857a-7386-11e5-9bd0-367e97644403.png" width="534" height="418" alt="Git for Windows" />
Note that if you use TortoiseGit and if Bower keeps asking for your SSH
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
### Ubuntu users
To use Bower on Ubuntu, you might need to link `nodejs` executable to `node`:
Bower supports installing packages from its local cache (without internet connection), if the packages were installed before.
```
bower install <package-name> --offline
```
The content of the cache can be listed with:
```
bower cache list
```
The cache can be cleaned with:
```
bower cache clean
sudo ln -s /usr/bin/nodejs /usr/bin/node
```
## 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 manually set `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:
```
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:
```
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).
Bower can be configured using JSON in a `.bowerrc` file. Read over available options at [bower.io/docs/config](http://bower.io/docs/config).
## 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);
});
```
## Completion (experimental)
_NOTE_: Completion is still not implemented for the 1.0.0 release
Bower now has an experimental `completion` command that is based on, and works
similarly to the [npm completion](https://npmjs.org/doc/completion.html). It is
not available for Windows users.
This command will output a Bash / ZSH script to put into your `~/.bashrc`,
`~/.bash_profile`, or `~/.zshrc` file.
```
bower completion >> ~/.bash_profile
```
## Contact
Have a question?
## Support
* [StackOverflow](http://stackoverflow.com/questions/tagged/bower)
* [Mailinglist](http://groups.google.com/group/twitter-bower) - twitter-bower@googlegroups.com
* [\#bower](http://webchat.freenode.net/?channels=bower) on Freenode
## Contributing to this project
## Contributing
Anyone and everyone is welcome to contribute. Please take a moment to
We welcome [contributions](https://github.com/bower/bower/graphs/contributors) of all kinds from anyone. Please take a moment to
review the [guidelines for contributing](CONTRIBUTING.md).
* [Bug reports](CONTRIBUTING.md#bugs)
@@ -368,57 +112,14 @@ review the [guidelines for contributing](CONTRIBUTING.md).
* [Pull requests](CONTRIBUTING.md#pull-requests)
## Bower Team
### Core team
* [@satazor](https://github.com/satazor)
* [@wibblymat](https://github.com/wibblymat)
* [@paulirish](https://github.com/paulirish)
* [@benschwarz](https://github.com/benschwarz)
* [@sindresorhus](https://github.com/sindresorhus)
* [@svnlto](https://github.com/svnlto)
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)
* [@maccman](https://github.com/maccman)
Note that on Windows for tests to pass you need to configure Git before cloning:
```
git config --global core.autocrlf input
```
## License
Copyright (c) 2014 Twitter and other contributors
Copyright (c) 2015 Twitter and [other contributors](https://github.com/bower/bower/graphs/contributors)
Licensed under the MIT License

42
appveyor.yml Normal file
View File

@@ -0,0 +1,42 @@
# Thanks for Grunt for template of this file!
# http://www.appveyor.com/docs/appveyor-yml
# Fix line endings in Windows. (runs before repo cloning)
init:
- git config --global core.autocrlf input
# Test against these versions of Node.js.
environment:
matrix:
- nodejs_version: "0.10"
# - nodejs_version: "0.11"
# Allow failing jobs for bleeding-edge Node.js versions.
matrix:
allow_failures:
- nodejs_version: "0.11"
# Install scripts. (runs after repo cloning)
install:
# Get the latest stable version of Node 0.STABLE.latest
- ps: Install-Product node $env:nodejs_version
# Install stable version of npm
- npm install -g npm@^2.0.0
# Install subversion
- choco install svn -y
# Install bower
- npm install
# Post-install test scripts.
test_script:
# Output useful info for debugging.
- node --version
- npm --version
- cmd: npm test
# Don't actually build.
build: off
# Set build version format here instead of in the admin panel.
version: "{build}"

View File

@@ -1,28 +1,24 @@
#!/usr/bin/env node
'use strict';
process.bin = process.title = 'bower';
var Q = require('q');
var path = require('path');
var mout = require('mout');
var updateNotifier = require('update-notifier');
var Logger = require('bower-logger');
var osenv = require('osenv');
var userHome = require('user-home');
var bower = require('../lib');
var pkg = require(path.join(__dirname, '..', 'package.json'));
var pkg = require('../package.json');
var cli = require('../lib/util/cli');
var rootCheck = require('../lib/util/rootCheck');
var analytics = require('../lib/util/analytics');
// --------
var options;
var renderer;
var loglevel;
var command;
var commandFunc;
var logger;
var notifier;
var levels = Logger.LEVELS;
options = cli.readOptions({
@@ -73,7 +69,7 @@ while (options.argv.remain.length) {
}
// Ask for Insights on first run.
analytics.setup().then(function () {
analytics.setup(bower.config).then(function () {
// Execute the command
commandFunc = command && mout.object.get(bower.commands, command);
command = command && command.replace(/\./g, ' ');
@@ -129,17 +125,18 @@ analytics.setup().then(function () {
});
// Warn if HOME is not SET
if (!osenv.home()) {
logger.warn('no-home', 'HOME not set, user configuration will not be loaded');
if (!userHome) {
logger.warn('no-home', 'HOME environment variable not set. User config will not be loaded.');
}
// Check for newer version of Bower
notifier = updateNotifier({
packageName: pkg.name,
packageVersion: pkg.version
});
if (bower.config.interactive) {
var updateNotifier = require('update-notifier');
if (notifier.update && levels.info >= loglevel) {
renderer.updateNotice(notifier.update);
// Check for newer version of Bower
var notifier = updateNotifier({pkg: pkg});
if (notifier.update && levels.info >= loglevel) {
notifier.notify();
}
}
});

View File

@@ -1,22 +1,19 @@
var fs = require('graceful-fs');
var fs = require('../../util/fs');
var path = require('path');
var mout = require('mout');
var Q = require('q');
var rimraf = require('rimraf');
var Logger = require('bower-logger');
var rimraf = require('../../util/rimraf');
var endpointParser = require('bower-endpoint-parser');
var PackageRepository = require('../../core/PackageRepository');
var semver = require('../../util/semver');
var cli = require('../../util/cli');
var defaultConfig = require('../../config');
function clean(endpoints, options, config) {
var logger = new Logger();
function clean(logger, endpoints, options, config) {
var decEndpoints;
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) {
@@ -33,21 +30,13 @@ function clean(endpoints, options, config) {
});
}
Q.all([
return Q.all([
clearPackages(decEndpoints, config, logger),
clearLinks(names, config, logger),
!names ? clearCompletion(config, logger) : null
clearLinks(names, config, logger)
])
.spread(function (entries) {
return entries;
})
.done(function (entries) {
logger.emit('end', entries);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
function clearPackages(decEndpoints, config, logger) {
@@ -181,37 +170,16 @@ function clearLinks(names, config, logger) {
});
}
function clearCompletion(config, logger) {
var dir = config.storage.completion;
return Q.nfcall(fs.stat, dir)
.then(function () {
return Q.nfcall(rimraf, dir)
.then(function () {
logger.info('deleted', 'Completion cache', {
file: dir
});
});
}, function (error) {
if (error.code !== 'ENOENT') {
throw error;
}
});
}
// -------------------
clean.line = function (argv) {
var options = clean.options(argv);
return clean(options.argv.remain.slice(2), options);
};
clean.readOptions = function (argv) {
var cli = require('../../util/cli');
var options = cli.readOptions(argv);
var endpoints = options.argv.remain.slice(2);
clean.options = function (argv) {
return cli.readOptions(argv);
};
delete options.argv;
clean.completion = function () {
// TODO:
return [endpoints, options];
};
module.exports = clean;

View File

@@ -1,4 +0,0 @@
module.exports = {
clean: require('./clean'),
list: require('./list')
};

View File

@@ -1,14 +1,11 @@
var mout = require('mout');
var Logger = require('bower-logger');
var PackageRepository = require('../../core/PackageRepository');
var cli = require('../../util/cli');
var defaultConfig = require('../../config');
function list(packages, options, config) {
function list(logger, packages, options, config) {
var repository;
var logger = new Logger();
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
repository = new PackageRepository(config, logger);
// If packages is an empty array, null them
@@ -16,7 +13,7 @@ function list(packages, options, config) {
packages = null;
}
repository.list()
return repository.list()
.then(function (entries) {
if (packages) {
// Filter entries according to the specified packages
@@ -28,29 +25,19 @@ function list(packages, options, config) {
}
return entries;
})
.done(function (entries) {
logger.emit('end', entries);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
list.line = function (argv) {
var options = list.options(argv);
return list(options.argv.remain.slice(2), options);
};
list.readOptions = function (argv) {
var cli = require('../../util/cli');
var options = cli.readOptions(argv);
var packages = options.argv.remain.slice(2);
list.options = function (argv) {
return cli.readOptions(argv);
};
delete options.argv;
list.completion = function () {
// TODO:
return [packages, options];
};
module.exports = list;

View File

@@ -1,31 +0,0 @@
var Logger = require('bower-logger');
var cli = require('../util/cli');
function completion(config) {
var logger = new Logger();
process.nextTick(function () {
logger.emit('end');
});
return logger;
}
// -------------------
completion.line = function (argv) {
var options = completion.options(argv);
var name = options.argv.remain[1];
return completion(name);
};
completion.options = function (argv) {
return cli.readOptions(argv);
};
completion.completion = function () {
// TODO:
};
module.exports = completion;

View File

@@ -1,12 +1,10 @@
var Q = require('q');
var path = require('path');
var fs = require('graceful-fs');
var Logger = require('bower-logger');
var cli = require('../util/cli');
var fs = require('../util/fs');
var createError = require('../util/createError');
function help(name) {
function help(logger, name, config) {
var json;
var logger = new Logger();
if (name) {
json = path.resolve(__dirname, '../../templates/json/help-' + name.replace(/\s+/g, '/') + '.json');
@@ -14,39 +12,28 @@ function help(name) {
json = path.resolve(__dirname, '../../templates/json/help.json');
}
fs.exists(json, function (exists) {
return Q.promise(function (resolve) {
fs.exists(json, resolve);
})
.then(function (exists) {
if (!exists) {
return logger.emit('error', createError('Unknown command: ' + name, 'EUNKOWNCMD', {
throw createError('Unknown command: ' + name, 'EUNKNOWNCMD', {
command: name
}));
});
}
try {
json = require(json);
} catch (error) {
return logger.emit('error', error);
}
logger.emit('end', json);
return require(json);
});
return logger;
}
// -------------------
help.line = function (argv) {
var options = help.options(argv);
help.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain.slice(1).join(' ');
return help(options.argv.remain.slice(1).join(' '));
};
help.options = function (argv) {
return cli.readOptions(argv);
};
help.completion = function () {
// TODO
return [name];
};
module.exports = help;

View File

@@ -1,19 +1,15 @@
var mout = require('mout');
var Logger = require('bower-logger');
var Project = require('../core/Project');
var open = require('open');
var open = require('opn');
var endpointParser = require('bower-endpoint-parser');
var cli = require('../util/cli');
var createError = require('../util/createError');
var defaultConfig = require('../config');
function home(name, config) {
function home(logger, name, config) {
var project;
var promise;
var decEndpoint;
var logger = new Logger();
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
// Get the package meta
@@ -37,7 +33,7 @@ function home(name, config) {
}
// Get homepage and open it
promise.then(function (pkgMeta) {
return promise.then(function (pkgMeta) {
var homepage = pkgMeta.homepage;
if (!homepage) {
@@ -46,31 +42,17 @@ function home(name, config) {
open(homepage);
return homepage;
})
.done(function (homepage) {
logger.emit('end', homepage);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
home.line = function (argv) {
var options = home.options(argv);
home.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain[1];
return home(name);
};
home.options = function (argv) {
return cli.readOptions(argv);
};
home.completion = function () {
// TODO:
return [name];
};
module.exports = home;

View File

@@ -1,5 +1,6 @@
var Q = require('q');
var Logger = require('bower-logger');
var config = require('../config');
/**
* Require commands only when called.
@@ -8,50 +9,73 @@ var Logger = require('bower-logger');
* a command function. The difference is that `cmd = commandFactory()` and `cmd()`
* return as soon as possible and load and execute the command asynchronously.
*/
function lazyRequire(id) {
function command() {
var logger = new Logger();
var commandArgs = arguments;
function commandFactory(id) {
if (process.env.STRICT_REQUIRE) {
require(id);
}
Q.try(function () {
// call require asynchronously
function command() {
var commandArgs = [].slice.call(arguments);
return withLogger(function (logger) {
commandArgs.unshift(logger);
return require(id).apply(undefined, commandArgs);
})
.done(function (commandLogger) {
// forward to exposed logger
commandLogger.on('end', logger.emit.bind(logger, 'end'));
commandLogger.on('error', logger.emit.bind(logger, 'error'));
});
}
function runFromArgv(argv) {
return withLogger(function (logger) {
var command = require(id);
var commandArgs = command.readOptions(argv);
commandArgs.unshift(logger);
return command.apply(undefined, commandArgs);
});
}
function withLogger(func) {
var logger = new Logger();
Q.try(func, logger)
.done(function () {
config.restore();
var args = [].slice.call(arguments);
args.unshift('end');
logger.emit.apply(logger, args);
}, function (error) {
config.restore();
logger.emit('error', error);
});
return logger;
}
function runFromArgv() {
return require(id).line.apply(undefined, arguments);
}
command.line = runFromArgv;
return command;
}
module.exports = {
cache: lazyRequire('./cache'),
completion: lazyRequire('./completion'),
help: lazyRequire('./help'),
home: lazyRequire('./home'),
info: lazyRequire('./info'),
init: lazyRequire('./init'),
install: lazyRequire('./install'),
link: lazyRequire('./link'),
list: lazyRequire('./list'),
lookup: lazyRequire('./lookup'),
prune: lazyRequire('./prune'),
register: lazyRequire('./register'),
search: lazyRequire('./search'),
update: lazyRequire('./update'),
uninstall: lazyRequire('./uninstall'),
version: lazyRequire('./version')
cache: {
clean: commandFactory('./cache/clean'),
list: commandFactory('./cache/list'),
},
help: commandFactory('./help'),
home: commandFactory('./home'),
info: commandFactory('./info'),
init: commandFactory('./init'),
install: commandFactory('./install'),
link: commandFactory('./link'),
list: commandFactory('./list'),
login: commandFactory('./login'),
lookup: commandFactory('./lookup'),
prune: commandFactory('./prune'),
register: commandFactory('./register'),
search: commandFactory('./search'),
update: commandFactory('./update'),
uninstall: commandFactory('./uninstall'),
unregister: commandFactory('./unregister'),
version: commandFactory('./version')
};

View File

@@ -1,26 +1,27 @@
var mout = require('mout');
var Q = require('q');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var PackageRepository = require('../core/PackageRepository');
var cli = require('../util/cli');
var Tracker = require('../util/analytics').Tracker;
var defaultConfig = require('../config');
function info(endpoint, property, config) {
function info(logger, endpoint, property, config) {
if (!endpoint) {
return;
}
var repository;
var decEndpoint;
var tracker;
var logger = new Logger();
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
repository = new PackageRepository(config, logger);
tracker = new Tracker(config);
decEndpoint = endpointParser.decompose(endpoint);
tracker.trackDecomposedEndpoints('info', [decEndpoint]);
Q.all([
return Q.all([
getPkgMeta(repository, decEndpoint, property),
decEndpoint.target === '*' && !property ? repository.versions(decEndpoint.source) : null
])
@@ -34,14 +35,7 @@ function info(endpoint, property, config) {
}
return pkgMeta;
})
.done(function (result) {
logger.emit('end', result);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
function getPkgMeta(repository, decEndpoint, property) {
@@ -62,24 +56,13 @@ function getPkgMeta(repository, decEndpoint, property) {
// -------------------
info.line = function (argv) {
var options = info.options(argv);
info.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var pkg = options.argv.remain[1];
var property = options.argv.remain[2];
if (!pkg) {
return null;
}
return info(pkg, property);
};
info.options = function (argv) {
return cli.readOptions(argv);
};
info.completion = function () {
// TODO:
return [pkg, property];
};
module.exports = info;

View File

@@ -1,37 +1,36 @@
var mout = require('mout');
var fs = require('graceful-fs');
var fs = require('../util/fs');
var path = require('path');
var Q = require('q');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var Project = require('../core/Project');
var defaultConfig = require('../config');
var GitHubResolver = require('../core/resolvers/GitHubResolver');
var GitFsResolver = require('../core/resolvers/GitFsResolver');
var cli = require('../util/cli');
var cmd = require('../util/cmd');
var createError = require('../util/createError');
function init(config) {
function init(logger, config) {
var project;
var logger = new Logger();
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = config || {};
if (!config.cwd) {
config.cwd = process.cwd();
}
config = defaultConfig(config);
// This command requires interactive to be enabled
if (!config.interactive) {
process.nextTick(function () {
logger.emit('error', createError('Register requires an interactive shell', 'ENOINT', {
details: 'Note that you can manually force an interactive shell with --config.interactive'
}));
throw createError('Register requires an interactive shell', 'ENOINT', {
details: 'Note that you can manually force an interactive shell with --config.interactive'
});
return logger;
}
project = new Project(config, logger);
// Start with existing JSON details
readJson(project, logger)
return readJson(project, logger)
// Fill in defaults
.then(setDefaults.bind(null, config))
// Now prompt user to make changes
@@ -41,14 +40,7 @@ function init(config) {
// Set dependencies based on the response
.spread(setDependencies.bind(null, project))
// All done!
.spread(saveJson.bind(null, project, logger))
.done(function (json) {
logger.emit('end', json);
}, function (error) {
logger.emit('error', error);
});
return logger;
.spread(saveJson.bind(null, project, logger));
}
function readJson(project, logger) {
@@ -65,7 +57,7 @@ function readJson(project, logger) {
function saveJson(project, logger, json) {
// Cleanup empty props (null values, empty strings, objects and arrays)
mout.object.forOwn(json, function (value, key) {
if (value == null || mout.lang.isEmpty(value)) {
if (!validConfigValue(value)) {
delete json[key];
}
});
@@ -88,6 +80,18 @@ function saveJson(project, logger, json) {
});
}
// Test if value is of a type supported by bower.json[0] - Object, Array, String, Boolean - or a Number
// [0]: https://github.com/bower/bower.json-spec
function validConfigValue(val) {
return (
mout.lang.isObject(val) ||
mout.lang.isArray(val) ||
mout.lang.isString(val) ||
mout.lang.isBoolean(val) ||
mout.lang.isNumber(val)
);
}
function setDefaults(config, json) {
var name;
var promise = Q.resolve();
@@ -97,19 +101,6 @@ function setDefaults(config, json) {
json.name = path.basename(config.cwd);
}
// Version
if (!json.version) {
// Assume latest semver tag if it's a git repo
promise = promise.then(function () {
return GitFsResolver.versions(config.cwd)
.then(function (versions) {
json.version = versions[0] || '0.0.0';
}, function () {
json.version = '0.0.0';
});
});
}
// Main
if (!json.main) {
// Remove '.js' from the end of the package name if it is there
@@ -183,12 +174,6 @@ function promptUser(logger, json) {
'default': json.name,
'type': 'input'
},
{
'name': 'version',
'message': 'version',
'default': json.version,
'type': 'input'
},
{
'name': 'description',
'message': 'description',
@@ -254,7 +239,6 @@ function promptUser(logger, json) {
return Q.nfcall(logger.prompt.bind(logger), questions)
.then(function (answers) {
json.name = answers.name;
json.version = answers.version;
json.description = answers.description;
json.main = answers.main;
json.moduleType = answers.moduleType;
@@ -329,16 +313,8 @@ function setDependencies(project, json, answers) {
// -------------------
init.line = function () {
return init();
};
init.options = function (argv) {
return cli.readOptions(argv);
};
init.completion = function () {
// TODO:
init.readOptions = function (argv) {
return [];
};
module.exports = init;

View File

@@ -1,19 +1,15 @@
var mout = require('mout');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var Project = require('../core/Project');
var cli = require('../util/cli');
var Tracker = require('../util/analytics').Tracker;
var defaultConfig = require('../config');
function install(endpoints, options, config) {
function install(logger, endpoints, options, config) {
var project;
var decEndpoints;
var tracker;
var logger = new Logger();
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
if (options.save === undefined) {
options.save = config.defaultSave;
}
@@ -27,35 +23,27 @@ function install(endpoints, options, config) {
});
tracker.trackDecomposedEndpoints('install', decEndpoints);
project.install(decEndpoints, options)
.done(function (installed) {
tracker.trackPackages('installed', installed);
logger.emit('end', installed);
}, function (error) {
logger.emit('error', error);
});
return logger;
return project.install(decEndpoints, options, config);
}
// -------------------
install.line = function (argv) {
var options = install.options(argv);
return install(options.argv.remain.slice(1), options);
};
install.readOptions = function (argv) {
var cli = require('../util/cli');
install.options = function (argv) {
return cli.readOptions({
var options = cli.readOptions({
'force-latest': { type: Boolean, shorthand: 'F'},
'production': { type: Boolean, shorthand: 'p' },
'save': { type: Boolean, shorthand: 'S' },
'save-dev': { type: Boolean, shorthand: 'D' }
'save-dev': { type: Boolean, shorthand: 'D' },
'save-exact': { type: Boolean, shorthand: 'E' }
}, argv);
};
install.completion = function () {
// TODO:
var packages = options.argv.remain.slice(1);
delete options.argv;
return [packages, options];
};
module.exports = install;

View File

@@ -1,21 +1,25 @@
var path = require('path');
var rimraf = require('rimraf');
var mout = require('mout');
var rimraf = require('../util/rimraf');
var Q = require('q');
var Logger = require('bower-logger');
var Project = require('../core/Project');
var createLink = require('../util/createLink');
var cli = require('../util/cli');
var defaultConfig = require('../config');
function linkSelf(config) {
var project;
var logger = new Logger();
function link(logger, name, localName, config) {
if (name) {
return linkTo(logger, name, localName, config);
} else {
return linkSelf(logger, config);
}
}
config = mout.object.deepFillIn(config || {}, defaultConfig);
function linkSelf(logger, config) {
var project;
config = defaultConfig(config);
project = new Project(config, logger);
project.getJson()
return project.getJson()
.then(function (json) {
var src = config.cwd;
var dst = path.join(config.storage.links, json.name);
@@ -32,30 +36,23 @@ function linkSelf(config) {
dst: dst
};
});
})
.done(function (result) {
logger.emit('end', result);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
function linkTo(name, localName, config) {
function linkTo(logger, name, localName, config) {
var src;
var dst;
var logger = new Logger();
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);
dst = path.join(process.cwd(), config.directory, localName);
dst = path.join(config.cwd, config.directory, localName);
// Delete destination folder if any
Q.nfcall(rimraf, dst)
return Q.nfcall(rimraf, dst)
// Link locally
.then(function () {
return createLink(src, dst);
@@ -70,41 +67,18 @@ function linkTo(name, localName, config) {
dst: dst,
installed: installed
};
})
.done(function (result) {
logger.emit('end', result);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
var link = {
linkTo: linkTo,
linkSelf: linkSelf
};
link.line = function (argv) {
var options = link.options(argv);
link.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain[1];
var localName = options.argv.remain[2];
if (name) {
return linkTo(name, localName);
}
return linkSelf();
};
link.options = function (argv) {
return cli.readOptions(argv);
};
link.completion = function () {
// TODO:
return [name, localName];
};
module.exports = link;

View File

@@ -1,15 +1,12 @@
var path = require('path');
var mout = require('mout');
var Q = require('q');
var Logger = require('bower-logger');
var Project = require('../core/Project');
var semver = require('../util/semver');
var cli = require('../util/cli');
var defaultConfig = require('../config');
function list(options, config) {
function list(logger, options, config) {
var project;
var logger = new Logger();
options = options || {};
@@ -18,13 +15,11 @@ function list(options, config) {
options.relative = true;
}
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
project.getTree(options)
return project.getTree(options)
.spread(function (tree, flattened) {
var baseDir = path.dirname(path.join(config.cwd, config.directory));
// Relativize paths
// Also normalize paths on windows
project.walkTree(tree, function (node) {
@@ -33,7 +28,7 @@ function list(options, config) {
}
if (options.relative) {
node.canonicalDir = path.relative(baseDir, node.canonicalDir);
node.canonicalDir = path.relative(config.cwd, node.canonicalDir);
}
if (options.paths) {
node.canonicalDir = normalize(node.canonicalDir);
@@ -48,7 +43,7 @@ function list(options, config) {
}
if (options.relative) {
node.canonicalDir = path.relative(baseDir, node.canonicalDir);
node.canonicalDir = path.relative(config.cwd, node.canonicalDir);
}
if (options.paths) {
node.canonicalDir = normalize(node.canonicalDir);
@@ -70,16 +65,7 @@ function list(options, config) {
.then(function () {
return tree;
});
})
.done(function (value) {
logger.emit('end', value);
}, function (error) {
logger.emit('error', error);
});
logger.json = !!options.paths;
return logger;
}
function checkVersions(project, tree, logger) {
@@ -95,7 +81,7 @@ function checkVersions(project, tree, logger) {
}, true);
if (nodes.length) {
logger.info('check-new', 'Checking for new versions of the project dependencies..');
logger.info('check-new', 'Checking for new versions of the project dependencies...');
}
// Check for new versions for each node
@@ -164,20 +150,17 @@ function normalize(src) {
// -------------------
list.line = function (argv) {
var options = list.options(argv);
return list(options);
};
list.readOptions = function (argv) {
var cli = require('../util/cli');
list.options = function (argv) {
return cli.readOptions({
var options = cli.readOptions({
'paths': { type: Boolean, shorthand: 'p' },
'relative': { type: Boolean, shorthand: 'r' }
}, argv);
};
list.completion = function () {
// TODO:
delete options.argv;
return [options];
};
module.exports = list;

123
lib/commands/login.js Normal file
View File

@@ -0,0 +1,123 @@
var Configstore = require('configstore');
var GitHub = require('github');
var Q = require('q');
var createError = require('../util/createError');
var defaultConfig = require('../config');
function login(logger, options, config) {
var configstore = new Configstore('bower-github');
config = defaultConfig(config);
var promise;
options = options || {};
if (options.token) {
promise = Q.resolve({ token: options.token });
} else {
// This command requires interactive to be enabled
if (!config.interactive) {
logger.emit('error', createError('Login requires an interactive shell', 'ENOINT', {
details: 'Note that you can manually force an interactive shell with --config.interactive'
}));
return;
}
var questions = [
{
'name': 'username',
'message': 'Username',
'type': 'input',
'default': configstore.get('username')
},
{
'name': 'password',
'message': 'Password',
'type': 'password'
}
];
var github = new GitHub({
version: '3.0.0'
});
promise = Q.nfcall(logger.prompt.bind(logger), questions)
.then(function (answers) {
configstore.set('username', answers.username);
github.authenticate({
type: 'basic',
username: answers.username,
password: answers.password
});
return Q.ninvoke(github.authorization, 'create', {
scopes: ['user', 'repo'],
note: 'Bower command line client (' + (new Date()).toISOString() + ')'
});
});
}
return promise.then(function (result) {
configstore.set('accessToken', result.token);
return result;
}, function (error) {
var message;
try {
message = JSON.parse(error.message).message;
} catch (e) {
message = 'Authorization failed';
}
var questions = [
{
'name': 'otpcode',
'message': 'Two-Factor Auth Code',
'type': 'input'
}
];
if (message === 'Must specify two-factor authentication OTP code.') {
return Q.nfcall(logger.prompt.bind(logger), questions)
.then(function (answers) {
return Q.ninvoke(github.authorization, 'create', {
scopes: ['user', 'repo'],
note: 'Bower command line client (' + (new Date()).toISOString() + ')',
headers: {
'X-GitHub-OTP': answers.otpcode
}
});
})
.then(function (result) {
configstore.set('accessToken', result.token);
return result;
}, function () {
logger.emit('error', createError(message, 'EAUTH'));
});
} else {
logger.emit('error', createError(message, 'EAUTH'));
}
});
}
// -------------------
login.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions({
token: { type: String, shorthand: 't' },
}, argv);
delete options.argv;
return [options];
};
module.exports = login;

View File

@@ -1,20 +1,20 @@
var mout = require('mout');
var Q = require('q');
var Logger = require('bower-logger');
var RegistryClient = require('bower-registry-client');
var cli = require('../util/cli');
var defaultConfig = require('../config');
function lookup(name, config) {
var registryClient;
var logger = new Logger();
function lookup(logger, name, config) {
if (!name) {
return new Q(null);
}
config = mout.object.deepFillIn(config || {}, defaultConfig);
var registryClient;
config = defaultConfig(config);
config.cache = config.storage.registry;
registryClient = new RegistryClient(config, logger);
Q.nfcall(registryClient.lookup.bind(registryClient), name)
return Q.nfcall(registryClient.lookup.bind(registryClient), name)
.then(function (entry) {
// TODO: Handle entry.type.. for now it's only 'alias'
// When we got published packages, this needs to be adjusted
@@ -22,35 +22,17 @@ function lookup(name, config) {
name: name,
url: entry && entry.url
};
})
.done(function (result) {
logger.emit('end', result);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
lookup.line = function (argv) {
var options = lookup.options(argv);
lookup.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain[1];
if (!name) {
return null;
}
return lookup(name);
};
lookup.options = function (argv) {
return cli.readOptions(argv);
};
lookup.completion = function () {
// TODO:
return [name];
};
module.exports = lookup;

View File

@@ -1,25 +1,15 @@
var mout = require('mout');
var Logger = require('bower-logger');
var Project = require('../core/Project');
var cli = require('../util/cli');
var defaultConfig = require('../config');
function prune(options, config) {
function prune(logger, options, config) {
var project;
var logger = new Logger();
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
clean(project, options)
.done(function (removed) {
logger.emit('end', removed);
}, function (error) {
logger.emit('error', error);
});
return logger;
return clean(project, options);
}
function clean(project, options, removed) {
@@ -50,19 +40,16 @@ function clean(project, options, removed) {
// -------------------
prune.line = function (argv) {
var options = prune.options(argv);
return prune(options);
};
prune.readOptions = function (argv) {
var cli = require('../util/cli');
prune.options = function (argv) {
return cli.readOptions({
var options = cli.readOptions({
'production': { type: Boolean, shorthand: 'p' },
}, argv);
};
prune.completion = function () {
// TODO:
delete options.argv;
return [options];
};
module.exports = prune;

View File

@@ -1,48 +1,31 @@
var mout = require('mout');
var Q = require('q');
var chalk = require('chalk');
var PackageRepository = require('../core/PackageRepository');
var Logger = require('bower-logger');
var Config = require('bower-config');
var Tracker = require('../util/analytics').Tracker;
var cli = require('../util/cli');
var createError = require('../util/createError');
var defaultConfig = require('../config');
var GitHubResolver = require('../core/resolvers/GitHubResolver');
function register(name, url, config) {
function register(logger, name, url, config) {
var repository;
var registryClient;
var tracker;
var logger = new Logger();
var force;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
force = config.force;
tracker = new Tracker(config);
name = (name || '').trim();
url = (url || '').trim();
// Bypass any cache
config.offline = false;
config.force = true;
// Trim name
name = name.trim();
process.nextTick(function () {
// Verify name
// TODO: Verify with the new spec regexp?
if (!name) {
return logger.emit('error', createError('Please type a name', 'EINVNAME'));
}
// The public registry only allows git:// endpoints
// As such, we attempt to convert URLs as necessary
if (config.registry.register === Config.DEFAULT_REGISTRY) {
url = convertUrl(url, logger);
if (!mout.string.startsWith(url, 'git://')) {
return logger.emit('error', createError('The registry only accepts URLs starting with git://', 'EINVFORMAT'));
}
return Q.try(function () {
// Verify name and url
if (!name || !url) {
throw createError('Usage: bower register <name> <url>', 'EINVFORMAT');
}
tracker.track('register');
@@ -50,88 +33,54 @@ function register(name, url, config) {
// Attempt to resolve the package referenced by the URL to ensure
// everything is ok before registering
repository = new PackageRepository(config, logger);
repository.fetch({ name: name, source: url, target: '*' })
.spread(function (canonicalDir, pkgMeta) {
if (pkgMeta.private) {
throw createError('The package you are trying to register is marked as private', 'EPRIV');
}
// If non interactive or user forced, bypass confirmation
if (!config.interactive || force) {
return true;
}
// Confirm if the user really wants to register
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message: 'Registering a package will make it installable via the registry (' +
chalk.cyan.underline(config.registry.register) + '), continue?',
default: true
});
})
.then(function (result) {
// If user response was negative, abort
if (!result) {
return;
}
// Register
registryClient = repository.getRegistryClient();
logger.action('register', url, {
name: name,
url: url
});
return Q.nfcall(registryClient.register.bind(registryClient), name, url);
})
.done(function (result) {
tracker.track('registered');
logger.emit('end', result);
}, function (error) {
logger.emit('error', error);
});
});
return logger;
}
function convertUrl(url, logger) {
var pair;
var newUrl;
if (!mout.string.startsWith(url, 'git://')) {
// Convert GitHub 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);
return repository.fetch({ name: name, source: url, target: '*' });
})
.spread(function (canonicalDir, pkgMeta) {
if (pkgMeta.private) {
throw createError('The package you are trying to register is marked as private', 'EPRIV');
}
}
return newUrl || url;
// If non interactive or user forced, bypass confirmation
if (!config.interactive || force) {
return true;
}
// Confirm if the user really wants to register
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message: 'Registering a package will make it installable via the registry (' +
chalk.cyan.underline(config.registry.register) + '), continue?',
default: true
});
})
.then(function (result) {
// If user response was negative, abort
if (!result) {
return;
}
// Register
registryClient = repository.getRegistryClient();
logger.action('register', url, {
name: name,
url: url
});
return Q.nfcall(registryClient.register.bind(registryClient), name, url);
});
}
// -------------------
register.line = function (argv) {
var options = register.options(argv);
register.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain[1];
var url = options.argv.remain[2];
if (!name || !url) {
return null;
}
return register(name, url);
};
register.options = function (argv) {
return cli.readOptions(argv);
};
register.completion = function () {
// TODO:
return [name, url];
};
module.exports = register;

View File

@@ -1,18 +1,13 @@
var mout = require('mout');
var Q = require('q');
var Logger = require('bower-logger');
var RegistryClient = require('bower-registry-client');
var cli = require('../util/cli');
var Tracker = require('../util/analytics').Tracker;
var defaultConfig = require('../config');
function search(name, config) {
function search(logger, name, config) {
var registryClient;
var promise;
var tracker;
var logger = new Logger();
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
config.cache = config.storage.registry;
registryClient = new RegistryClient(config, logger);
@@ -21,35 +16,21 @@ function search(name, config) {
// If no name was specified, list all packages
if (!name) {
promise = Q.nfcall(registryClient.list.bind(registryClient));
return Q.nfcall(registryClient.list.bind(registryClient));
// Otherwise search it
} else {
promise = Q.nfcall(registryClient.search.bind(registryClient), name);
return Q.nfcall(registryClient.search.bind(registryClient), name);
}
promise
.done(function (results) {
logger.emit('end', results);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
search.line = function (argv) {
var options = search.options(argv);
return search(options.argv.remain.slice(1).join(' '), options);
};
search.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain.slice(1).join(' ');
search.options = function (argv) {
return cli.readOptions(argv);
};
search.completion = function () {
// TODO:
return [name];
};
module.exports = search;

View File

@@ -1,24 +1,25 @@
var mout = require('mout');
var Logger = require('bower-logger');
var Q = require('q');
var Project = require('../core/Project');
var cli = require('../util/cli');
var Tracker = require('../util/analytics').Tracker;
var defaultConfig = require('../config');
function uninstall(names, options, config) {
function uninstall(logger, names, options, config) {
if (!names.length) {
return new Q();
}
var project;
var tracker;
var logger = new Logger();
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
tracker = new Tracker(config);
tracker.trackNames('uninstall', names);
project.getTree(options)
return project.getTree(options)
.spread(function (tree, flattened) {
// Uninstall nodes
return project.uninstall(names, options)
@@ -37,15 +38,7 @@ function uninstall(names, options, config) {
// Clean them!
return clean(project, children, uninstalled);
});
})
.done(function (uninstalled) {
logger.emit('end', uninstalled);
tracker.trackNames('uninstalled', Object.keys(uninstalled));
}, function (error) {
logger.emit('error', error);
});
return logger;
}
function clean(project, names, removed) {
@@ -109,26 +102,19 @@ function clean(project, names, removed) {
// -------------------
uninstall.line = function (argv) {
var options = uninstall.options(argv);
var names = options.argv.remain.slice(1);
uninstall.readOptions = function (argv) {
var cli = require('../util/cli');
if (!names.length) {
return null;
}
return uninstall(names, options);
};
uninstall.options = function (argv) {
return cli.readOptions({
var options = cli.readOptions({
'save': { type: Boolean, shorthand: 'S' },
'save-dev': { type: Boolean, shorthand: 'D' }
}, argv);
};
uninstall.completion = function () {
// TODO:
var names = options.argv.remain.slice(1);
delete options.argv;
return [names, options];
};
module.exports = uninstall;

View File

@@ -0,0 +1,85 @@
var chalk = require('chalk');
var Q = require('q');
var defaultConfig = require('../config');
var PackageRepository = require('../core/PackageRepository');
var Tracker = require('../util/analytics').Tracker;
var createError = require('../util/createError');
function unregister(logger, name, config) {
if (!name) {
return;
}
var repository;
var registryClient;
var tracker;
var force;
config = defaultConfig(config);
force = config.force;
tracker = new Tracker(config);
// Bypass any cache
config.offline = false;
config.force = true;
// Trim name
name = name.trim();
repository = new PackageRepository(config, logger);
tracker.track('unregister');
if (!config.accessToken) {
return logger.emit('error',
createError('Use "bower login" with collaborator credentials', 'EFORBIDDEN')
);
}
return Q.resolve()
.then(function () {
// If non interactive or user forced, bypass confirmation
if (!config.interactive || force) {
return true;
}
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message: 'You are about to remove component "' + chalk.cyan.underline(name) + '" from the bower registry (' + chalk.cyan.underline(config.registry.register) + '). It is generally considered bad behavior to remove versions of a library that others are depending on. Are you really sure?',
default: false
});
})
.then(function (result) {
// If user response was negative, abort
if (!result) {
return;
}
registryClient = repository.getRegistryClient();
logger.action('unregister', name, { name: name });
return Q.nfcall(registryClient.unregister.bind(registryClient), name);
})
.then(function (result) {
tracker.track('unregistered');
logger.info('Package unregistered', name);
return result;
});
}
// -------------------
unregister.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain[1];
return [name];
};
module.exports = unregister;

View File

@@ -1,15 +1,11 @@
var mout = require('mout');
var Logger = require('bower-logger');
var Project = require('../core/Project');
var cli = require('../util/cli');
var defaultConfig = require('../config');
function update(names, options, config) {
function update(logger, names, options, config) {
var project;
var logger = new Logger();
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
// If names is an empty array, null them
@@ -17,32 +13,24 @@ function update(names, options, config) {
names = null;
}
project.update(names, options)
.done(function (installed) {
logger.emit('end', installed);
}, function (error) {
logger.emit('error', error);
});
return logger;
return project.update(names, options);
}
// -------------------
update.line = function (argv) {
var options = update.options(argv);
return update(options.argv.remain.slice(1), options);
};
update.readOptions = function (argv) {
var cli = require('../util/cli');
update.options = function (argv) {
return cli.readOptions({
var options = cli.readOptions({
'force-latest': { type: Boolean, shorthand: 'F' },
'production': { type: Boolean, shorthand: 'p' }
}, argv);
};
update.completion = function () {
// TODO:
var names = options.argv.remain.slice(1);
delete options.argv;
return [names, options];
};
module.exports = update;

View File

@@ -1,38 +1,29 @@
var mout = require('mout');
var semver = require('semver');
var Logger = require('bower-logger');
var which = require('which');
var fs = require('fs');
var fs = require('../util/fs');
var path = require('path');
var Q = require('q');
var execFile = require('child_process').execFile;
var Project = require('../core/Project');
var cli = require('../util/cli');
var defaultConfig = require('../config');
var createError = require('../util/createError');
function version(versionArg, options, config) {
function version(logger, versionArg, options, config) {
var project;
var logger = new Logger();
config = mout.object.deepFillIn(config || {}, defaultConfig);
options = options || {};
config = defaultConfig(config);
project = new Project(config, logger);
bump(project, versionArg, options.message)
.done(function () {
logger.emit('end');
}, function (error) {
logger.emit('error', error);
});
return logger;
return bump(project, versionArg, options.message);
}
function bump(project, versionArg, message) {
var cwd = project._config.cwd || process.cwd();
var newVersion;
var doGitCommit = false;
return checkGit()
return checkGit(cwd)
.then(function (hasGit) {
doGitCommit = hasGit;
})
@@ -44,11 +35,12 @@ function bump(project, versionArg, message) {
.then(project.saveJson.bind(project))
.then(function () {
if (doGitCommit) {
return gitCommitAndTag(newVersion, message);
return gitCommitAndTag(cwd, newVersion, message);
}
})
.then(function () {
console.log('v' + newVersion);
return newVersion;
});
}
@@ -66,12 +58,12 @@ function getNewVersion(currentVersion, versionArg) {
return newVersion;
}
function checkGit() {
var gitDir = path.join(process.cwd(), '.git');
function checkGit(cwd) {
var gitDir = path.join(cwd, '.git');
return Q.nfcall(fs.stat, gitDir)
.then(function (stat) {
if (stat.isDirectory()) {
return checkGitStatus();
return checkGitStatus(cwd);
}
return false;
}, function () {
@@ -80,14 +72,14 @@ function checkGit() {
});
}
function checkGitStatus() {
function checkGitStatus(cwd) {
return Q.nfcall(which, 'git')
.fail(function (err) {
err.code = 'ENOGIT';
throw err;
})
.then(function () {
return Q.nfcall(execFile, 'git', ['status', '--porcelain'], {env: process.env});
return Q.nfcall(execFile, 'git', ['status', '--porcelain'], {env: process.env, cwd: cwd});
})
.then(function (value) {
var stdout = value[0];
@@ -108,33 +100,29 @@ function filterModifiedStatusLines(stdout) {
});
}
function gitCommitAndTag(newVersion, message) {
message = message || 'v' + newVersion;
function gitCommitAndTag(cwd, newVersion, message) {
var tag = 'v' + newVersion;
message = message || tag;
message = message.replace(/%s/g, newVersion);
return Q.nfcall(execFile, 'git', ['add', 'bower.json'], {env: process.env})
return Q.nfcall(execFile, 'git', ['add', 'bower.json'], {env: process.env, cwd: cwd})
.then(function () {
return Q.nfcall(execFile, 'git', ['commit', '-m', message], {env: process.env});
return Q.nfcall(execFile, 'git', ['commit', '-m', message], {env: process.env, cwd: cwd});
})
.then(function () {
return Q.nfcall(execFile, 'git', ['tag', newVersion, '-am', message], {env: process.env});
return Q.nfcall(execFile, 'git', ['tag', tag, '-am', message], {env: process.env, cwd: cwd});
});
}
// -------------------
version.line = function (argv) {
var options = version.options(argv);
return version(options.argv.remain[1], options);
};
version.readOptions = function (argv) {
var cli = require('../util/cli');
version.options = function (argv) {
return cli.readOptions({
var options = cli.readOptions({
'message': { type: String, shorthand: 'm'}
}, argv);
return [options.argv.remain[1], options];
};
version.completion = function () {
// TODO:
};
module.exports = version;
module.exports = version;

View File

@@ -1,33 +1,67 @@
var tty = require('tty');
var mout = require('mout');
var config = require('bower-config').read();
var cli = require('./util/cli');
var object = require('mout').object;
var bowerConfig = require('bower-config');
var Configstore = require('configstore');
// Delete the json attribute because it is no longer supported
// and conflicts with --json
delete config.json;
var current;
// If interactive is auto (null), guess its value
if (config.interactive == null) {
config.interactive = process.bin === 'bower' && tty.isatty(1);
function defaultConfig(config) {
config = config || {};
return readCachedConfig(config.cwd || process.cwd(), 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 && !process.env.CI;
function readCachedConfig(cwd, overwrites) {
current = bowerConfig.create(cwd).load(overwrites);
var config = current.toObject();
var configstore = new Configstore('bower-github').all;
object.mixIn(config, configstore);
// Delete the json attribute because it is no longer supported
// and conflicts with --json
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
if (process.bin === 'bower') {
var cli = require('./util/cli');
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 restoreConfig () {
if (current) {
current.restore();
}
}
module.exports = config;
function resetCache () {
restoreConfig();
current = undefined;
}
module.exports = defaultConfig;
module.exports.restore = restoreConfig;
module.exports.reset = resetCache;

View File

@@ -2,8 +2,8 @@ var Q = require('q');
var mout = require('mout');
var path = require('path');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var fs = require('graceful-fs');
var rimraf = require('../util/rimraf');
var fs = require('../util/fs');
var endpointParser = require('bower-endpoint-parser');
var PackageRepository = require('./PackageRepository');
var semver = require('../util/semver');
@@ -23,11 +23,13 @@ function Manager(config, logger) {
Manager.prototype.configure = function (setup) {
var targetsHash = {};
this._conflicted = {};
// Targets
this._targets = setup.targets || [];
// Targets - ignore those specified in ignoredDependencies
this._targets = mout.array.reject(setup.targets || [], function(target) {
return mout.array.contains(this._config.ignoredDependencies, target.name );
}, this);
this._targets.forEach(function (decEndpoint) {
decEndpoint.initialName = decEndpoint.name;
decEndpoint.dependants = mout.object.values(decEndpoint.dependants);
@@ -80,6 +82,17 @@ Manager.prototype.configure = function (setup) {
return this;
};
/**
* It starts recursive fetching of targets. The flow is as follows:
*
* 1. this._fetch() that calls:
* 2. this._onFetchError() or this._onFetchSuccess() that calls:
* 3. this._parseDependencies() that calls for all dependencies:
* 4. this._fetch() again.
*
* Fetching and parsing of dependencies continues until all dependencies are fetched.
*
*/
Manager.prototype.resolve = function () {
// If already resolving, error out
if (this._working) {
@@ -109,8 +122,43 @@ 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 componentsDirContents;
var that = this;
// If already resolving, error out
@@ -125,21 +173,27 @@ 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 = [];
componentsDirContents = fs.readdirSync(componentsDir);
mout.object.forOwn(that._dissected, function (decEndpoint, name) {
var promise;
var dst;
var release = decEndpoint.pkgMeta._release;
var exists = mout.array.contains(componentsDirContents, name);
that._logger.action('install', name + (release ? '#' + release : ''), that.toData(decEndpoint));
dst = path.join(componentsDir, name);
if (exists && !that._installed[name] && !that._config.force) {
// There is a folder in the components directory that has
// this name, but it was not installed by bower.
that._logger.warn('skipped', name + ' was not installed because there is already a non-bower directory with that name in the components directory (' + path.relative(that._config.cwd, dst) + '). You can force installation with --force.');
return;
}
// Remove existent and copy canonical dir
promise = Q.nfcall(rimraf, dst)
.then(copy.copyDir.bind(copy, decEndpoint.canonicalDir, dst))
@@ -169,9 +223,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
@@ -309,7 +360,7 @@ Manager.prototype._onFetchSuccess = function (decEndpoint, canonicalDir, pkgMeta
resolved.push(decEndpoint);
// Parse dependencies
this._parseDependencies(decEndpoint, pkgMeta, 'dependencies');
this._parseDependencies(decEndpoint, pkgMeta);
// Check if there are incompatibilities for this package name
// If there are, we need to fetch them
@@ -386,18 +437,32 @@ Manager.prototype._failFast = function () {
}.bind(this), 20000);
};
Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta, jsonKey) {
/**
* Parses decEndpoint dependencies and fetches missing ones.
*
* It fetches all non-yet-fetching dependencies with _fetch.
*
*
* @param {string} pkgMeta Metadata of package to resolve dependencies.
*/
Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta) {
var pending = [];
decEndpoint.dependencies = decEndpoint.dependencies || {};
// Parse package dependencies
mout.object.forOwn(pkgMeta[jsonKey], function (value, key) {
mout.object.forOwn(pkgMeta.dependencies, function (value, key) {
var resolved;
var fetching;
var compatible;
var childDecEndpoint = endpointParser.json2decomposed(key, value);
// Check if this depdendency should be skipped.
if (mout.array.contains(this._config.ignoredDependencies, childDecEndpoint.name)) {
this._logger.action('skipped', childDecEndpoint.name, this.toData(decEndpoint));
return;
}
// Check if a compatible one is already resolved
// If there's one, we don't need to resolve it twice
resolved = this._resolved[key];
@@ -461,11 +526,24 @@ Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta, jsonKey)
if (pending.length > 0) {
Q.all(pending)
.then(function () {
this._parseDependencies(decEndpoint, pkgMeta, jsonKey);
this._parseDependencies(decEndpoint, pkgMeta);
}.bind(this));
}
};
/**
* After batch of packages has been fetched, the results are stored in _resolved hash.
*
* {
* packageName: [array, of, decEndpoints]
* }
*
* This method takes all possible decEndpoints and selects only one of them per packageName.
*
* It stores selected packages in the this._dissected array.
*
* The _dissected array is used later on by install method to move packages to config.directory.
*/
Manager.prototype._dissect = function () {
var err;
var componentsDir;
@@ -543,13 +621,16 @@ Manager.prototype._dissect = function () {
return;
}
this._logger.info('resolution', 'Removed unnecessary ' + name + '#' + resolution + ' resolution', {
// Packages that didn't have any conflicts in resolution yet have
// "resolution" entry in bower.json are deemed unnecessary
//
// Probably in future we want to use them for force given resolution, but for now...
// See: https://github.com/bower/bower/issues/1362
this._logger.warn('extra-resolution', 'Unnecessary resolution: ' + name + '#' + resolution, {
name: name,
resolution: resolution,
action: 'delete'
});
delete this._resolutions[name];
}, this);
// Filter only packages that need to be installed
@@ -558,19 +639,28 @@ Manager.prototype._dissect = function () {
var installedMeta = this._installed[name];
var dst;
// Analyse a few props
// Skip linked dependencies
if (decEndpoint.linked) {
return false;
}
// Skip if source is the same as dest
// FIXME: Shoudn't we force installation if force flag is used here?
dst = path.join(componentsDir, name);
if (dst === decEndpoint.canonicalDir) {
return false;
}
// We skip installing decEndpoint, if:
// 1. We have installed package with the same name
// 2. Packages have matching meta fields (needs explanation)
// 3. We didn't force the installation
if (installedMeta &&
installedMeta._target === decEndpoint.target &&
installedMeta._originalSource === decEndpoint.source &&
installedMeta._release === decEndpoint.pkgMeta._release
) {
return false;
}
// Skip if source is the same as dest
dst = path.join(componentsDir, name);
if (dst === decEndpoint.canonicalDir) {
return false;
return this._config.force;
}
return true;
@@ -744,7 +834,7 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
choices = picks.map(function (pick, index) { return index + 1; });
return Q.nfcall(this._logger.prompt.bind(this._logger), {
type: 'input',
message: 'Answer:',
message: 'Answer',
validate: function (choice) {
choice = Number(mout.string.trim(choice.trim(), '!'));
@@ -791,10 +881,28 @@ Manager.prototype._storeResolution = function (pick) {
this._resolutions[name] = resolution;
};
/**
* Checks if some endpoint is compatible with already resolved target.
*
* It is used in two situations:
* * checks if resolved component matches dependency constraint
* * checks if not resolved component matches already fetched component
*
* If candidate matches already resolved component, it won't be downloaded.
*
* @param {Endpoint} candidate endpoint
* @param {Endpoint} resolved endpoint
*
* @return {Boolean}
*/
Manager.prototype._areCompatible = function (candidate, resolved) {
var resolvedVersion;
var highestCandidate;
var highestResolved;
var candidateIsRange = semver.validRange(candidate.target);
var resolvedIsRange = semver.validRange(resolved.target);
var candidateIsVersion = semver.valid(candidate.target);
var resolvedIsVersion = semver.valid(resolved.target);
// Check if targets are equal
if (candidate.target === resolved.target) {
@@ -802,30 +910,77 @@ Manager.prototype._areCompatible = function (candidate, resolved) {
}
resolvedVersion = resolved.pkgMeta && resolved.pkgMeta.version;
// If there is no pkgMeta, resolvedVersion is downloading now
// Check based on target requirements
if (!resolvedVersion) {
// If one of the targets is range and other is version,
// check version against the range
if (candidateIsVersion && resolvedIsRange) {
return semver.satisfies(candidate.target, resolved.target);
}
if (resolvedIsVersion && candidateIsRange) {
return semver.satisfies(resolved.target, candidate.target);
}
if (resolvedIsVersion && candidateIsVersion) {
return semver.eq(resolved.target, candidate.target);
}
// If both targets are range, check that both have same
// higher cap
if (resolvedIsRange && candidateIsRange) {
highestCandidate =
this._getCap(semver.toComparators(candidate.target), 'highest');
highestResolved =
this._getCap(semver.toComparators(resolved.target), 'highest');
// This never happens, but you can't be sure without tests
if (!highestResolved.version || !highestCandidate.version) {
return false;
}
return semver.eq(highestCandidate.version, highestResolved.version) &&
highestCandidate.comparator === highestResolved.comparator;
}
return false;
}
// If target is a version, compare against the resolved version
if (semver.valid(candidate.target)) {
if (candidateIsVersion) {
return semver.eq(candidate.target, resolvedVersion);
}
// If target is a range, check if the max versions of the range are the same
// and if the resolved version satisfies the candidate target
if (semver.validRange(candidate.target) && semver.validRange(resolved.target)) {
highestCandidate = this._getCap(semver.toComparators(candidate.target), 'highest');
highestResolved = this._getCap(semver.toComparators(resolved.target), 'highest');
return highestCandidate.version && highestResolved.version &&
semver.eq(highestCandidate.version, highestResolved.version) &&
highestCandidate.comparator === highestResolved.comparator &&
semver.satisfies(resolvedVersion, candidate.target);
// If target is a range, check if resolved version satisfies it
if (candidateIsRange) {
return semver.satisfies(resolvedVersion, candidate.target);
}
return false;
};
/**
* Gets highest/lowest version from set of comparators.
*
* The only thing that matters for this function is version number.
* Returned comparator is splitted to comparator and version parts.
*
* It is used to receive lowest / highest bound of toComparators result:
* semver.toComparators('~0.1.1') // => [ [ '>=0.1.1-0', '<0.2.0-0' ] ]
*
* Examples:
*
* _getCap([['>=2.1.1-0', '<2.2.0-0'], '<3.2.0'], 'highest')
* // => { comparator: '<', version: '3.2.0' }
*
* _getCap([['>=2.1.1-0', '<2.2.0-0'], '<3.2.0'], 'lowest')
* // => { comparator: '>=', version: '2.1.1-0' }
*
* @param {Array.<Array|string>} comparators
* @param {string} side, 'highest' (default) or 'lowest'
*
* @return {{ comparator: string, version: string }}
*/
Manager.prototype._getCap = function (comparators, side) {
var matches;
var candidate;
@@ -861,6 +1016,23 @@ Manager.prototype._getCap = function (comparators, side) {
return cap;
};
/**
* Filters out unique endpoints, comparing by name and then source.
*
* It leaves last matching endpoint.
*
* Examples:
*
* manager._uniquify([
* { name: 'foo', source: 'google.com' },
* { name: 'foo', source: 'facebook.com' }
* ]);
* // => { name: 'foo', source: 'facebook.com' }
*
* @param {Array.<Endpoint>} decEndpoints
* @return {Array.<Endpoint>} Filtered elements of decEndpoints
*
*/
Manager.prototype._uniquify = function (decEndpoints) {
var length = decEndpoints.length;

View File

@@ -38,15 +38,18 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
that._extendLog(log, info);
});
// Get the appropriate resolver
return resolverFactory(decEndpoint, this._config, logger, this._registryClient)
return this._getResolver(decEndpoint, logger)
// Decide if we retrieve from the cache or not
// Also decide if we validate the cached entry or not
.then(function (resolver) {
info.resolver = resolver;
isTargetable = resolver.constructor.isTargetable;
// If force flag is used, bypass cache
if (!resolver.isCacheable()) {
return that._resolve(resolver, logger);
}
// If force flag is used, bypass cache, but write to cache anyway
if (that._config.force) {
logger.action('resolve', resolver.getSource() + '#' + resolver.getTarget());
return that._resolve(resolver, logger);
@@ -87,7 +90,7 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
logger.action('validate', (pkgMeta._release ? pkgMeta._release + ' against ': '') +
resolver.getSource() + (resolver.getTarget() ? '#' + resolver.getTarget() : ''));
return resolver.hasNew(canonicalDir, pkgMeta)
return resolver.hasNew(pkgMeta)
.then(function (hasNew) {
// If there are no new contents, resolve to
// the cached one
@@ -113,15 +116,15 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
PackageRepository.prototype.versions = function (source) {
// Resolve the source using the factory because the
// source can actually be a registry name
return resolverFactory.getConstructor(source, this._config, this._registryClient)
.spread(function (ConcreteResolver, source) {
return this._getResolver({ source: source })
.then(function (resolver) {
// If offline, resolve using the cached versions
if (this._config.offline) {
return this._resolveCache.versions(source);
return this._resolveCache.versions(resolver.getSource());
}
// Otherwise, fetch remotely
return ConcreteResolver.versions(source);
return resolver.constructor.versions(resolver.getSource());
}.bind(this));
};
@@ -164,20 +167,33 @@ PackageRepository.clearRuntimeCache = function () {
// ---------------------
PackageRepository.prototype._getResolver = function (decEndpoint, logger) {
logger = logger || this._logger;
// Get the appropriate resolver
return resolverFactory(decEndpoint, { config: this._config, logger: logger }, this._registryClient);
};
PackageRepository.prototype._resolve = function (resolver, logger) {
var that = this;
// Resolve the resolver
return resolver.resolve()
// Store in the cache
.then(function (canonicalDir) {
return this._resolveCache.store(canonicalDir, resolver.getPkgMeta());
}.bind(this))
if (!resolver.isCacheable()) {
return canonicalDir;
}
return that._resolveCache.store(canonicalDir, resolver.getPkgMeta());
})
// Resolve promise with canonical dir and package meta
.then(function (dir) {
var pkgMeta = resolver.getPkgMeta();
logger.info('resolved', resolver.getSource() + (pkgMeta._release ? '#' + pkgMeta._release : ''));
return [dir, pkgMeta, resolver.constructor.isTargetable()];
}.bind(this));
});
};
PackageRepository.prototype._extendLog = function (log, info) {

View File

@@ -1,26 +1,21 @@
var glob = require('glob');
var path = require('path');
var fs = require('graceful-fs');
var fs = require('../util/fs');
var Q = require('q');
var mout = require('mout');
var rimraf = require('rimraf');
var rimraf = require('../util/rimraf');
var endpointParser = require('bower-endpoint-parser');
var Logger = require('bower-logger');
var md5 = require('md5-hex');
var Manager = require('./Manager');
var defaultConfig = require('../config');
var semver = require('../util/semver');
var md5 = require('../util/md5');
var createError = require('../util/createError');
var readJson = require('../util/readJson');
var validLink = require('../util/validLink');
var scripts = require('./scripts');
function Project(config, logger) {
// This is the only architecture component that ensures defaults
// 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 = config;
this._logger = logger || new Logger();
this._manager = new Manager(this._config, this._logger);
@@ -29,7 +24,7 @@ function Project(config, logger) {
// -----------------
Project.prototype.install = function (decEndpoints, options) {
Project.prototype.install = function (decEndpoints, options, config) {
var that = this;
var targets = [];
var resolved = {};
@@ -41,21 +36,28 @@ Project.prototype.install = function (decEndpoints, options) {
}
this._options = options || {};
this._config = config || {};
this._working = true;
// 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.missing || node.different) {
targets.push(node);
} else if (node.incompatible) {
if (node.incompatible) {
incompatibles.push(node);
} else if (node.missing || node.different || that._config.force) {
targets.push(node);
} else {
resolved[name] = node;
}
});
}, true);
// Add decomposed endpoints as targets
decEndpoints = decEndpoints || [];
@@ -71,21 +73,33 @@ Project.prototype.install = function (decEndpoints, 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) {
// Handle save and saveDev options
if (that._options.save || that._options.saveDev) {
if (that._options.save || that._options.saveDev || that._options.saveExact) {
// Cycle through the specified endpoints
decEndpoints.forEach(function (decEndpoint) {
var jsonEndpoint;
jsonEndpoint = endpointParser.decomposed2json(decEndpoint);
if (that._options.save) {
that._json.dependencies = mout.object.mixIn(that._json.dependencies || {}, jsonEndpoint);
if (that._options.saveExact) {
if (decEndpoint.name !== decEndpoint.source) {
jsonEndpoint[decEndpoint.name] = decEndpoint.source + '#' + decEndpoint.pkgMeta.version;
} else {
jsonEndpoint[decEndpoint.name] = decEndpoint.pkgMeta.version;
}
}
if (that._options.saveDev) {
that._json.devDependencies = mout.object.mixIn(that._json.devDependencies || {}, jsonEndpoint);
} else {
that._json.dependencies = mout.object.mixIn(that._json.dependencies || {}, jsonEndpoint);
}
});
}
@@ -93,7 +107,9 @@ Project.prototype.install = function (decEndpoints, options) {
// 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 () {
@@ -174,11 +190,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;
});
});
});
})
@@ -462,7 +486,8 @@ Project.prototype._analyse = function () {
var isSaved = jsonCopy.dependencies[key] || jsonCopy.devDependencies[key];
// The _direct propery is saved by the manager when .newly is specified
if (!isSaved && pkgMeta._direct) {
// It may happen pkgMeta is undefined if package is uninstalled
if (!isSaved && pkgMeta && pkgMeta._direct) {
decEndpoint.extraneous = true;
if (decEndpoint.linked) {
@@ -521,9 +546,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 () {
@@ -533,9 +556,29 @@ Project.prototype._readJson = function () {
return Q.resolve(this._json);
}
// Read local json
return this._json = readJson(this._config.cwd, {
assume: { name: path.basename(this._config.cwd) || 'root' }
return Q.fcall(function(){
// This will throw if package.json does not exist
return fs.readFileSync(path.join(that._config.cwd, 'package.json'));
})
.then(function(buffer){
// If package.json exists, use it's values as defaults
var defaults = {}, npm = JSON.parse(buffer.toString());
defaults.name = npm.name || path.basename(that._config.cwd) || 'root';
defaults.description = npm.description;
defaults.main = npm.main;
defaults.authors = npm.contributors || npm.author;
defaults.license = npm.license;
defaults.keywords = npm.keywords;
return defaults;
})
.catch(function(err){
// Most likely no package.json so just set default name
return { name: path.basename(that._config.cwd) || 'root' };
})
.then(function(defaults){
return that._json = readJson(that._config.cwd, { assume: defaults });
})
.spread(function (json, deprecated, assumed) {
var jsonStr;
@@ -728,7 +771,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
@@ -738,10 +781,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) {
@@ -788,15 +832,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

@@ -1,15 +1,15 @@
var fs = require('graceful-fs');
var fs = require('../util/fs');
var path = require('path');
var mout = require('mout');
var Q = require('q');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var rimraf = require('../util/rimraf');
var LRU = require('lru-cache');
var lockFile = require('lockfile');
var md5 = require('md5-hex');
var semver = require('../util/semver');
var readJson = require('../util/readJson');
var copy = require('../util/copy');
var md5 = require('../util/md5');
function ResolveCache(config) {
// TODO: Make some config entries, such as:

View File

@@ -1,166 +1,216 @@
var Q = require('q');
var fs = require('graceful-fs');
var fs = require('../util/fs');
var path = require('path');
var mout = require('mout');
var resolvers = require('./resolvers');
var createError = require('../util/createError');
function createInstance(decEndpoint, config, logger, registryClient) {
return getConstructor(decEndpoint.source, config, registryClient)
.spread(function (ConcreteResolver, source, fromRegistry) {
var decEndpointCopy = mout.object.pick(decEndpoint, ['name', 'target']);
var pluginResolverFactory = require('./resolvers/pluginResolverFactory');
decEndpointCopy.source = source;
function createInstance(decEndpoint, options, registryClient) {
decEndpoint = mout.object.pick(decEndpoint, ['name', 'target', 'source']);
// Signal if it was fetched from the registry
if (fromRegistry) {
decEndpoint.registry = true;
// If no name was specified, assume the name from the registry
if (!decEndpointCopy.name) {
decEndpointCopy.name = decEndpoint.name = decEndpoint.source;
}
}
options.version = require('../../package.json').version;
return new ConcreteResolver(decEndpointCopy, config, logger);
return getConstructor(decEndpoint, options, registryClient)
.spread(function (ConcreteResolver, decEndpoint) {
return new ConcreteResolver(decEndpoint, options.config, options.logger);
});
}
function getConstructor(source, config, registryClient) {
var absolutePath,
promise;
function getConstructor(decEndpoint, options, registryClient) {
var source = decEndpoint.source;
var config = options.config;
// Below we try a series of async tests to guess the type of resolver to use
// If a step was unable to guess the resolver, it returns undefined
// If a step can guess the resolver, it returns with construcotor of resolver
var promise = Q.resolve();
var addResolver = function (resolverFactory) {
promise = promise.then(function (result) {
if (result === undefined) {
return resolverFactory(decEndpoint, options);
} else {
return result;
}
});
};
// Plugin resolvers.
//
// It requires each resolver defined in config.resolvers and calls
// its "match" to check if given resolves supports given decEndpoint
addResolver(function () {
var selectedResolver;
var resolverNames;
if (Array.isArray(config.resolvers)) {
resolverNames = config.resolvers;
} else if (!!config.resolvers) {
resolverNames = config.resolvers.split(',');
} else {
resolverNames = [];
}
var resolverPromises = resolverNames.map(function (resolverName) {
var resolver = resolvers[resolverName]
|| pluginResolverFactory(require(resolverName), options);
return function () {
if (selectedResolver === undefined) {
var match = resolver.match.bind(resolver);
return Q.fcall(match, source).then(function (result) {
if (result) {
return selectedResolver = resolver;
}
});
} else {
return selectedResolver;
}
};
});
return resolverPromises.reduce(Q.when, new Q(undefined)).then(function (resolver) {
if (resolver) {
return Q.fcall(resolver.locate.bind(resolver), decEndpoint.source).then(function (result) {
if (result && result !== decEndpoint.source) {
decEndpoint.source = result;
decEndpoint.registry = true;
return getConstructor(decEndpoint, options, registryClient);
} else {
return [resolver, decEndpoint];
}
});
}
});
});
// Git case: git git+ssh, git+http, git+https
// .git at the end (probably ssh shorthand)
// git@ at the start
if (/^git(\+(ssh|https?))?:\/\//i.test(source) || /\.git\/?$/i.test(source) || /^git@/i.test(source)) {
source = source.replace(/^git\+/, '');
return Q.fcall(function () {
addResolver(function() {
if (/^git(\+(ssh|https?))?:\/\//i.test(source) || /\.git\/?$/i.test(source) || /^git@/i.test(source)) {
decEndpoint.source = source.replace(/^git\+/, '');
// If it's a GitHub repository, return the specialized resolver
if (resolvers.GitHub.getOrgRepoPair(source)) {
return [resolvers.GitHub, source];
return [resolvers.GitHub, decEndpoint];
}
return [resolvers.GitRemote, source];
});
}
return [resolvers.GitRemote, decEndpoint];
}
});
// SVN case: svn, svn+ssh, svn+http, svn+https, svn+file
if (/^svn(\+(ssh|https?|file))?:\/\//i.test(source)) {
return Q.fcall(function () {
return [resolvers.Svn, source];
});
}
addResolver(function () {
if (/^svn(\+(ssh|https?|file))?:\/\//i.test(source)) {
return [resolvers.Svn, decEndpoint];
}
});
// URL case
if (/^https?:\/\//i.exec(source)) {
return Q.fcall(function () {
return [resolvers.Url, source];
});
}
addResolver(function () {
if (/^https?:\/\//i.exec(source)) {
return [resolvers.Url, decEndpoint];
}
});
// Below we try a series of async tests to guess the type of resolver to use
// If a step was unable to guess the resolver, it throws an error
// If a step was able to guess the resolver, it resolves with a function
// That function returns a promise that will resolve with the concrete type
// If source is ./ or ../ or an absolute path
absolutePath = path.resolve(config.cwd, source);
if (/^\.\.?[\/\\]/.test(source) || /^~\//.test(source) || path.normalize(source).replace(/[\/\\]+$/, '') === absolutePath) {
promise = Q.nfcall(fs.stat, path.join(absolutePath, '.git'))
.then(function (stats) {
if (stats.isDirectory()) {
return function () {
return Q.resolve([resolvers.GitFs, absolutePath]);
};
}
addResolver(function () {
var absolutePath = path.resolve(config.cwd, source);
throw new Error('Not a Git repository');
})
// If not, check if source is a valid Subversion repository
.fail(function () {
return Q.nfcall(fs.stat, path.join(absolutePath, '.svn'))
if (/^\.\.?[\/\\]/.test(source) || /^~\//.test(source) ||
path.normalize(source).replace(/[\/\\]+$/, '') === absolutePath
) {
return Q.nfcall(fs.stat, path.join(absolutePath, '.git'))
.then(function (stats) {
decEndpoint.source = absolutePath;
if (stats.isDirectory()) {
return function () {
return Q.resolve([resolvers.Svn, absolutePath]);
};
return Q.resolve([resolvers.GitFs, decEndpoint]);
}
throw new Error('Not a Subversion repository');
});
})
// If not, check if source is a valid file/folder
.fail(function () {
return Q.nfcall(fs.stat, absolutePath)
.then(function () {
return function () {
return Q.resolve([resolvers.Fs, absolutePath]);
};
});
});
} else {
promise = Q.reject(new Error('Not an absolute or relative file'));
}
throw new Error('Not a Git repository');
})
// If not, check if source is a valid Subversion repository
.fail(function () {
return Q.nfcall(fs.stat, path.join(absolutePath, '.svn'))
.then(function (stats) {
decEndpoint.source = absolutePath;
if (stats.isDirectory()) {
return Q.resolve([resolvers.Svn, decEndpoint]);
}
throw new Error('Not a Subversion repository');
});
})
// If not, check if source is a valid file/folder
.fail(function () {
return Q.nfcall(fs.stat, absolutePath)
.then(function () {
decEndpoint.source = absolutePath;
return Q.resolve([resolvers.Fs, decEndpoint]);
});
});
}
});
return promise
// Check if is a shorthand and expand it
.fail(function (err) {
var parts;
addResolver(function () {
// Skip ssh and/or URL with auth
if (/[:@]/.test(source)) {
throw err;
return;
}
// Ensure exactly only one "/"
parts = source.split('/');
var parts = source.split('/');
if (parts.length === 2) {
source = mout.string.interpolate(config.shorthandResolver, {
decEndpoint.source = mout.string.interpolate(config.shorthandResolver, {
shorthand: source,
owner: parts[0],
package: parts[1]
});
return function () {
return getConstructor(source, config, registryClient);
};
return getConstructor(decEndpoint, options, registryClient);
}
});
throw err;
})
// As last resort, we try the registry
.fail(function (err) {
addResolver(function () {
if (!registryClient) {
throw err;
return;
}
return function () {
return Q.nfcall(registryClient.lookup.bind(registryClient), source)
.then(function (entry) {
if (!entry) {
throw createError('Package ' + source + ' not found', 'ENOTFOUND');
}
return Q.nfcall(registryClient.lookup.bind(registryClient), source)
.then(function (entry) {
if (!entry) {
throw createError('Package ' + source + ' not found', 'ENOTFOUND');
}
// TODO: Handle entry.type.. for now it's only 'alias'
// When we got published packages, this needs to be adjusted
source = entry.url;
decEndpoint.registry = true;
return getConstructor(source, config, registryClient)
.spread(function (ConcreteResolver, source) {
return [ConcreteResolver, source, true];
});
});
};
})
// If we got the function, simply call and return
.then(function (func) {
return func();
// Finally throw a meaningful error
}, function () {
if (!decEndpoint.name) {
decEndpoint.name = decEndpoint.source;
}
decEndpoint.source = entry.url;
return getConstructor(decEndpoint, options);
});
});
addResolver(function () {
throw createError('Could not find appropriate resolver for ' + source, 'ENORESOLVER');
});
return promise;
}
function clearRuntimeCache() {

View File

@@ -1,5 +1,5 @@
var util = require('util');
var fs = require('graceful-fs');
var fs = require('../../util/fs');
var path = require('path');
var mout = require('mout');
var Q = require('q');

View File

@@ -1,6 +1,7 @@
var util = require('util');
var path = require('path');
var mout = require('mout');
var Q = require('q');
var GitRemoteResolver = require('./GitRemoteResolver');
var download = require('../../util/download');
var extract = require('../../util/extract');
@@ -35,6 +36,11 @@ 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 = function() {
return Q.resolve(true);
};
}
util.inherits(GitHubResolver, GitRemoteResolver);
@@ -66,7 +72,7 @@ GitHubResolver.prototype._checkout = function () {
// Download tarball
return download(tarballUrl, file, {
proxy: this._config.httpsProxy,
ca: this._config.ca.default,
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
headers: reqHeaders

View File

@@ -25,6 +25,11 @@ function GitRemoteResolver(decEndpoint, config, logger) {
} else {
this._host = url.parse(this._source).host;
}
this._remote = url.parse(this._source);
// Verify whether the server supports shallow cloning
this._shallowClone = this._supportsShallowCloning;
}
util.inherits(GitRemoteResolver, GitResolver);
@@ -112,34 +117,36 @@ GitRemoteResolver.prototype._fastClone = function (resolution) {
branch = resolution.tag || resolution.branch;
args = ['clone', this._source, '-b', branch, '--progress', '.'];
// If the host does not support shallow clones, we don't use --depth=1
if (!GitRemoteResolver._noShallow.get(this._host)) {
args.push('--depth', 1);
}
return cmd('git', args, { cwd: this._tempDir })
.spread(function (stdout, stderr) {
// Only after 1.7.10 --branch accepts tags
// Detect those cases and inform the user to update git otherwise it's
// a lot slower than newer versions
if (!/branch .+? not found/i.test(stderr)) {
return;
return this._shallowClone().then(function (shallowCloningSupported) {
// If the host does not support shallow clones, we don't use --depth=1
if (shallowCloningSupported && !GitRemoteResolver._noShallow.get(this._host)) {
args.push('--depth', 1);
}
that._logger.warn('old-git', 'It seems you are using an old version of git, it will be slower and propitious to errors!');
return cmd('git', ['checkout', resolution.commit], { cwd: that._tempDir });
}, function (err) {
// Some git servers do not support shallow clones
// When that happens, we mark this host and try again
if (!GitRemoteResolver._noShallow.has(that._source) &&
err.details &&
/(rpc failed|shallow|--depth)/i.test(err.details)
) {
GitRemoteResolver._noShallow.set(that._host, true);
return that._fastClone(resolution);
}
return cmd('git', args, { cwd: that._tempDir })
.spread(function (stdout, stderr) {
// Only after 1.7.10 --branch accepts tags
// Detect those cases and inform the user to update git otherwise it's
// a lot slower than newer versions
if (!/branch .+? not found/i.test(stderr)) {
return;
}
throw err;
that._logger.warn('old-git', 'It seems you are using an old version of git, it will be slower and propitious to errors!');
return cmd('git', ['checkout', resolution.commit], { cwd: that._tempDir });
}, function (err) {
// Some git servers do not support shallow clones
// When that happens, we mark this host and try again
if (!GitRemoteResolver._noShallow.has(that._source) &&
err.details &&
/(rpc failed|shallow|--depth)/i.test(err.details)
) {
GitRemoteResolver._noShallow.set(that._host, true);
return that._fastClone(resolution);
}
throw err;
});
});
};
@@ -157,6 +164,79 @@ GitRemoteResolver.prototype._suggestProxyWorkaround = function (err) {
}
};
// Verifies whether the server supports shallow cloning.
// This is done according to the rules found in the following links:
// * https://github.com/dimitri/el-get/pull/1921/files
// * http://stackoverflow.com/questions/9270488/is-it-possible-to-detect-whether-a-http-git-remote-is-smart-or-dumb
//
// Summary of the rules:
// * Protocols like ssh or git always support shallow cloning
// * HTTP-based protocols can be verified by sending a HEAD or GET request to the URI (appended to the URL of the Git repo):
// /info/refs?service=git-upload-pack
// * If the server responds with a 'Content-Type' header of 'application/x-git-upload-pack-advertisement',
// the server supports shallow cloning ("smart server")
// * If the server responds with a different content type, the server does not support shallow cloning ("dumb server")
// * Instead of doing the HEAD or GET request using an HTTP client, we're letting Git and Curl do the heavy lifting.
// Calling Git with the GIT_CURL_VERBOSE=2 env variable will provide the Git and Curl output, which includes
// the content type. This has the advantage that Git will take care of using stored credentials and any additional
// negotiation that needs to take place.
//
// The above should cover most cases, including BitBucket.
GitRemoteResolver.prototype._supportsShallowCloning = function () {
var value = true;
// Verify that the remote could be parsed and that a protocol is set
// This case is unlikely, but let's still cover it.
if (this._remote == null || this._remote.protocol == null) {
return Q.resolve(false);
}
if (!this._host || !this._config.shallowCloneHosts || this._config.shallowCloneHosts.indexOf(this._host) === -1) {
return Q.resolve(false);
}
// Check for protocol - the remote check for hosts supporting shallow cloning is only required for
// HTTP or HTTPS, not for Git or SSH.
// Also check for hosts that have been checked in a previous request and have been found to support
// shallow cloning.
if (mout.string.startsWith(this._remote.protocol, 'http')
&& !GitRemoteResolver._canShallow.get(this._host)) {
// Provide GIT_CURL_VERBOSE=2 environment variable to capture curl output.
// Calling ls-remote includes a call to the git-upload-pack service, which returns the content type in the response.
var processEnv = mout.object.merge(process.env, { 'GIT_CURL_VERBOSE': '2' });
value = cmd('git', ['ls-remote', '--heads', this._source], {
env: processEnv
})
.spread(function (stdout, stderr) {
// Check stderr for content-type, ignore stdout
var isSmartServer;
// If the content type is 'x-git', then the server supports shallow cloning
isSmartServer = mout.string.contains(stderr,
'Content-Type: application/x-git-upload-pack-advertisement');
this._logger.debug('detect-smart-git', 'Smart Git host detected: ' + isSmartServer);
if (isSmartServer) {
// Cache this host
GitRemoteResolver._canShallow.set(this._host, true);
}
return isSmartServer;
}.bind(this));
}
else {
// One of the following cases:
// * A non-HTTP/HTTPS protocol
// * A host that has been checked before and that supports shallow cloning
return Q.resolve(true);
}
return value;
};
// ------------------------------
// Grab refs remotely
@@ -195,4 +275,7 @@ GitRemoteResolver.refs = function (source) {
// Store hosts that do not support shallow clones here
GitRemoteResolver._noShallow = new LRU({ max: 50, maxAge: 5 * 60 * 1000 });
// Store hosts that support shallow clones here
GitRemoteResolver._canShallow = new LRU({ max: 50, maxAge: 5 * 60 * 1000 });
module.exports = GitRemoteResolver;

View File

@@ -1,8 +1,7 @@
var util = require('util');
var path = require('path');
var Q = require('q');
var chmodr = require('chmodr');
var rimraf = require('rimraf');
var rimraf = require('../../util/rimraf');
var mkdirp = require('mkdirp');
var which = require('which');
var LRU = require('lru-cache');
@@ -10,7 +9,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 +20,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) {
@@ -41,7 +39,7 @@ mout.object.mixIn(GitResolver, Resolver);
// -----------------
GitResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
GitResolver.prototype._hasNew = function (pkgMeta) {
var oldResolution = pkgMeta._resolution || {};
return this._findResolution()
@@ -145,7 +143,7 @@ GitResolver.prototype._findResolution = function (target) {
throw createError('No tag found that was able to satisfy ' + target, 'ENORESTARGET', {
details: !versions.length ?
'No versions found in ' + that._source :
'Available versions: ' + versions.map(function (version) { return version.version; }).join(', ')
'Available versions in ' + that._source + ': ' + versions.map(function (version) { return version.version; }).join(', ')
});
});
});
@@ -165,6 +163,18 @@ GitResolver.prototype._findResolution = function (target) {
return that._resolution = { type: 'branch', branch: target, commit: branches[target] };
}
if ((/^[a-f0-9]{4,40}$/).test(target)) {
if (target.length < 12) {
that._logger.warn(
'short-sha',
'Consider using longer commit SHA to avoid conflicts'
);
}
that._resolution = { type: 'commit', commit: target };
return that._resolution;
}
branches = Object.keys(branches);
tags = Object.keys(tags);
@@ -184,23 +194,7 @@ GitResolver.prototype._findResolution = function (target) {
GitResolver.prototype._cleanup = function () {
var gitFolder = path.join(this._tempDir, '.git');
// Remove the .git folder
// Note that on windows, we need to chmod to 0777 before due to a bug in git
// See: https://github.com/isaacs/rimraf/issues/19
if (process.platform === 'win32') {
return Q.nfcall(chmodr, gitFolder, 0777)
.then(function () {
return Q.nfcall(rimraf, gitFolder);
}, function (err) {
// If .git does not exist, chmodr returns ENOENT
// so, we ignore that error code
if (err.code !== 'ENOENT') {
throw err;
}
});
} else {
return Q.nfcall(rimraf, gitFolder);
}
return Q.nfcall(rimraf, gitFolder);
};
GitResolver.prototype._savePkgMeta = function (meta) {

View File

@@ -1,9 +1,9 @@
var fs = require('graceful-fs');
var fs = require('../../util/fs');
var path = require('path');
var Q = require('q');
var tmp = require('tmp');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var rimraf = require('../../util/rimraf');
var readJson = require('../../util/readJson');
var createError = require('../../util/createError');
var removeIgnores = require('../../util/removeIgnores');
@@ -43,9 +43,8 @@ Resolver.prototype.getPkgMeta = function () {
return this._pkgMeta;
};
Resolver.prototype.hasNew = function (canonicalDir, pkgMeta) {
Resolver.prototype.hasNew = function (pkgMeta) {
var promise;
var metaFile;
var that = this;
// If already working, error out
@@ -56,24 +55,7 @@ Resolver.prototype.hasNew = function (canonicalDir, pkgMeta) {
this._working = true;
// Avoid reading the package meta if already given
if (pkgMeta) {
promise = this._hasNew(canonicalDir, pkgMeta);
// Otherwise call _hasNew with both the package meta and the canonical dir
} else {
metaFile = path.join(canonicalDir, '.bower.json');
promise = readJson(metaFile)
.spread(function (pkgMeta) {
return that._hasNew(canonicalDir, pkgMeta);
}, function (err) {
that._logger.debug('read-json', 'Failed to read ' + metaFile, {
filename: metaFile,
error: err
});
return true; // Simply resolve to true if there was an error reading the file
});
}
promise = this._hasNew(pkgMeta);
return promise.fin(function () {
that._working = false;
@@ -114,6 +96,26 @@ Resolver.prototype.resolve = function () {
});
};
Resolver.prototype.isCacheable = function () {
// Bypass cache for local dependencies
if (this._source &&
/^(?:file:[\/\\]{2}|[A-Z]:)?\.?\.?[\/\\]/.test(this._source)
) {
return false;
}
// We don't want to cache moving targets like branches
if (this._pkgMeta &&
this._pkgMeta._resolution &&
this._pkgMeta._resolution.type === 'branch')
{
return false;
}
return true;
};
// -----------------
// Abstract functions that must be implemented by concrete resolvers
@@ -123,7 +125,7 @@ Resolver.prototype._resolve = function () {
// Abstract functions that can be re-implemented by concrete resolvers
// as necessary
Resolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
Resolver.prototype._hasNew = function (pkgMeta) {
return Q.resolve(true);
};
@@ -149,8 +151,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));
};
@@ -201,26 +203,36 @@ Resolver.prototype._applyPkgMeta = function (meta) {
}
// Otherwise remove them from the temp dir
return removeIgnores(this._tempDir, meta.ignore)
return removeIgnores(this._tempDir, meta)
.then(function () {
return meta;
});
};
Resolver.prototype._savePkgMeta = function (meta) {
var that = this;
var contents;
// Store original source & target
meta._source = this._source;
meta._target = this._target;
['main', 'ignore'].forEach(function (attr) {
if (meta[attr]) return;
that._logger.log(
'warn', 'invalid-meta',
(meta.name || 'component') + ' is missing "' + attr + '" entry in bower.json'
);
});
// Stringify contents
contents = JSON.stringify(meta, null, 2);
return Q.nfcall(fs.writeFile, path.join(this._tempDir, '.bower.json'), contents)
.then(function () {
return this._pkgMeta = meta;
}.bind(this));
return that._pkgMeta = meta;
});
};
module.exports = Resolver;

View File

@@ -1,7 +1,5 @@
var util = require('util');
var path = require('path');
var Q = require('q');
var rimraf = require('rimraf');
var which = require('which');
var LRU = require('lru-cache');
var mout = require('mout');
@@ -42,7 +40,7 @@ SvnResolver.getSource = function (source) {
.replace(/\/+$/, ''); // Remove trailing slashes
};
SvnResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
SvnResolver.prototype._hasNew = function (pkgMeta) {
var oldResolution = pkgMeta._resolution || {};
return this._findResolution()
@@ -67,19 +65,13 @@ SvnResolver.prototype._resolve = function () {
return this._findResolution()
.then(function () {
return that._checkout()
// Always run cleanup after checkout to ensure that .svn is removed!
// If it's not removed, problems might arise when the "tmp" module attempts
// to delete the temporary folder
.fin(function () {
return that._cleanup();
});
return that._export();
});
};
// -----------------
SvnResolver.prototype._checkout = function () {
SvnResolver.prototype._export = function () {
var promise;
var timer;
var reporter;
@@ -88,19 +80,19 @@ SvnResolver.prototype._checkout = function () {
this.source = SvnResolver.getSource(this._source);
this._logger.action('checkout', resolution.tag || resolution.branch || resolution.commit, {
this._logger.action('export', resolution.tag || resolution.branch || resolution.commit, {
resolution: resolution,
to: this._tempDir
});
if (resolution.type === 'commit') {
promise = cmd('svn', ['checkout', this._source + '/trunk', '-r' + resolution.commit, this._tempDir]);
promise = cmd('svn', ['export', '--force', '--non-interactive', this._source + '/trunk', '-r' + resolution.commit, this._tempDir]);
} else if (resolution.type === 'branch' && resolution.branch === 'trunk') {
promise = cmd('svn', ['checkout', this._source + '/trunk', this._tempDir]);
promise = cmd('svn', ['export', '--force', '--non-interactive', this._source + '/trunk', this._tempDir]);
} else if (resolution.type === 'branch') {
promise = cmd('svn', ['checkout', this._source + '/branches/' + resolution.branch, this._tempDir]);
promise = cmd('svn', ['export', '--force', '--non-interactive', this._source + '/branches/' + resolution.branch, this._tempDir]);
} else {
promise = cmd('svn', ['checkout', this._source + '/tags/' + resolution.tag, this._tempDir]);
promise = cmd('svn', ['export', '--force', '--non-interactive', this._source + '/tags/' + resolution.tag, this._tempDir]);
}
// Throttle the progress reporter to 1 time each sec
@@ -196,7 +188,7 @@ SvnResolver.prototype._findResolution = function (target) {
throw createError('No tag found that was able to satisfy ' + target, 'ENORESTARGET', {
details: !versions.length ?
'No versions found in ' + that._source :
'Available versions: ' + versions.map(function (version) { return version.version; }).join(', ')
'Available versions in ' + that._source + ': ' + versions.map(function (version) { return version.version; }).join(', ')
});
});
});
@@ -232,12 +224,6 @@ SvnResolver.prototype._findResolution = function (target) {
});
};
SvnResolver.prototype._cleanup = function () {
var svnFolder = path.join(this._tempDir, '.svn');
return Q.nfcall(rimraf, svnFolder);
};
SvnResolver.prototype._savePkgMeta = function (meta) {
var version;
@@ -338,7 +324,7 @@ SvnResolver.tags = function (source) {
return Q.resolve(value);
}
value = cmd('svn', ['list', source + '/tags', '--verbose'])
value = cmd('svn', ['list', source + '/tags', '--verbose', '--non-interactive'])
.spread(function (stout) {
var tags = SvnResolver.parseSubversionListOutput(stout.toString());
@@ -363,7 +349,7 @@ SvnResolver.branches = function (source) {
return Q.resolve(value);
}
value = cmd('svn', ['list', source + '/branches', '--verbose'])
value = cmd('svn', ['list', source + '/branches', '--verbose', '--non-interactive'])
.spread(function (stout) {
var branches = SvnResolver.parseSubversionListOutput(stout.toString());

View File

@@ -1,6 +1,6 @@
var util = require('util');
var path = require('path');
var fs = require('graceful-fs');
var fs = require('../../util/fs');
var url = require('url');
var request = require('request');
var Q = require('q');
@@ -39,7 +39,7 @@ UrlResolver.isTargetable = function () {
return false;
};
UrlResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
UrlResolver.prototype._hasNew = function (pkgMeta) {
var oldCacheHeaders = pkgMeta._cacheHeaders || {};
var reqHeaders = {};
@@ -55,7 +55,7 @@ UrlResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
// Make an HEAD request to the source
return Q.nfcall(request.head, this._source, {
proxy: this._remote.protocol === 'https:' ? this._config.httpsProxy : this._config.proxy,
ca: this._config.ca.default,
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
headers: reqHeaders
@@ -104,8 +104,18 @@ UrlResolver.prototype._resolve = function () {
// -----------------
UrlResolver.prototype._parseSourceURL = function (_url) {
return url.parse(path.basename(_url)).pathname;
};
UrlResolver.prototype._download = function () {
var fileName = url.parse(path.basename(this._source)).pathname;
var fileName = this._parseSourceURL(this._source);
if (!fileName) {
this._source = this._source.replace(/\/(?=\?|#)/, '');
fileName = this._parseSourceURL(this._source);
}
var file = path.join(this._tempDir, fileName);
var reqHeaders = {};
var that = this;
@@ -121,7 +131,7 @@ UrlResolver.prototype._download = function () {
// Download the file
return download(this._source, file, {
proxy: this._remote.protocol === 'https:' ? this._config.httpsProxy : this._config.proxy,
ca: this._config.ca.default,
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
headers: reqHeaders

View File

@@ -0,0 +1,322 @@
var Q = require('q');
var path = require('path');
var fs = require('../../util/fs');
var object = require('mout/object');
var semver = require('../../util/semver');
var createError = require('../../util/createError');
var readJson = require('../../util/readJson');
var removeIgnores = require('../../util/removeIgnores');
function pluginResolverFactory(resolverFactory, bower) {
bower = bower || {};
if (typeof resolverFactory !== 'function') {
throw createError('Resolver has "' + typeof resolverFactory + '" type instead of "function" type.', 'ERESOLERAPI');
}
var resolver = resolverFactory(bower);
function maxSatisfyingVersion(versions, target) {
var versionsArr, index;
versionsArr = versions.map(function (obj) { return obj.version; });
// Find a satisfying version, enabling strict match so that pre-releases
// have lower priority over normal ones when target is *
index = semver.maxSatisfyingIndex(versionsArr, target, true);
if (index !== -1) {
return versions[index];
}
}
function PluginResolver(decEndpoint) {
this._decEndpoint = decEndpoint;
}
// @private
PluginResolver.prototype.getEndpoint = function () {
return object.merge(this._decEndpoint, {
name: this.getName(),
source: this.getSource(),
target: this.getTarget()
});
};
PluginResolver.prototype.getSource = function () {
return this._decEndpoint.source;
};
PluginResolver.prototype.getTarget = function () {
return this._decEndpoint.target || '*';
};
PluginResolver.prototype.getName = function() {
if (!this._decEndpoint.name && typeof resolver.getName === 'function') {
return resolver.getName.call(resolver, this.getSource());
} else if (!this._decEndpoint.name) {
return path.basename(this.getSource());
} else {
return this._decEndpoint.name;
}
};
PluginResolver.prototype.getPkgMeta = function () {
return this._pkgMeta;
};
// -----------------
// Plugin Resolver is always considered potentially cacheable
// The "resolve" method decides whether to use cached or fetch new version.
PluginResolver.prototype.isCacheable = function() {
return true;
};
// Not only it's always potentially cacheable, but also always potenially new.
// The "resolve" handles logic of re-downloading target if needed.
PluginResolver.prototype.hasNew = function (pkgMeta) {
if (this.hasNewPromise) {
return this.hasNewPromise;
}
this._pkgMeta = pkgMeta;
return this.hasNewPromise = this.resolve().then(function (result) {
return result !== undefined;
});
};
PluginResolver.prototype.resolve = function () {
if (this.resolvePromise) {
return this.resolvePromise;
}
var that = this;
return this.resolvePromise = Q.fcall(function() {
var target = that.getTarget();
// It means that we can accept ranges as targets
if(that.constructor.isTargetable()) {
that._release = target;
if (semver.validRange(target)) {
return Q.fcall(resolver.releases.bind(resolver), that.getSource())
.then(function (result) {
if (!result) {
throw createError('Resolver did not provide releases of package.');
}
var releases = this._releases = result;
var versions = releases.filter(function (target) {
return semver.clean(target.version);
});
var maxRelease = maxSatisfyingVersion(versions, target);
if (maxRelease) {
that._version = maxRelease.version;
that._release = that._decEndpoint.target = maxRelease.target;
} else {
throw createError('No version found that was able to satisfy ' + target, 'ENORESTARGET', {
details: !versions.length ?
'No versions found in ' + that.getSource() :
'Available versions: ' + versions.map(function (version) { return version.version; }).join(', ')
});
}
});
}
} else {
if (semver.validRange(target) && target !== '*') {
return Q.reject(createError('Resolver does not accept version ranges (' + target + ')'));
}
}
})
.then(function () {
// We pass old _resolution (if hasNew has been called before contents).
// So resolver can decide wheter use cached version of contents new one.
if (typeof resolver.fetch !== 'function') {
throw createError('Resolver does not implement the "fetch" method.');
}
var cached = {};
if (that._releases) {
cached.releases = that._releases;
}
if (that._pkgMeta) {
cached.endpoint = {
name: that._pkgMeta.name,
source: that._pkgMeta._source,
target: that._pkgMeta._target
};
cached.release = that._pkgMeta._release;
cached.version = that._pkgMeta.version;
cached.resolution = that._pkgMeta._resolution || {};
}
return Q.fcall(resolver.fetch.bind(resolver), that.getEndpoint(), cached);
})
.then(function (result) {
// Empty result means to re-use existing resolution
if (!result) {
return;
} else {
if (!result.tempPath) {
throw createError('Resolver did not provide path to extracted contents of package.');
}
that._tempDir = result.tempPath;
return that._readJson(that._tempDir).then(function (meta) {
return that._applyPkgMeta(meta, result)
.then(that._savePkgMeta.bind(that, meta, result))
.then(function () {
return that._tempDir;
});
});
}
});
};
PluginResolver.prototype._readJson = function (dir) {
var that = this;
return readJson(dir, {
assume: { name: that.getName() }
})
.spread(function (json, deprecated) {
if (deprecated) {
bower.logger.warn('deprecated', 'Package ' + that.getName() + ' is using the deprecated ' + deprecated);
}
return json;
});
};
PluginResolver.prototype._applyPkgMeta = function (meta, result) {
// Check if name defined in the json is different
// If so and if the name was "guessed", assume the json name
if (meta.name !== this._name) {
this._name = meta.name;
}
// Handle ignore property, deleting all files from the temporary directory
// If no ignores were specified, simply resolve
if (result.removeIgnores === false || !meta.ignore || !meta.ignore.length) {
return Q.resolve(meta);
}
// Otherwise remove them from the temp dir
return removeIgnores(this._tempDir, meta).then(function () {
return meta;
});
};
PluginResolver.prototype._savePkgMeta = function (meta, result) {
var that = this;
meta._source = that.getSource();
meta._target = that.getTarget();
if (result.resolution) {
meta._resolution = result.resolution;
}
if (that._release) {
meta._release = that._release;
}
if (that._version) {
meta.version = that._version;
} else {
delete meta.version;
}
// Stringify contents
var contents = JSON.stringify(meta, null, 2);
return Q.nfcall(fs.writeFile, path.join(this._tempDir, '.bower.json'), contents)
.then(function () {
return that._pkgMeta = meta;
});
};
// It is used only by "bower info". It returns all semver versions.
PluginResolver.versions = function (source) {
return Q.fcall(resolver.releases.bind(resolver), source).then(function (result) {
if (!result) {
throw createError('Resolver did not provide releases of package.');
}
var releases = this._releases = result;
var versions = releases.map(function (version) {
return semver.clean(version.version);
});
versions = versions.filter(function (version) {
return version;
});
versions.sort(function (a, b) {
return semver.rcompare(a, b);
});
return versions;
});
};
PluginResolver.isTargetable = function() {
// If resolver doesn't define versions function, it's not targetable..
return typeof resolver.releases === 'function';
};
PluginResolver.clearRuntimeCache = function () {
resolver = resolverFactory(bower);
};
PluginResolver.match = function (source) {
if (typeof resolver.match !== 'function') {
throw createError('Resolver is missing "match" method.', 'ERESOLVERAPI');
}
var match = resolver.match.bind(resolver);
return Q.fcall(match, source).then(function (result) {
if (typeof result !== 'boolean') {
throw createError('Resolver\'s "match" method should return a boolean', 'ERESOLVERAPI');
}
return result;
});
};
PluginResolver.locate = function (source) {
if (typeof resolver.locate !== 'function') {
return source;
}
return Q.fcall(resolver.locate.bind(resolver), source).then(function (result) {
if (typeof result !== 'string') {
throw createError('Resolver\'s "locate" method should return a string', 'ERESOLVERAPI');
}
return result;
});
};
return PluginResolver;
}
module.exports = pluginResolverFactory;

View File

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

View File

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

@@ -1,10 +1,8 @@
var cardinal = require('cardinal');
var chalk = require('chalk');
var path = require('path');
var mout = require('mout');
var archy = require('archy');
var Q = require('q');
var inquirer = require('inquirer');
var stringifyObject = require('stringify-object');
var os = require('os');
var pkg = require(path.join(__dirname, '../..', 'package.json'));
@@ -25,13 +23,23 @@ function StandardRenderer(command, config) {
};
this._command = command;
this._config = config;
this._config = config || {};
if (this.constructor._wideCommands.indexOf(command) === -1) {
this._compact = true;
} 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) {
@@ -71,7 +79,7 @@ StandardRenderer.prototype.error = function (err) {
/*jshint camelcase:true*/
this._write(process.stderr, str);
console.trace();
this._write(process.stderr, new Error().stack);
// Print bower version, node version and system info.
this._write(process.stderr, chalk.yellow('\nSystem info:\n'));
@@ -106,16 +114,12 @@ StandardRenderer.prototype.prompt = function (prompts) {
// Prompt
deferred = Q.defer();
var inquirer = require('inquirer');
inquirer.prompt(prompts, deferred.resolve);
return deferred.promise;
};
StandardRenderer.prototype.updateNotice = function (data) {
var str = template.render('std/update-notice.std', data);
this._write(process.stderr, str);
};
// -------------------------
StandardRenderer.prototype._help = function (data) {
@@ -171,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) {
@@ -394,6 +402,8 @@ StandardRenderer.prototype._write = function (stream, str) {
};
StandardRenderer.prototype._highlightJson = function (json) {
var cardinal = require('cardinal');
return cardinal.highlight(stringifyObject(json, { indent: ' ' }), {
theme: {
String: {

View File

@@ -1,43 +1,109 @@
var Q = require('q');
var Insight = require('insight');
var mout = require('mout');
var config = require('../config');
var pkg = require('../../package.json');
var analytics = module.exports;
var insight;
var enableAnalytics = false;
// 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');
if (process.env.NODE_ENV === 'test') {
insight = new Insight({
trackingCode: 'UA-00000000-0',
pkg: {
name: 'bower-test',
version: '1.0.0'
}
});
insight.config.clear();
} else {
insight = new Insight({
trackingCode: 'UA-43531210-1',
pkg: require('../../package.json')
});
}
}
}
// Initializes the application-wide insight singleton and asks for the
// permission on the CLI during the first run.
analytics.setup = function setup() {
//
// This method is called only from bin/bower. Programmatic API skips it.
analytics.setup = function setup (config) {
var deferred = Q.defer();
insight = new Insight({
trackingCode: 'UA-43531210-1',
packageName: pkg.name,
packageVersion: pkg.version
});
// Display the ask prompt only if it hasn't been answered before
// and the current session is looking to configure the analytics.
if (insight.optOut === undefined && config.analytics) {
insight.askPermission(null, deferred.resolve);
// 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 {
// 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;
@@ -96,7 +99,7 @@ function executeCmd(command, args, options) {
fullCommand += args.length ? ' ' + args.join(' ') : '';
// Build the error instance
error = createError('Failed to execute "' + fullCommand + '", exit code of #' + code, 'ECMDERR', {
error = createError('Failed to execute "' + fullCommand + '", exit code of #' + code + '\n' + stderr, 'ECMDERR', {
details: stderr,
exitCode: code
});

View File

@@ -1,6 +1,6 @@
var fstream = require('fstream');
var fstreamIgnore = require('fstream-ignore');
var fs = require('graceful-fs');
var fs = require('./fs');
var Q = require('q');
function copy(reader, writer) {

View File

@@ -1,4 +1,4 @@
var fs = require('graceful-fs');
var fs = require('./fs');
var path = require('path');
var Q = require('q');
var mkdirp = require('mkdirp');

View File

@@ -3,8 +3,9 @@ var request = require('request');
var Q = require('q');
var mout = require('mout');
var retry = require('retry');
var fs = require('graceful-fs');
var createError = require('./createError');
var createWriteStream = require('fs-write-stream-atomic');
var userAgent = require('./userAgent');
var errorCodes = [
'EADDRINFO',
@@ -15,7 +16,6 @@ var errorCodes = [
function download(url, file, options) {
var operation;
var response;
var deferred = Q.defer();
var progressDelay = 8000;
@@ -29,41 +29,17 @@ function download(url, file, options) {
// Retry on network errors
operation = retry.operation(options);
operation.attempt(function () {
var req;
var writeStream;
var contentLength;
var bytesDownloaded = 0;
req = progress(request(url, options), {
delay: progressDelay
})
.on('response', function (res) {
var status = res.statusCode;
if (status < 200 || status >= 300) {
return deferred.reject(createError('Status code of ' + status, 'EHTTP'));
Q.fcall(fetch, url, file, {
progressDelay: progressDelay,
headers: {
'User-Agent': userAgent
}
response = res;
contentLength = Number(res.headers['content-length']);
})
.on('data', function (data) {
bytesDownloaded += data.length;
})
.on('progress', function (state) {
deferred.notify(state);
})
.on('end', function () {
// Check if the whole file was downloaded
// In some unstable connections the ACK/FIN packet might be sent in the
// middle of the download
// See: https://github.com/joyent/node/issues/6143
if (contentLength && bytesDownloaded < contentLength) {
req.emit('error', createError('Transfer closed with ' + (contentLength - bytesDownloaded) + ' bytes remaining to read', 'EINCOMPLETE'));
}
})
.on('error', function (error) {
}).then(function(response) {
deferred.resolve(response);
}).fail(function (error) {
// Save timeout before retrying to report
var timeout = operation._timeouts[0];
// Reject if error is not a network error
@@ -74,36 +50,79 @@ function download(url, file, options) {
// Next attempt will start reporting download progress immediately
progressDelay = 0;
// Check if there are more retries
if (operation.retry(error)) {
// Ensure that there are no more events from this request
req.removeAllListeners();
req.on('error', function () {});
// Ensure that there are no more events from the write stream
writeStream.removeAllListeners();
writeStream.on('error', function () {});
deferred.notify({
retry: true,
delay: timeout,
error: error
});
return deferred.notify({
retry: true,
delay: timeout,
error: error
});
}
// No more retries, reject!
deferred.reject(error);
});
// Pipe read stream to write stream
writeStream = req
.pipe(fs.createWriteStream(file))
.on('error', deferred.reject)
.on('close', function () {
deferred.resolve(response);
operation.retry(error);
});
});
return deferred.promise;
}
function fetch(url, file, options) {
var response;
var deferred = Q.defer();
// Retry on network errors
var req;
var writeStream;
var contentLength;
var bytesDownloaded = 0;
req = progress(request(url, options), {
delay: options.progressDelay
})
.on('response', function (res) {
var status = res.statusCode;
if (status < 200 || status >= 300) {
return deferred.reject(createError('Status code of ' + status, 'EHTTP'));
}
response = res;
contentLength = Number(res.headers['content-length']);
})
.on('data', function (data) {
bytesDownloaded += data.length;
})
.on('progress', function (state) {
deferred.notify(state);
})
.on('error', function (error) {
deferred.reject(error);
})
.on('end', function () {
// Check if the whole file was downloaded
// In some unstable connections the ACK/FIN packet might be sent in the
// middle of the download
// See: https://github.com/joyent/node/issues/6143
if (contentLength && bytesDownloaded < contentLength) {
req.emit('error', createError(
'Transfer closed with ' + (contentLength - bytesDownloaded) + ' bytes remaining to read',
'EINCOMPLETE'
));
}
});
writeStream = createWriteStream(file);
// Pipe read stream to write stream
writeStream.on('error', function (error) {
writeStream.destroy();
deferred.reject(error);
});
writeStream.on('finish', function () {
deferred.resolve(response);
});
req.pipe(writeStream);
return deferred.promise;
}
module.exports = download;

View File

@@ -1,12 +1,14 @@
var path = require('path');
var fs = require('graceful-fs');
var fs = require('./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');
var createError = require('./createError');
var createWriteStream = require('fs-write-stream-atomic');
var destroy = require('destroy');
// This forces the default chunk size to something small in an attempt
// to avoid issue #314
@@ -23,6 +25,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
@@ -48,15 +51,24 @@ function extractZip(archive, dst) {
function extractTar(archive, dst) {
var deferred = Q.defer();
fs.createReadStream(archive)
.on('error', deferred.reject)
.pipe(tar.Extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
var stream = fs.createReadStream(archive);
var reject = function (error) {
destroy(stream);
deferred.reject(error);
};
stream.on('error', reject)
.pipe(tar.extract(dst, {
ignore: isSymlink, // Filter symlink files
dmode: 0555, // Ensure dirs are readable
fmode: 0444 // Ensure files are readable
}))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
.on('error', reject)
.on('finish', function (result) {
destroy(stream);
deferred.resolve(dst);
});
return deferred.promise;
}
@@ -64,17 +76,26 @@ function extractTar(archive, dst) {
function extractTarGz(archive, dst) {
var deferred = Q.defer();
fs.createReadStream(archive)
.on('error', deferred.reject)
var stream = fs.createReadStream(archive);
var reject = function (error) {
destroy(stream);
deferred.reject(error);
};
stream.on('error', 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
.on('error', reject)
.pipe(tar.extract(dst, {
ignore: isSymlink, // Filter symlink files
dmode: 0555, // Ensure dirs are readable
fmode: 0444 // Ensure files are readable
}))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
.on('error', reject)
.on('finish', function (result) {
destroy(stream);
deferred.resolve(dst);
});
return deferred.promise;
}
@@ -82,17 +103,29 @@ function extractTarGz(archive, dst) {
function extractGz(archive, dst) {
var deferred = Q.defer();
fs.createReadStream(archive)
.on('error', deferred.reject)
var stream = fs.createReadStream(archive);
var reject = function (error) {
destroy(stream);
deferred.reject(error);
};
stream.on('error', reject)
.pipe(zlib.createGunzip())
.on('error', deferred.reject)
.pipe(fs.createWriteStream(dst))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
.on('error', reject)
.pipe(createWriteStream(dst))
.on('error', reject)
.on('finish', function (result) {
destroy(stream);
deferred.resolve(dst);
});
return deferred.promise;
}
function isSymlink(entry) {
return entry.type === 'SymbolicLink';
}
function filterSymlinks(entry) {
return entry.type !== 'SymbolicLink';
}

34
lib/util/fs.js Normal file
View File

@@ -0,0 +1,34 @@
var fs = require('graceful-fs');
var readdir = fs.readdir.bind(fs);
var readdirSync = fs.readdirSync.bind(fs);
module.exports = fs;
module.exports.readdir = function (dir, callback) {
fs.stat(dir, function (err, stats) {
if (err) return callback(err);
if (stats.isDirectory()) {
return readdir(dir, callback);
} else {
var error = new Error('ENOTDIR, not a directory \'' + dir + '\'');
error.code = 'ENOTDIR';
error.path = dir;
error.errono = -20;
return callback(error);
}
});
};
module.exports.readdirSync = function (dir) {
var stats = fs.statSync(dir);
if (stats.isDirectory()) {
return readdirSync(dir);
} else {
var error = new Error();
error.code = 'ENOTDIR';
throw error;
}
};

View File

@@ -1,7 +0,0 @@
var crypto = require('crypto');
function md5(contents) {
return crypto.createHash('md5').update(contents).digest('hex');
}
module.exports = md5;

View File

@@ -1,22 +1,29 @@
var path = require('path');
var rimraf = require('rimraf');
var rimraf = require('../util/rimraf');
var fstreamIgnore = require('fstream-ignore');
var mout = require('mout');
var Q = require('q');
function removeIgnores(dir, ignore) {
function removeIgnores(dir, meta) {
var reader;
var applyIgnores;
var deferred = Q.defer();
var ignored = [];
var nonIgnored = [];
var nonIgnored = ['bower.json'];
// Don't ignore main files
nonIgnored = nonIgnored.concat(meta.main || []);
nonIgnored = nonIgnored.map(function (file) {
return path.join(dir, file);
});
reader = fstreamIgnore({
path: dir,
type: 'Directory'
});
reader.addIgnoreRules(ignore);
reader.addIgnoreRules(meta.ignore || []);
// Monkey patch applyIgnores such that we get hold of all ignored files
applyIgnores = reader.applyIgnores;

46
lib/util/rimraf.js Normal file
View File

@@ -0,0 +1,46 @@
var rimraf = require('rimraf');
var chmodr = require('chmodr');
var fs = require('./fs');
module.exports = function (dir, callback) {
var checkAndRetry = function (e) {
fs.lstat(dir, function (err, stats) {
if (err) {
if (err.code === 'ENOENT') return callback();
return callback(e);
}
chmodr(dir, 0777, function (err) {
if (err) return callback(e);
rimraf(dir, callback);
});
});
};
if (process.platform === 'win32') {
checkAndRetry();
} else {
rimraf(dir, checkAndRetry);
}
};
module.exports.sync = function (dir) {
var checkAndRetry = function () {
try {
fs.lstatSync(dir);
chmodr.sync(dir, 0777);
return rimraf.sync(dir);
} catch (e) {
if (e.code === 'ENOENT') return;
throw e;
}
};
try {
return rimraf.sync(dir);
} catch (e) {
return checkAndRetry();
} finally {
return checkAndRetry();
}
};

View File

@@ -2,7 +2,6 @@
'use strict';
var isRoot = require('is-root');
var createError = require('./createError');
var cli = require('./cli');
var renderer;
@@ -23,6 +22,7 @@ https://gist.github.com/isaacs/579814\n\n\
You can however run a command with sudo using --allow-root option';
if (isRoot()) {
var cli = require('./cli');
renderer = cli.getRenderer('', false, config);
renderer.error(createError('Cannot be run with sudo', 'ESUDO', { details : errorMsg }));
process.exit(1);

View File

@@ -1,5 +1,5 @@
var path = require('path');
var fs = require('graceful-fs');
var fs = require('./fs');
var Handlebars = require('handlebars');
var mout = require('mout');
var helpers = require('../../templates/helpers');

3
lib/util/userAgent.js Normal file
View File

@@ -0,0 +1,3 @@
var pkg = require('../../package.json');
module.exports = 'node/' + process.version + ' ' + process.platform + ' ' + process.arch + ' ' + ';Bower ' + pkg.version;

View File

@@ -1,5 +1,5 @@
var Q = require('q');
var fs = require('graceful-fs');
var fs = require('./fs');
function validLink(file) {
// Ensures that a file is a symlink that points

1306
npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,84 +1,141 @@
{
"name": "bower",
"version": "1.3.2",
"description": "The browser package manager.",
"version": "1.6.5",
"description": "The browser package manager",
"author": "Twitter",
"licenses": [
{
"type": "MIT",
"url": "https://github.com/bower/bower/blob/master/LICENSE"
}
],
"repository": {
"type": "git",
"url": "git://github.com/bower/bower.git"
},
"license": "MIT",
"repository": "bower/bower",
"main": "lib",
"homepage": "http://bower.io",
"engines": {
"node": ">=0.10.0"
},
"keywords": [
"bower"
],
"dependencies": {
"abbrev": "~1.0.4",
"archy": "0.0.2",
"bower-config": "~0.5.0",
"bower-endpoint-parser": "~0.2.0",
"bower-json": "~0.4.0",
"bower-logger": "~0.2.2",
"bower-registry-client": "~0.1.4",
"cardinal": "~0.4.0",
"chalk": "~0.4.0",
"chmodr": "~0.1.0",
"decompress-zip": "~0.0.3",
"fstream": "~0.1.22",
"fstream-ignore": "~0.0.6",
"glob": "~3.2.9",
"graceful-fs": "~2.0.0",
"handlebars": "~1.3.0",
"inquirer": "~0.4.0",
"junk": "~0.2.2",
"mkdirp": "~0.3.5",
"mout": "~0.9.1",
"nopt": "~2.1.2",
"lru-cache": "~2.5.0",
"open": "~0.0.3",
"osenv": "~0.0.3",
"promptly": "~0.2.0",
"q": "~1.0.1",
"request": "~2.33.0",
"request-progress": "~0.3.0",
"retry": "~0.6.0",
"rimraf": "~2.2.0",
"semver": "~2.2.1",
"stringify-object": "~0.2.0",
"tar": "~0.1.17",
"tmp": "~0.0.20",
"update-notifier": "~0.1.3",
"which": "~1.0.5",
"p-throttler": "~0.0.1",
"insight": "~0.3.0",
"is-root": "~0.1.0",
"shell-quote": "~1.4.1",
"lockfile": "~0.4.2"
"abbrev": "^1.0.5",
"archy": "1.0.0",
"bower-config": "^1.2.2",
"bower-endpoint-parser": "^0.2.2",
"bower-json": "^0.4.0",
"bower-logger": "^0.2.2",
"bower-registry-client": "^1.0.0",
"cardinal": "0.4.4",
"chalk": "^1.0.0",
"chmodr": "^1.0.2",
"configstore": "^0.3.2",
"decompress-zip": "^0.1.0",
"destroy": "^1.0.3",
"fs-write-stream-atomic": "^1.0.4",
"fstream": "^1.0.3",
"fstream-ignore": "^1.0.2",
"github": "^0.2.3",
"glob": "^4.3.2",
"graceful-fs": "^3.0.5",
"handlebars": "^2.0.0",
"inquirer": "0.10.0",
"insight": "^0.7.0",
"is-root": "^1.0.0",
"junk": "^1.0.0",
"lockfile": "^1.0.0",
"lru-cache": "^2.5.0",
"md5-hex": "^1.0.2",
"mkdirp": "0.5.0",
"mout": "^0.11.0",
"nopt": "^3.0.1",
"opn": "^1.0.1",
"p-throttler": "0.1.1",
"promptly": "0.2.0",
"q": "^1.1.2",
"request": "2.53.0",
"request-progress": "0.3.1",
"retry": "0.6.1",
"rimraf": "^2.2.8",
"semver": "^2.3.0",
"shell-quote": "^1.4.2",
"stringify-object": "^1.0.0",
"tar-fs": "^1.4.1",
"tmp": "0.0.24",
"update-notifier": "^0.3.0",
"user-home": "^1.1.0",
"which": "^1.0.8"
},
"devDependencies": {
"expect.js": "~0.2.0",
"grunt": "~0.4.1",
"grunt-simple-mocha": "~0.4.0",
"grunt-contrib-watch": "~0.5.3",
"grunt-contrib-jshint": "~0.8.0",
"grunt-exec": "~0.4.2",
"mocha": "~1.18",
"nock": "~0.27.2",
"istanbul": "~0.2.4",
"proxyquire": "~0.5.3",
"load-grunt-tasks": "~0.3.0"
"chai": "^1.10.0",
"coveralls": "^2.11.2",
"expect.js": "^0.3.1",
"grunt": "^0.4.5",
"grunt-cli": "^0.1.13",
"grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-exec": "^0.4.6",
"grunt-simple-mocha": "^0.4.0",
"istanbul": "^0.3.5",
"load-grunt-tasks": "^2.0.0",
"mocha": "^2.1.0",
"multiline": "^1.0.2",
"nock": "^2.13.0",
"node-uuid": "^1.4.2",
"npm-shrinkwrap": "^5.4.1",
"proxyquire": "^1.3.0",
"spawn-sync": "^1.0.5"
},
"bundledDependencies": [
"abbrev",
"archy",
"bower-config",
"bower-endpoint-parser",
"bower-json",
"bower-logger",
"bower-registry-client",
"cardinal",
"chalk",
"chmodr",
"configstore",
"decompress-zip",
"destroy",
"fs-write-stream-atomic",
"fstream",
"fstream-ignore",
"github",
"glob",
"graceful-fs",
"handlebars",
"inquirer",
"insight",
"is-root",
"junk",
"lockfile",
"lru-cache",
"md5-hex",
"mkdirp",
"mout",
"nopt",
"opn",
"p-throttler",
"promptly",
"q",
"request",
"request-progress",
"retry",
"rimraf",
"semver",
"shell-quote",
"stringify-object",
"tar-fs",
"tmp",
"update-notifier",
"user-home",
"which"
],
"scripts": {
"test": "grunt test"
},
"bin": {
"bower": "bin/bower"
},
"preferGlobal": true
"bin": "bin/bower",
"files": [
"bin",
"lib",
"templates"
]
}

View File

@@ -1,5 +1,5 @@
{
"command": "cache list",
"command": "cache clean",
"description": "Cleans cached packages.",
"usage": [
"cache clean [<options>]",

View File

@@ -1,5 +1,5 @@
{
"command": "cache clean",
"command": "cache list",
"description": "Lists cached packages.",
"usage": [
"cache list [<options>]",

View File

@@ -30,6 +30,11 @@
"shorthand": "-D",
"flag": "--save-dev",
"description": "Save installed packages into the project's bower.json devDependencies"
},
{
"shorthand": "-E",
"flag": "--save-exact",
"description": "Configure installed packages with an exact version rather than semver"
}
]
}

View File

@@ -0,0 +1,19 @@
{
"command": "login",
"description": "Authenticate with GitHub and store credentials to be used later.",
"usage": [
"login [<options>]"
],
"options": [
{
"shorthand": "-h",
"flag": "--help",
"description": "Show this help message"
},
{
"shorthand": "-t",
"flag": "--token",
"description": "Pass an existing GitHub auth token rather than prompting for username and password."
}
]
}

View File

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

@@ -0,0 +1,14 @@
{
"command": "unregister",
"description": "Unregisters a package.",
"usage": [
"unregister <name> [<options>]"
],
"options": [
{
"shorthand": "-h",
"flag": "--help",
"description": "Show this help message"
}
]
}

View File

@@ -10,13 +10,15 @@
"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",
"login": "Authenticate with GitHub and store credentials",
"lookup": "Look up a package URL by name",
"prune": "Removes local extraneous packages",
"register": "Register a package",
"search": "Search for a package by name",
"update": "Update a local package",
"uninstall": "Remove a local package",
"unregister": "Remove a package from the registry",
"version": "Bump a package version"
},
"options": [
@@ -58,6 +60,15 @@
{
"flag": "--allow-root",
"description": "Allows running commands as root"
},
{
"shorthand": "-v",
"flag": "--version",
"description": "Output Bower version"
},
{
"flag": "--no-color",
"description": "Disable colors"
}
]
}

View File

@@ -1,9 +1,9 @@
{{#yellow}}Please note that,{{/yellow}}
{{#condense}}
{{#each picks}}
{{#if dependants}}{{#white}}{{dependants}}{{/white}}{{else}}none{{/if}} depends on {{#cyan}}{{endpoint.name}}#{{endpoint.target}}{{/cyan}}{{#if pkgMeta._release}} which resolved to {{#white}}{{endpoint.name}}#{{pkgMeta._release}}{{/white}}{{/if}}
{{#if dependants}}{{#green}}{{dependants}}{{/green}}{{else}}none{{/if}} depends on {{#cyan}}{{endpoint.name}}#{{endpoint.target}}{{/cyan}}{{#if pkgMeta._release}} which resolved to {{#green}}{{endpoint.name}}#{{pkgMeta._release}}{{/green}}{{/if}}
{{/each}}
{{/condense}}
Resort to using {{#cyan}}{{suitable.endpoint.name}}#{{resolution}}{{/cyan}} which resolved to {{#white}}{{suitable.endpoint.name}}#{{suitable.pkgMeta._release}}{{/white}}
Resort to using {{#cyan}}{{suitable.endpoint.name}}#{{resolution}}{{/cyan}} which resolved to {{#green}}{{suitable.endpoint.name}}#{{suitable.pkgMeta._release}}{{/green}}
Code incompatibilities may occur.

View File

@@ -1,9 +1,11 @@
{{#yellow}}Unable to find a suitable version for {{name}}, please choose one:{{/yellow}}
{{#condense}}
{{#each picks}}
{{#magenta}}{{sum @index 1}}){{/magenta}} {{#cyan}}{{endpoint.name}}#{{endpoint.target}}{{/cyan}}{{#if pkgMeta._release}} which resolved to {{#white}}{{pkgMeta._release}}{{/white}}{{/if}}{{#if dependants}} and is required by {{#white}}{{dependants}}{{/white}} {{/if}}
{{#magenta}}{{sum @index 1}}){{/magenta}} {{#cyan}}{{endpoint.name}}#{{endpoint.target}}{{/cyan}}{{#if pkgMeta._release}} which resolved to {{#green}}{{pkgMeta._release}}{{/green}}{{/if}}{{#if dependants}} and is required by {{#green}}{{dependants}}{{/green}}{{/if}}
{{/each}}
{{/condense}}
{{#unless saveResolutions}}
Prefix the choice with ! to persist it to bower.json
{{/unless}}
{{/unless}}

View File

@@ -14,3 +14,4 @@ Commands:
{{#rpad length="23"}}{{@key}}{{/rpad}} {{.}}
{{/each}}
{{/condense}}

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

@@ -1,51 +1,51 @@
[
{
"canonicalDir": "/test/assets/temp-resolve-cache/3668e6529b32a6d3e8931a68474e909d/0.2.0",
"canonicalDir": "/test/tmp/temp-resolve-cache/3668e6529b32a6d3e8931a68474e909d/0.2.0",
"pkgMeta": {
"name": "abc",
"version": "0.2.0"
}
},
{
"canonicalDir": "/test/assets/temp-resolve-cache/77008abea14f06f199c2f481362b48d9/0.0.1",
"canonicalDir": "/test/tmp/temp-resolve-cache/77008abea14f06f199c2f481362b48d9/0.0.1",
"pkgMeta": {
"name": "foo",
"version": "0.0.1"
}
},
{
"canonicalDir": "/test/assets/temp-resolve-cache/77008abea14f06f199c2f481362b48d9/0.1.0",
"canonicalDir": "/test/tmp/temp-resolve-cache/77008abea14f06f199c2f481362b48d9/0.1.0",
"pkgMeta": {
"name": "foo",
"version": "0.1.0"
}
},
{
"canonicalDir": "/test/assets/temp-resolve-cache/3668e6529b32a6d3e8931a68474e909d/0.2.1",
"canonicalDir": "/test/tmp/temp-resolve-cache/3668e6529b32a6d3e8931a68474e909d/0.2.1",
"pkgMeta": {
"name": "foo",
"version": "0.2.1"
}
},
{
"canonicalDir": "/test/assets/temp-resolve-cache/77008abea14f06f199c2f481362b48d9/aa",
"canonicalDir": "/test/tmp/temp-resolve-cache/77008abea14f06f199c2f481362b48d9/aa",
"pkgMeta": {
"name": "foo",
"_target": "aa"
}
},
{
"canonicalDir": "/test/assets/temp-resolve-cache/77008abea14f06f199c2f481362b48d9/bar",
"canonicalDir": "/test/tmp/temp-resolve-cache/77008abea14f06f199c2f481362b48d9/bar",
"pkgMeta": {
"name": "foo",
"_target": "bar"
}
},
{
"canonicalDir": "/test/assets/temp-resolve-cache/77008abea14f06f199c2f481362b48d9/foo",
"canonicalDir": "/test/tmp/temp-resolve-cache/77008abea14f06f199c2f481362b48d9/foo",
"pkgMeta": {
"name": "foo",
"_target": "foo"
}
}
]
]

View File

@@ -1,10 +1,10 @@
var fs = require('graceful-fs');
var fs = require('../../../lib/util/fs');
var path = require('path');
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

@@ -1,10 +1,10 @@
var fs = require('graceful-fs');
var fs = require('../../../lib/util/fs');
var path = require('path');
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

31
test/commands/bower.js Normal file
View File

@@ -0,0 +1,31 @@
var expect = require('expect.js');
var helpers = require('../helpers');
describe('bower', function () {
var oldStdout;
var text;
before(function() {
oldStdout = process.stdout.write;
text = '';
process.stdout.write = function(args) {
text += args;
};
});
it('runs bower installation', function (done) {
helpers.require('bin/bower');
setTimeout(function() {
done();
}, 250);
});
after(function() {
process.stdout.write = oldStdout;
expect(text).to.contain('Usage:');
expect(text).to.contain('Commands:');
});
});

89
test/commands/cache/clean.js vendored Normal file
View File

@@ -0,0 +1,89 @@
var expect = require('expect.js');
var md5 = require('md5-hex');
var helpers = require('../../helpers');
var cacheClean = helpers.command('cache/clean');
var object = require('mout/object');
describe('bower cache clean', function () {
// Because directory names are required to be md5 of _source
var cacheFilesFactory = function (spec) {
var files = {};
object.map(spec, function(bowerJson) {
bowerJson._source = bowerJson.name + '/' + bowerJson.version;
var path = md5(bowerJson._source) + '/' + bowerJson.version + '/.bower.json';
files[path] = bowerJson;
});
return files;
};
var cacheFiles = cacheFilesFactory([
{
name: 'angular',
version: '1.3.8'
},
{
name: 'angular',
version: '1.3.9'
},
{
name: 'jquery',
version: '1.0.0'
}
]);
var cacheDir = new helpers.TempDir(cacheFiles);
it('correctly reads arguments', function() {
expect(cacheClean.readOptions(['jquery', 'angular']))
.to.eql([['jquery', 'angular'], {}]);
});
it('removes all cache', function () {
cacheDir.prepare();
return helpers.run(cacheClean, [undefined, {}, {
storage: {
packages: cacheDir.path
}
}]).spread(function(result) {
object.map(cacheFiles, function (_, cacheFile) {
expect(cacheDir.exists(cacheFile)).to.be(false);
});
});
});
it('removes single package', function () {
cacheDir.prepare();
return helpers.run(cacheClean, [['angular'], {}, {
storage: {
packages: cacheDir.path
}
}]).spread(function(result) {
var paths = Object.keys(cacheFiles);
expect(cacheDir.exists(paths[0])).to.be(false);
expect(cacheDir.exists(paths[1])).to.be(false);
expect(cacheDir.exists(paths[2])).to.be(true);
});
});
it('removes single package package version', function () {
cacheDir.prepare();
return helpers.run(cacheClean, [['angular#1.3.8'], {}, {
storage: {
packages: cacheDir.path
}
}]).spread(function(result) {
var paths = Object.keys(cacheFiles);
expect(cacheDir.exists(paths[0])).to.be(false);
expect(cacheDir.exists(paths[1])).to.be(true);
expect(cacheDir.exists(paths[2])).to.be(true);
});
});
});

60
test/commands/cache/list.js vendored Normal file
View File

@@ -0,0 +1,60 @@
var expect = require('expect.js');
var helpers = require('../../helpers');
var cacheList = helpers.command('cache/list');
describe('bower cache list', function () {
var cacheDir = new helpers.TempDir({
'87323d6d4e48be291a9616a033d4cc6c/1.3.8/.bower.json': {
name: 'angular',
version: '1.3.8'
},
'87323d6d4e48be291a9616a033d4cc6c/1.3.9/.bower.json': {
name: 'angular',
version: '1.3.9'
},
'9eaed103d6a7e78d91f673cfad796850/1.0.0/.bower.json': {
name: 'jquery',
version: '1.0.0'
}
});
it('correctly reads arguments', function() {
expect(cacheList.readOptions(['jquery', 'angular']))
.to.eql([['jquery', 'angular'], {}]);
});
it('lists packages from cache', function () {
cacheDir.prepare();
return helpers.run(cacheList, [undefined, {}, {
storage: {
packages: cacheDir.path
}
}]).spread(function(result) {
expect(result[0].canonicalDir)
.to.be(cacheDir.getPath('87323d6d4e48be291a9616a033d4cc6c/1.3.8'));
expect(result[0].pkgMeta.version).to.be('1.3.8');
expect(result[1].pkgMeta.version).to.be('1.3.9');
expect(result[2].pkgMeta.version).to.be('1.0.0');
});
});
it('lists selected package names', function () {
cacheDir.prepare();
return helpers.run(cacheList, [['angular'], {}, {
storage: {
packages: cacheDir.path
}
}]).spread(function(result) {
expect(result[0].canonicalDir)
.to.be(cacheDir.getPath('87323d6d4e48be291a9616a033d4cc6c/1.3.8'));
expect(result[0].pkgMeta.version).to.be('1.3.8');
expect(result[1].pkgMeta.version).to.be('1.3.9');
});
});
});

43
test/commands/help.js Normal file
View File

@@ -0,0 +1,43 @@
var expect = require('expect.js');
var helpers = require('../helpers');
var help = helpers.command('help');
describe('bower help', function () {
it('correctly reads arguments', function() {
expect(help.readOptions(['foo'])).to.eql(['foo']);
});
it('shows general help', function () {
return helpers.run(help).spread(function(result) {
expect(result.usage[0]).to.be.a('string');
expect(result.commands).to.be.a('object');
expect(result.options).to.be.a('object');
});
});
var commands = [
'home', 'info', 'init', 'install',
'link', 'list', 'lookup', 'prune', 'register',
'search', 'update', 'uninstall', 'version',
'cache list', 'cache clean'
];
commands.forEach(function(command) {
it('shows help for ' + command + ' command', function() {
return helpers.run(help, [command]).spread(function(result) {
expect(result.command).to.be(command);
expect(result.description).to.be.a('string');
expect(result.usage[0]).to.be.a('string');
});
});
});
it('displays error for non-existing command', function() {
return helpers.run(help, ['fuu']).fail(function(e) {
expect(e.message).to.be('Unknown command: fuu');
expect(e.command).to.be('fuu');
expect(e.code).to.be('EUNKNOWNCMD');
});
});
});

59
test/commands/home.js Normal file
View File

@@ -0,0 +1,59 @@
var Q = require('q');
var expect = require('expect.js');
var helpers = require('../helpers');
var home = helpers.command('home');
describe('bower home', function () {
it('correctly reads arguments', function() {
expect(home.readOptions(['foo'])).to.eql(['foo']);
});
var package = new helpers.TempDir({
'bower.json': {
name: 'package',
homepage: 'http://bower.io'
}
});
var wrongPackage = new helpers.TempDir({
'bower.json': {
name: 'package'
}
});
it('opens repository home page in web browser', function () {
package.prepare();
return Q.Promise(function(resolve) {
var home = helpers.command('home', { opn: resolve });
helpers.run(home, [package.path]);
}).then(function(url) {
expect(url).to.be('http://bower.io');
});
});
it('opens home page of current repository', function () {
package.prepare();
return Q.Promise(function(resolve) {
var home = helpers.command('home', { opn: resolve });
helpers.run(home, [undefined, { cwd: package.path }]);
}).then(function(url) {
expect(url).to.be('http://bower.io');
});
});
it('errors if no homepage is set', function () {
wrongPackage.prepare();
return Q.Promise(function(resolve) {
var home = helpers.command('home', { opn: resolve });
helpers.run(home, [wrongPackage.path]).fail(resolve);
}).then(function(reason) {
expect(reason.message).to.be('No homepage set for package');
expect(reason.code).to.be('ENOHOME');
});
});
});

21
test/commands/index.js Normal file
View File

@@ -0,0 +1,21 @@
describe('integration tests', function () {
require('./cache/list');
require('./cache/clean');
require('./help');
require('./home');
require('./info');
require('./init');
require('./install');
require('./list');
require('./link');
require('./lookup');
require('./prune');
require('./register');
require('./search');
require('./uninstall');
require('./update');
require('./version');
// run last because it changes defaults
require('./bower');
});

52
test/commands/info.js Normal file
View File

@@ -0,0 +1,52 @@
var expect = require('expect.js');
var helpers = require('../helpers');
var info = helpers.command('info');
describe('bower info', function () {
it('correctly reads arguments', function() {
expect(info.readOptions(['pkg', 'property']))
.to.eql(['pkg', 'property']);
});
var meta = {
name: 'package',
version: '0.1.2',
homepage: 'http://bower.io',
description: 'Hello world!'
};
var meta2 = {
name: 'package',
version: '0.1.3',
homepage: 'http://bower.io',
description: 'Hello world! Hello!'
};
var package = new helpers.TempDir({
'0.1.2': { 'bower.json': meta },
'0.1.3': { 'bower.json': meta2 }
});
it('just returns if not package is specified', function () {
return helpers.run(info).spread(function(results) {
expect(results).to.be(undefined);
});
});
it('shows info about given package', function () {
package.prepareGit({});
return helpers.run(info, [package.path]).spread(function(results) {
expect(results).to.eql({
'latest': meta2,
'name': package.path,
'versions': [
'0.1.3',
'0.1.2'
]
});
});
});
});

149
test/commands/init.js Normal file
View File

@@ -0,0 +1,149 @@
var expect = require('expect.js');
var helpers = require('../helpers');
var init = helpers.command('init');
describe('bower init', function () {
var package = new helpers.TempDir();
it('correctly reads arguments', function() {
expect(init.readOptions([]))
.to.eql([]);
});
it('generates bower.json file', function () {
package.prepare();
var logger = init({
cwd: package.path,
interactive: true
});
return helpers.expectEvent(logger, 'prompt')
.spread(function (prompt, answer) {
answer({
name: 'test-name',
description: 'test-description',
moduleType: 'test-moduleType',
keywords: 'test-keyword',
authors: 'test-author',
license: 'test-license',
homepage: 'test-homepage',
private: true
});
return helpers.expectEvent(logger, 'prompt');
})
.spread(function (prompt, answer) {
answer({ prompt: true });
return helpers.expectEvent(logger, 'end');
})
.then(function () {
expect(package.readJson('bower.json')).to.eql({
name: 'test-name',
homepage: 'test-homepage',
authors: [ 'test-author' ],
description: 'test-description',
moduleType: 'test-moduleType',
keywords: [ 'test-keyword' ],
license: 'test-license',
private: true
});
});
});
it('errors on non-interactive mode', function () {
package.prepare();
return helpers.run(init, { cwd: package.path }).then(
function () { throw 'should fail'; },
function (reason) {
expect(reason.message).to.be('Register requires an interactive shell');
expect(reason.code).to.be('ENOINT');
}
);
});
it('warns about existing bower.json', function () {
package.prepare({
'bower.json': {
name: 'foobar'
}
});
var logger = init({ cwd: package.path, interactive: true });
return helpers.expectEvent(logger, 'log').spread(function(event) {
expect(event.level).to.be('warn');
expect(event.message).to.be(
'The existing bower.json file will be used and filled in'
);
});
});
it('gets defaults from package.json', function () {
package.prepare({
'package.json': {
'name': 'name from npm',
'description': 'description from npm',
'main': 'index.js',
'keywords': [
'foo',
'bar'
],
'author': 'JD Isaacks',
'license': 'ISC'
}
});
var logger = init({
cwd: package.path,
interactive: true
});
return helpers.expectEvent(logger, 'prompt')
.spread(function (prompt, answer) {
// Get defaults from prompt
var defaults = prompt.reduce(function(memo, obj){
memo[obj.name] = obj['default'];
return memo;
}, {});
// Answer with defaults
answer({
name: defaults.name,
description: defaults.description,
main: defaults.main,
moduleType: defaults.moduleType,
keywords: defaults.keywords,
authors: defaults.authors,
license: defaults.license,
homepage: 'test-homepage',
private: true
});
return helpers.expectEvent(logger, 'prompt');
})
.spread(function (prompt, answer) {
answer({ prompt: true });
return helpers.expectEvent(logger, 'end');
})
.then(function () {
expect(package.readJson('bower.json')).to.eql({
'name': 'name from npm',
'description': 'description from npm',
'main': 'index.js',
'keywords': [
'foo',
'bar'
],
'authors': ['JD Isaacks'],
'license': 'ISC',
'private': true,
'homepage': 'test-homepage'
});
});
});
});

387
test/commands/install.js Normal file
View File

@@ -0,0 +1,387 @@
var expect = require('expect.js');
var helpers = require('../helpers');
var nock = require('nock');
var fs = require('../../lib/util/fs');
describe('bower install', function () {
var tempDir = new helpers.TempDir();
var install = helpers.command('install', { cwd: tempDir.path });
it('correctly reads arguments', function() {
expect(install.readOptions(['jquery', 'angular', '-F', '-p', '-S', '-D', '-E']))
.to.eql([['jquery', 'angular'], {
forceLatest: true,
production: true,
save: true,
saveDev: true,
saveExact: true
}]);
});
it('correctly reads long arguments', function() {
expect(install.readOptions([
'jquery', 'angular',
'--force-latest', '--production', '--save', '--save-dev', '--save-exact'
])).to.eql([['jquery', 'angular'], {
forceLatest: true,
production: true,
save: true,
saveDev: true,
saveExact: true
}]);
});
var package = new helpers.TempDir({
'bower.json': {
name: 'package'
}
}).prepare();
var gitPackage = new helpers.TempDir();
it('writes to bower.json if --save flag is used', function () {
package.prepare();
tempDir.prepare({
'bower.json': {
name: 'test'
}
});
return helpers.run(install, [[package.path], { save: true }]).then(function() {
expect(tempDir.read('bower.json')).to.contain('dependencies');
});
});
it('writes an exact version number to dependencies in bower.json if --save --save-exact flags are used', function () {
package.prepare({
'bower.json': {
name: 'package',
version: '1.2.3'
}
});
tempDir.prepare({
'bower.json': {
name: 'test'
}
});
return helpers.run(install, [
[package.path],
{ saveExact: true, save: true }
]).then(function() {
expect(tempDir.readJson('bower.json').dependencies.package).to.equal(package.path + '#1.2.3');
});
});
it('writes an exact version number to devDependencies in bower.json if --save-dev --save-exact flags are used', function () {
package.prepare({
'bower.json': {
name: 'package',
version: '0.1.0'
}
});
tempDir.prepare({
'bower.json': {
name: 'test'
}
});
return helpers.run(install, [
[package.path],
{ saveExact: true, saveDev: true }
]).then(function() {
expect(tempDir.readJson('bower.json').devDependencies.package).to.equal(package.path + '#0.1.0');
});
});
it('reads .bowerrc from cwd', function () {
package.prepare({ foo: 'bar' });
tempDir.prepare({
'.bowerrc': { directory: 'assets' },
'bower.json': {
name: 'test',
dependencies: {
package: package.path
}
}
});
return helpers.run(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: 'node -e \'require("fs").writeFileSync("preinstall.txt", "%")\''
}
}
});
return helpers.run(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: 'node -e \'require("fs").writeFileSync("postinstall.txt", "%")\''
}
}
});
return helpers.run(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: 'node -e \'require("fs").writeFileSync("hooks.txt", "%")\'',
preinstall: 'node -e \'require("fs").writeFileSync("hooks.txt", "%")\''
}
}
});
return helpers.run(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: 'node -e \'var fs = require("fs"); fs.writeFileSync("hook.txt", fs.readFileSync("bower.json"));\''
}
}
});
return helpers.run(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: 'node -e \'process.stdout.write("foobar")\''
}
}
});
var lastAction = null;
helpers.run(install).logger.intercept(function (log) {
if (log.level === 'action') {
lastAction = log;
}
}).on('end', function () {
expect(lastAction.message).to.be('foobar');
next();
});
});
it('skips components not installed by bower', function () {
package.prepare({
'.git': {} //Make a dummy file instead of using slower gitPrepare()
});
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: package.path
}
}
});
return helpers.run(install).then(function() {
var packageFiles = fs.readdirSync(package.path);
//presence of .git file implies folder was not overwritten
expect(packageFiles).to.contain('.git');
});
});
it('works for git repositories', function () {
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'
}
});
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
}
});
return helpers.run(install).then(function() {
expect(tempDir.read('bower_components/package/version.txt')).to.contain('1.0.0');
});
});
it('does not install ignored dependencies', function() {
package.prepare();
var package2 = new helpers.TempDir({
'bower.json': {
name: 'package2',
}
}).prepare();
var package3 = new helpers.TempDir({
'bower.json': {
name: 'package3',
dependencies: {
package2: package2.path,
package: package.path,
}
}
}).prepare();
tempDir.prepare({
'bower.json': {
name: 'test_tw',
dependencies: {
package3: package3.path
}
},
'.bowerrc': {
ignoredDependencies: ['package']
}
});
return helpers.run(install).then(function() {
expect(tempDir.exists('bower_components/package')).to.be(false);
expect(tempDir.exists('bower_components/package2')).to.be(true);
});
});
it('does not install ignored dependencies if run multiple times', function() {
package.prepare();
var package2 = new helpers.TempDir({
'bower.json': {
name: 'package2',
}
}).prepare();
var package3 = new helpers.TempDir({
'bower.json': {
name: 'package3',
dependencies: {
package2: package2.path,
package: package.path,
}
}
}).prepare();
tempDir.prepare({
'bower.json': {
name: 'test_tw',
dependencies: {
package3: package3.path
}
},
'.bowerrc': {
ignoredDependencies: ['package']
}
});
return helpers.run(install).then(function() {
return helpers.run(install).then(function() {
expect(tempDir.exists('bower_components/package')).to.be(false);
expect(tempDir.exists('bower_components/package2')).to.be(true);
});
});
});
it('recognizes proxy option in config', function () {
this.timeout(10000);
tempDir.prepare({
'bower.json': {
name: 'test_tw',
dependencies: {
pure: 'http://github.com/yahoo/pure/archive/v0.6.0.tar.gz'
}
}
});
var install = helpers.command('install', {
cwd: tempDir.path
});
nock.disableNetConnect();
nock('http://dummy.local')
.get('http://github.com/yahoo/pure/archive/v0.6.0.tar.gz')
.reply(500);
return helpers.run(install, [
undefined,
undefined,
{ proxy: 'http://dummy.local/' }
]).fail(function(error) {
expect(error.message).to.equal('Status code of 500');
nock.enableNetConnect();
});
});
});

110
test/commands/link.js Normal file
View File

@@ -0,0 +1,110 @@
var expect = require('expect.js');
var helpers = require('../helpers');
var link = helpers.command('link');
describe('bower link', function () {
var package = new helpers.TempDir({
'bower.json': {
name: 'package',
},
'index.js': 'Hello World!'
});
var otherPackage = new helpers.TempDir({
'bower.json': {
name: 'package2',
},
'index.js': 'Welcome World!'
});
var linksDir = new helpers.TempDir();
beforeEach(function() {
package.prepare();
otherPackage.prepare();
linksDir.prepare();
});
it('correctly reads arguments', function() {
expect(link.readOptions(['jquery', 'angular']))
.to.eql(['jquery', 'angular']);
});
it('creates self link', function () {
return helpers.run(link, [undefined, undefined,
{
cwd: package.path,
storage: {
links: linksDir.path
}
}
]).then(function() {
expect(linksDir.read('package/index.js'))
.to.be('Hello World!');
});
});
it('creates inter-link', function () {
return helpers.run(link, [undefined, undefined,
{
cwd: package.path,
storage: {
links: linksDir.path
}
}
]).then(function () {
return helpers.run(link, ['package', undefined,
{
cwd: otherPackage.path,
storage: {
links: linksDir.path
}
}
]);
}).then(function() {
expect(otherPackage.read('bower_components/package/index.js'))
.to.be('Hello World!');
});
});
it('creates inter-link with custom local name', function () {
return helpers.run(link, [undefined, undefined,
{
cwd: package.path,
storage: {
links: linksDir.path
}
}
]).then(function () {
return helpers.run(link, ['package', 'local',
{
cwd: otherPackage.path,
storage: {
links: linksDir.path
}
}
]);
}).then(function() {
expect(otherPackage.read('bower_components/local/index.js'))
.to.be('Hello World!');
});
});
it('errors on unexising package', function () {
return helpers.run(link, ['package', 'local',
{
cwd: otherPackage.path,
storage: {
links: linksDir.path
}
}
]).then(function() {
throw 'Should fail creating a link!';
}).fail(function(reason) {
expect(reason.code).to.be('ENOENT');
expect(reason.message).to.be('Failed to create link to package');
});
});
});

256
test/commands/list.js Normal file
View File

@@ -0,0 +1,256 @@
var expect = require('expect.js');
var object = require('mout').object;
var path = require('path');
var helpers = require('../helpers');
var commands = {
install: helpers.command('install'),
list: helpers.command('list')
};
describe('bower list', function () {
var tempDir = new helpers.TempDir();
var gitPackage = new helpers.TempDir();
var install = function(packages, options, config) {
config = object.merge(config || {}, {
cwd: tempDir.path
});
return helpers.run(commands.install, [packages, options, config]);
};
var list = function(options, config) {
config = object.merge(config || {}, {
cwd: tempDir.path
});
return helpers.run(commands.list, [options, config]);
};
it('correctly reads arguments', function() {
expect(commands.list.readOptions(['-p', '-r']))
.to.eql([{
paths: true,
relative: true
}]);
});
it('correctly reads long arguments', function() {
expect(commands.list.readOptions(['--paths', '--relative']))
.to.eql([{
paths: true,
relative: true
}]);
});
it('lists no packages when nothing installed', function () {
tempDir.prepare();
return list().spread(function(results) {
expect(results).to.be.an(Object);
expect(results.canonicalDir).to.equal(tempDir.path);
expect(results.pkgMeta.dependencies).to.eql({});
expect(results.pkgMeta.devDependencies).to.eql({});
expect(results.dependencies).to.eql({});
expect(results.nrDependants).to.eql(0);
expect(results.versions).to.eql([]);
});
});
it('lists 1 dependency when 1 local package installed', function () {
var package = new helpers.TempDir({
'bower.json': {
name: 'package',
main: 'test.txt'
}
}).prepare();
package.prepare();
return install([package.path]).then(function() {
return list().spread(function(results) {
expect(results).to.be.an(Object);
expect(results.canonicalDir).to.equal(tempDir.path);
expect(results.pkgMeta.dependencies).to.eql({
package: package.path + '#*'
});
expect(results.pkgMeta.devDependencies).to.eql({});
expect(results.dependencies.package).to.be.an(Object);
expect(results.dependencies.package.pkgMeta).to.be.an(Object);
expect(results.dependencies.package.pkgMeta.main).to.equal('test.txt');
expect(results.dependencies.package.canonicalDir).to.equal(
path.join(tempDir.path, 'bower_components/package')
);
expect(results.dependencies.package.dependencies).to.eql({});
expect(results.dependencies.package.nrDependants).to.equal(1);
expect(results.dependencies.package.versions).to.eql([]);
expect(results.nrDependants).to.equal(0);
expect(results.versions).to.eql([]);
});
});
});
it('lists 1 dependency with relative paths when 1 local package installed', function () {
var package = new helpers.TempDir({
'bower.json': {
name: 'package',
main: 'test.txt'
}
}).prepare();
package.prepare();
return install([package.path]).then(function() {
return list({relative: true}).spread(function(results) {
expect(results).to.be.an(Object);
expect(results.canonicalDir).to.equal(tempDir.path);
expect(results.dependencies).to.be.an(Object);
expect(results.dependencies.package).to.be.an(Object);
expect(results.dependencies.package.pkgMeta).to.be.an(Object);
expect(results.dependencies.package.pkgMeta.main).to.equal('test.txt');
expect(results.pkgMeta.dependencies).to.eql({
package: package.path + '#*'
});
expect(results.dependencies.package.canonicalDir).to.equal(
path.normalize('bower_components/package')
);
});
});
});
it('lists 1 dependency with 1 source relative source mapping when 1 local package installed', function () {
var package = new helpers.TempDir({
'bower.json': {
name: 'package',
main: 'test.txt'
}
}).prepare();
package.prepare();
return install([package.path]).then(function() {
return list({paths: true}).spread(function(results) {
expect(results).to.be.an(Object);
expect(results.package).to.equal(
'bower_components/package/test.txt'
);
});
});
});
it('lists 1 dependency with 2 source relative source mapping when 1 local package installed', function () {
var package = new helpers.TempDir({
'bower.json': {
name: 'package',
main: ['test.txt', 'test2.txt']
}
}).prepare();
package.prepare();
return install([package.path]).then(function() {
return list({paths: true}).spread(function(results) {
expect(results).to.be.an(Object);
expect(results.package).to.be.an(Object);
expect(results.package).to.eql([
'bower_components/package/test.txt',
'bower_components/package/test2.txt'
]);
});
});
});
it('lists 1 dependency when 1 git package installed', function () {
gitPackage.prepareGit({
'1.0.0': {
'bower.json': {
name: 'package',
main: 'test.txt'
},
'version.txt': '1.0.0'
},
'1.0.1': {
'bower.json': {
name: 'package',
main: 'test2.txt'
},
'version.txt': '1.0.1'
}
});
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
}
});
return install().then(function() {
return list().spread(function(results) {
expect(results).to.be.an(Object);
expect(results.canonicalDir).to.equal(tempDir.path);
expect(results.pkgMeta.dependencies).to.eql({
package: gitPackage.path + '#1.0.0'
});
expect(results.pkgMeta.devDependencies).to.eql({});
expect(results.dependencies.package).to.be.an(Object);
expect(results.dependencies.package.pkgMeta).to.be.an(Object);
expect(results.dependencies.package.pkgMeta.main).to.equal('test.txt');
expect(results.dependencies.package.canonicalDir).to.equal(
path.join(tempDir.path, 'bower_components/package')
);
expect(results.dependencies.package.dependencies).to.eql({});
expect(results.dependencies.package.nrDependants).to.equal(1);
expect(results.dependencies.package.versions).to.eql(['1.0.1', '1.0.0']);
expect(results.nrDependants).to.equal(0);
expect(results.versions).to.eql([]);
});
});
});
it('lists 1 dependency with relative paths when 1 git package installed', function () {
gitPackage.prepareGit({
'1.0.0': {
'bower.json': {
name: 'package',
main: 'test.txt'
},
'version.txt': '1.0.0'
},
'1.0.1': {
'bower.json': {
name: 'package',
main: 'test2.txt'
},
'version.txt': '1.0.1'
}
});
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
}
});
return install().then(function() {
return list({relative: true}).spread(function(results) {
expect(results.canonicalDir).to.equal(tempDir.path);
expect(results.pkgMeta.dependencies).to.eql({
package: gitPackage.path + '#1.0.0'
});
expect(results.dependencies.package.canonicalDir).to.equal(
path.normalize('bower_components/package')
);
});
});
});
});

187
test/commands/login.js Normal file
View File

@@ -0,0 +1,187 @@
var expect = require('expect.js');
var helpers = require('../helpers');
var fakeGitHub = function (authenticate) {
function FakeGitHub() { }
var _creds;
FakeGitHub.prototype.authenticate = function (creds) {
_creds = creds;
};
FakeGitHub.prototype.authorization = {
create: function (options, cb) {
if (_creds.password === 'validpassword') {
cb(null, { token: 'faketoken' });
} else if (_creds.password === 'withtwofactor') {
if (options.headers && options.headers['X-GitHub-OTP'] === '123456') {
cb(null, { token: 'faketwoauthtoken' });
} else {
cb({ code: 401, message: '{ "message": "Must specify two-factor authentication OTP code." }' });
}
} else {
cb({ code: 401, message: 'Bad credentials' });
}
}
};
return FakeGitHub;
};
var fakeConfigstore = function (set, get) {
function FakeConfigstore() { }
FakeConfigstore.prototype.set = set;
FakeConfigstore.prototype.get = get;
return FakeConfigstore;
};
var login = helpers.command('login');
var loginFactory = function (options) {
return helpers.command('login', {
'github': fakeGitHub(),
'configstore': fakeConfigstore(
options.set || function () { return true; },
options.get || function () { return true; }
)
});
};
describe('bower login', function () {
it('correctly reads arguments', function() {
expect(login.readOptions(['--token', 'foobar']))
.to.eql([{ token: 'foobar' }]);
});
it('fails if run in non-interactive shell without token passed', function () {
return helpers.run(login, []).fail(function(reason) {
expect(reason.message).to.be('Login requires an interactive shell');
expect(reason.code).to.be('ENOINT');
});
});
it('succeeds if run in non-interactive shell with token passed', function () {
return helpers.run(login, [{ token: 'foobar' }]);
});
it('succeeds if provided password is valid', function () {
var login = loginFactory({});
var logger = login({}, { interactive: true });
logger.once('prompt', function (prompt, answer) {
answer({
username: 'user',
password: 'validpassword'
});
});
return helpers.expectEvent(logger, 'end')
.spread(function(options) {
expect(options.token).to.be('faketoken');
});
});
it('supports two-factor authorization', function () {
var login = loginFactory({});
var logger = login({}, { interactive: true });
logger.once('prompt', function (prompt, answer) {
logger.once('prompt', function (prompt, answer) {
answer({
otpcode: '123456'
});
});
answer({
username: 'user',
password: 'withtwofactor'
});
});
return helpers.expectEvent(logger, 'end')
.spread(function(options) {
expect(options.token).to.be('faketwoauthtoken');
});
});
it('fails if provided password is invalid', function () {
var login = loginFactory({});
var logger = login({}, { interactive: true });
logger.once('prompt', function (prompt, answer) {
answer({
username: 'user',
password: 'invalidpassword'
});
});
return helpers.expectEvent(logger, 'error').spread(function (error) {
expect(error.code).to.be('EAUTH');
expect(error.message).to.be('Authorization failed');
});
});
it('uses username stored in config as default username', function () {
var login = loginFactory({
get: function (key) {
if (key === 'username') {
return 'savedusername';
}
}
});
var logger = login({}, { interactive: true });
return helpers.expectEvent(logger, 'prompt')
.spread(function (prompt, answer) {
expect(prompt[0].default).to.be('savedusername');
});
});
it('saves username in config', function (done) {
var login = loginFactory({
set: function (key, value) {
if(key === 'username') {
expect(value).to.be('user');
done();
}
}
});
var logger = login({}, { interactive: true });
logger.once('prompt', function (prompt, answer) {
answer({
username: 'user',
password: 'validpassword'
});
});
});
it('saves received token in accessToken config', function (done) {
var login = loginFactory({
set: function (key, value) {
if(key === 'accessToken') {
expect(value).to.be('faketoken');
done();
}
}
});
var logger = login({}, { interactive: true });
logger.once('prompt', function (prompt, answer) {
answer({
username: 'user',
password: 'validpassword'
});
});
});
});

55
test/commands/lookup.js Normal file
View File

@@ -0,0 +1,55 @@
var expect = require('expect.js');
var helpers = require('../helpers');
var lookup = helpers.command('lookup');
describe('bower lookup', function () {
var lookupWithResult = function (response) {
return helpers.command('lookup', {
'bower-registry-client': function() {
return {
lookup: function(query, callback) {
if (query in response) {
callback(null, response[query]);
} else {
callback();
}
}
};
}
});
};
it('correctly reads arguments', function() {
expect(lookup.readOptions(['jquery']))
.to.eql(['jquery']);
});
it('lookups package by name', function () {
var lookup = lookupWithResult({ jquery: { url: 'http://jquery.org' } });
return helpers.run(lookup, ['jquery']).spread(function(result) {
expect(result).to.eql({
name: 'jquery',
url: 'http://jquery.org'
});
});
});
it('returns null if no package is found', function () {
var lookup = lookupWithResult({ jquery: { url: 'http://jquery.org' } });
return helpers.run(lookup, ['foobar']).spread(function(result) {
expect(result).to.eql(null);
});
});
it('returns null if called without argument', function () {
var lookup = lookupWithResult({ jquery: { url: 'http://jquery.org' } });
return helpers.run(lookup, []).spread(function(result) {
expect(result).to.eql(null);
});
});
});

65
test/commands/prune.js Normal file
View File

@@ -0,0 +1,65 @@
var expect = require('expect.js');
var helpers = require('../helpers');
var prune = helpers.command('prune');
describe('bower home', function () {
var package = new helpers.TempDir({
'bower.json': {
name: 'package',
dependencies: {
jquery: '*'
}
},
'bower_components/jquery/jquery.js': 'jquery source'
});
it('correctly reads arguments', function() {
expect(prune.readOptions(['-p']))
.to.eql([{ production: true }]);
});
it('correctly reads long arguments', function() {
expect(prune.readOptions(['--production']))
.to.eql([{ production: true }]);
});
it('removes extraneous packages', function () {
package.prepare({
'bower_components/angular/angular.js': 'angular source',
'bower_components/angular/.bower.json': { name: 'angular' }
});
return helpers.run(prune, [{}, { cwd: package.path }]).then(function() {
expect(package.exists('bower_components/angular/angular.js'))
.to.be(false);
});
});
it('leaves non-bower packages', function () {
package.prepare({
'bower_components/angular/angular.js': 'angular source'
});
return helpers.run(prune, [{}, { cwd: package.path }]).then(function() {
expect(package.exists('bower_components/angular/angular.js'))
.to.be(true);
});
});
it('deals with custom directory', function () {
package.prepare({
'.bowerrc': { directory: 'components' },
'bower_components/angular/.bower.json': { name: 'angular' },
'bower_components/angular/angular.js': 'angular source',
'components/angular/.bower.json': { name: 'angular' },
'components/angular/angular.js': 'angular source'
});
return helpers.run(prune, [{}, { cwd: package.path }]).then(function() {
expect(package.exists('components/angular/angular.js')).to.be(false);
expect(package.exists('bower_components/angular/angular.js')).to.be(true);
});
});
});

117
test/commands/register.js Normal file
View File

@@ -0,0 +1,117 @@
var Q = require('q');
var expect = require('expect.js');
var helpers = require('../helpers');
var register = helpers.command('register');
var fakeRepositoryFactory = function (canonicalDir, pkgMeta) {
function FakeRepository() { }
FakeRepository.prototype.fetch = function() {
return Q.fcall(function () {
return [canonicalDir, pkgMeta];
});
};
FakeRepository.prototype.getRegistryClient = function() {
return {
register: function (name, url, cb) {
cb(null, { name: name, url: url });
}
};
};
return FakeRepository;
};
var register = helpers.command('register');
var registerFactory = function (canonicalDir, pkgMeta) {
return helpers.command('register', {
'../core/PackageRepository': fakeRepositoryFactory(
canonicalDir, pkgMeta
)
});
};
describe('bower register', function () {
var package = new helpers.TempDir({
'bower.json': {
name: 'package'
}
});
it('correctly reads arguments', function() {
expect(register.readOptions(['jquery', 'url']))
.to.eql(['jquery', 'url']);
});
it('errors if name is not provided', function () {
return helpers.run(register).fail(function(reason) {
expect(reason.message).to.be('Usage: bower register <name> <url>');
expect(reason.code).to.be('EINVFORMAT');
});
});
it('errors if url is not provided', function () {
return helpers.run(register, ['some-name'])
.fail(function(reason) {
expect(reason.message).to.be('Usage: bower register <name> <url>');
expect(reason.code).to.be('EINVFORMAT');
});
});
it('errors if trying to register private package', function () {
package.prepare({ 'bower.json': { private: true } });
var register = registerFactory(package.path, package.meta());
return helpers.run(register, ['some-name', 'git://fake-url.git'])
.fail(function(reason) {
expect(reason.message).to.be('The package you are trying to register is marked as private');
expect(reason.code).to.be('EPRIV');
});
});
it('should call registry client with name and url', function () {
package.prepare();
var register = registerFactory(package.path, package.meta());
return helpers.run(register, ['some-name', 'git://fake-url.git'])
.spread(function(result) {
expect(result).to.eql({
// Result from register action on stub
name: 'some-name', url: 'git://fake-url.git'
});
});
});
it('should confirm in interactive mode', function () {
package.prepare();
var register = registerFactory(package.path, package.meta());
var promise = helpers.run(register,
['some-name', 'git://fake-url.git', { interactive: true }]
);
return helpers.expectEvent(promise.logger, 'confirm')
.spread(function(e) {
expect(e.type).to.be('confirm');
expect(e.message).to.be('Registering a package will make it installable via the registry (https://bower.herokuapp.com), continue?');
expect(e.default).to.be(true);
});
});
it('should skip confirming when forcing', function () {
package.prepare();
var register = registerFactory(package.path, package.meta());
return helpers.run(register,
['some-name', 'git://fake-url.git',
{ interactive: true, force: true }
]
);
});
});

44
test/commands/search.js Normal file
View File

@@ -0,0 +1,44 @@
var Q = require('q');
var expect = require('expect.js');
var helpers = require('../helpers');
var search = helpers.command('search');
describe('bower search', function () {
it('correctly reads arguments', function() {
expect(search.readOptions(['jquery']))
.to.eql(['jquery']);
});
it('searches for single repository', function () {
return Q.Promise(function(resolve) {
var search = helpers.command('search', {
'bower-registry-client': function() {
return {
search: resolve
};
}
});
helpers.run(search, ['jquery'], {});
}).then(function(query) {
expect(query).to.be('jquery');
});
});
it('lists all repositories if no query given', function () {
return Q.Promise(function(resolve) {
var search = helpers.command('search', {
'bower-registry-client': function() {
return {
list: resolve
};
}
});
helpers.run(search, [], {});
});
});
});

View File

@@ -0,0 +1,56 @@
var path = require('path');
var expect = require('expect.js');
var fs = require('../../lib/util/fs');
var helpers = require('../helpers');
var uninstall = helpers.command('uninstall');
describe('bower uninstall', function () {
var tempDir = new helpers.TempDir({
'bower.json': {
name: 'hello-world',
dependencies: {
'underscore': '*'
}
}
});
beforeEach(function() {
tempDir.prepare();
});
var bowerJsonPath = path.join(tempDir.path, 'bower.json');
function bowerJson() {
return JSON.parse(fs.readFileSync(bowerJsonPath));
}
var config = {
cwd: tempDir.path,
interactive: true
};
it('correctly reads arguments', function() {
expect(uninstall.readOptions(['jquery', '-S', '-D']))
.to.eql([['jquery'], { save: true, saveDev: true }]);
});
it('correctly reads long arguments', function() {
expect(uninstall.readOptions(['jquery', '--save', '--save-dev']))
.to.eql([['jquery'], { save: true, saveDev: true }]);
});
it('does not remove anything from dependencies by default', function () {
return helpers.run(uninstall, [['underscore'], undefined, config]).then(function () {
expect(bowerJson().dependencies).to.eql({ 'underscore': '*' });
});
});
it('removes dependency from bower.json if --save flag is used', function () {
return helpers.run(uninstall, [['underscore'], {save: true}, config]).then(function () {
expect(bowerJson().dependencies).to.eql({});
});
});
});

View File

@@ -0,0 +1,79 @@
var expect = require('expect.js');
var helpers = require('../helpers');
var fakeRepositoryFactory = function () {
function FakeRepository() { }
FakeRepository.prototype.getRegistryClient = function() {
return {
unregister: function (name, cb) {
cb(null, { name: name });
}
};
};
return FakeRepository;
};
var unregister = helpers.command('unregister');
var unregisterFactory = function () {
return helpers.command('unregister', {
'../core/PackageRepository': fakeRepositoryFactory()
});
};
describe('bower unregister', function () {
it('correctly reads arguments', function() {
expect(unregister.readOptions(['jquery']))
.to.eql(['jquery']);
});
it('errors if name is not provided', function () {
return helpers.run(unregister).fail(function(reason) {
expect(reason.message).to.be('Usage: bower unregister <name> <url>');
expect(reason.code).to.be('EINVFORMAT');
});
});
it('should call registry client with name', function () {
var unregister = unregisterFactory();
return helpers.run(unregister, ['some-name'])
.spread(function(result) {
expect(result).to.eql({
// Result from register action on stub
name: 'some-name'
});
});
});
it('should confirm in interactive mode', function () {
var register = unregisterFactory();
var promise = helpers.run(register,
['some-name', {
interactive: true,
registry: { register: 'http://localhost' }
}]
);
return helpers.expectEvent(promise.logger, 'confirm')
.spread(function(e) {
expect(e.type).to.be('confirm');
expect(e.message).to.be('You are about to remove component "some-name" from the bower registry (http://localhost). It is generally considered bad behavior to remove versions of a library that others are depending on. Are you really sure?');
expect(e.default).to.be(false);
});
});
it('should skip confirming when forcing', function () {
var register = unregisterFactory();
return helpers.run(register,
['some-name',
{ interactive: true, force: true }
]
);
});
});

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

@@ -0,0 +1,392 @@
var expect = require('expect.js');
var object = require('mout').object;
var helpers = require('../helpers');
var updateCmd = helpers.command('update');
var commands = helpers.require('lib/index').commands;
describe('bower update', function () {
var tempDir = new helpers.TempDir();
var subPackage = new helpers.TempDir({
'bower.json': {
name: 'subPackage'
}
}).prepare();
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',
dependencies: {
subPackage: subPackage.path
}
},
'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('correctly reads arguments', function() {
expect(updateCmd.readOptions(['jquery', '-F', '-p']))
.to.eql([['jquery'], { forceLatest: true, production: true }]);
});
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('does not install ignored dependencies', function() {
var package3 = new helpers.TempDir({
'bower.json': {
name: 'package3'
}
}).prepare();
var package2 = new helpers.TempDir({
'bower.json': {
name: 'package2',
dependencies: {
package3: package3.path
}
}
}).prepare();
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package2: package2.path
}
},
'.bowerrc': {
ignoredDependencies: ['package3']
}
});
return update().then(function() {
expect(tempDir.exists('bower_components/package2/bower.json')).to.equal(true);
expect(tempDir.exists('bower_components/package3')).to.equal(false);
});
});
it('does not install ignored dependencies if run multiple times', function() {
var package3 = new helpers.TempDir({
'bower.json': {
name: 'package3'
}
}).prepare();
var package2 = new helpers.TempDir({
'bower.json': {
name: 'package2',
dependencies: {
package3: package3.path
}
}
}).prepare();
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package2: package2.path
}
},
'.bowerrc': {
ignoredDependencies: ['package3']
}
});
return update().then(function() {
return update().then(function() {
expect(tempDir.exists('bower_components/package2/bower.json')).to.equal(true);
expect(tempDir.exists('bower_components/package3')).to.equal(false);
});
});
});
it('runs preinstall hook when installing missing package', function () {
package.prepare();
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: package.path
}
},
'.bowerrc': {
scripts: {
preinstall: 'node -e \'require("fs").writeFileSync("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: 'node -e \'require("fs").writeFileSync("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: 'node -e \'require("fs").writeFileSync("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('does not install ignored dependencies when updating a package', function () {
var package3 = new helpers.TempDir({
'bower.json': {
name: 'package3'
}
}).prepare();
var package2 = new helpers.TempDir().prepareGit({
'1.0.0': {
'bower.json': {
name: 'package2',
version: '1.0.0',
dependencies: {
package3: package3.path
}
}
},
'1.0.1': {
'bower.json': {
name: 'package2',
version: '1.0.1',
dependencies: {
package3: package3.path
}
}
}
});
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package2: package2.path + '#1.0.0'
}
},
'.bowerrc': {
ignoredDependencies: ['package3']
}
});
return install().then(function() {
expect(tempDir.readJson('bower_components/package2/bower.json').version).to.equal('1.0.0');
expect(tempDir.exists('bower_components/package3')).to.equal(false);
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package2: package2.path + '#1.0.1'
}
},
'.bowerrc': {
ignoredDependencies: ['package3']
}
});
return update().then(function() {
expect(tempDir.readJson('bower_components/package2/bower.json').version).to.equal('1.0.1');
expect(tempDir.exists('bower_components/package3')).to.equal(false);
});
});
});
it('runs preinstall hook when updating a package', function () {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
}
});
return install().then(function() {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.1'
}
},
'.bowerrc': {
scripts: {
preinstall: 'node -e \'require("fs").writeFileSync("preinstall.txt", "%")\''
}
}
});
expect(tempDir.exists('preinstall.txt')).to.be(false);
return update().then(function() {
expect(tempDir.read('preinstall.txt')).to.be('subPackage package');
});
});
});
it('runs postinstall hook when updating a package', function () {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
}
});
return install().then(function() {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.1'
}
},
'.bowerrc': {
scripts: {
preinstall: 'node -e \'require("fs").writeFileSync("preinstall.txt", "%")\'',
postinstall: 'node -e \'require("fs").writeFileSync("postinstall.txt", "%")\''
}
}
});
expect(tempDir.exists('postinstall.txt')).to.be(false);
return update().then(function() {
expect(tempDir.read('postinstall.txt')).to.be('subPackage package');
});
});
});
});

Some files were not shown because too many files have changed in this diff Show More