Compare commits

...

454 Commits

Author SHA1 Message Date
Adam Stankiewicz
b15acd2fea Add info file 2015-03-20 21:08:11 -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
Sindre Sorhus
5247d93d7b Merge pull request #1193 from chiester/wip-1191-improveUninstallWarning
clarify uninstall warning message
2014-04-11 21:45:37 +02:00
Chris Hiester
bda5312917 clarified uniinstall warning message
improved uninstall message

edits to uninstall message

revisions per conversation with core team

fixed lint errors

reverted mode changes

reverted test.js mode change
2014-04-11 12:35:28 -04:00
Ahmad Nassri
e8c071304c using 'svn export' for efficiency. Closes #1224 2014-04-11 09:00:41 -04:00
Sindre Sorhus
4e155caae9 Merge pull request #1124 from jmmk/quiet_output
Suppress end output if quiet option is specified
2014-04-11 12:11:45 +02:00
Sindre Sorhus
aedb7e44ba Merge pull request #1231 from bower/fix/dependencies
Update dependencies in package.json to newest patches
2014-04-11 12:02:25 +02:00
Sindre Sorhus
c58109c1b4 Merge pull request #1232 from bower/feature/lazy-loading
Loading commands on demand increases performance
2014-04-11 11:50:20 +02:00
Thomas Scholtes
2167c7b6d4 Loading commands on demand increases performance 2014-04-11 06:07:42 +02:00
Adam Stankiewicz
22eef989f6 Update dependencies in package.json to newest patches 2014-04-11 05:42:53 +02:00
Adam Stankiewicz
edf387363d doc: Point to new official msysgit website 2014-04-11 01:44:07 +02:00
Adam Stankiewicz
14ffb7443f Merge pull request #1230 from brettz9/readme
Refer to spec; add description in sample; fix map order
2014-04-10 02:26:29 +02:00
Brett Zamir
c21ba1e64c Refer to spec; add description in sample; fix map order 2014-04-10 08:02:18 +08:00
Sindre Sorhus
46b8069c6d Merge pull request #1227 from Aleanar/master
Update private Subversion repository example in README.md
2014-04-09 16:39:40 +02:00
Alexandre MOTTET
2fb697f452 Update private Subversion repository example in README.md 2014-04-09 13:44:22 +00:00
Ben Schwarz
73f3293314 Merge pull request #1212 from bower/release-1-3-2
Prepped 1.3.2 release
2014-04-07 11:40:45 +10:00
Ben Schwarz
53fc25550b Added yui moduletype 2014-04-07 11:35:16 +10:00
Eric Ferraiuolo
961d1775e5 Squashed commit of the following:
commit d5ab6721b6d0014b25ec7e5da2078627f0e794dd
Author: Eric Ferraiuolo <eferraiuolo@gmail.com>
Date:   Thu Feb 20 19:07:04 2014 -0500

    Add YUI modules as a `moduleType` choice

Closes gh-1129.
2014-04-06 18:13:37 -07:00
Ben Schwarz
bdaa359d11 Update changelog with #1211, remove #1077 2014-04-07 11:01:25 +10:00
Ben Schwarz
a90cc018dd Merge pull request #1216 from benschwarz/master
Revert 1077, merge 1211
2014-04-07 10:53:39 +10:00
Sindre Sorhus
e06f784d35 Merge pull request #1215 from t3chnoboy/master
Use SVG version of travis build status badge
2014-04-06 21:01:09 +02:00
Dmitry Mazuro
5ce1b6a4c1 Use SVG version of travis build status badge 2014-04-06 19:47:06 +03:00
Sindre Sorhus
86d579a7ae Merge pull request #1213 from rayshan/patch-1
Tweak insight tracking for `search` command
2014-04-06 14:15:29 +02:00
Ray Shan
d031ab5ceb Tweak insight tracking for search command
- Tracking should be consistent with `info` command; tracking should register upon user calling the command, not when the command is completed
- Vocabulary should be consistent with other tracking - if only tracking upon calling the command, should use the present tense of the verb without -"ed"

This is an improvement for tracking and reporting consistency, please see https://github.com/bower/bower/issues/1164#issuecomment-39385297
2014-04-05 23:00:38 -07:00
Ben Schwarz
dcf7230494 Merge remote-tracking branch 'sheerun/fix/concurrency' 2014-04-06 14:59:37 +10:00
Ben Schwarz
20aca45b39 Revert "Merge pull request #1077 from neoziro/fix-concurrent-renaming"
This reverts commit cd541c7a20, reversing
changes made to 3074711828.
2014-04-06 14:57:40 +10:00
Ben Schwarz
98ea43eca9 Prepped 1.3.2 release 2014-04-05 16:03:25 +11:00
Ben Schwarz
cd541c7a20 Merge pull request #1077 from neoziro/fix-concurrent-renaming
Fix bug with concurrent renaming
2014-04-05 15:41:39 +11:00
Adam Stankiewicz
5a5cf31aba Make bower concurrent friendly, fixes #933 2014-04-04 19:09:18 +02:00
Mat Scales
3074711828 Merge pull request #891 from bower/link-install-deps
Install linked package dependencies, closes #673.
2014-04-02 13:59:14 +01:00
Mat Scales
aa97c928e9 Merge pull request #1203 from wibblymat/node0.11
Add node 0.11 to Travis, but allow it to fail.
2014-04-02 13:54:52 +01:00
Mat Scales
5b4d6f2fee Add node 0.11 to Travis, but allow it to fail. 2014-04-02 12:55:00 +01:00
André Cruz
7120ad5014 Merge branch 'master' of github.com:bower/bower into link-install-deps 2014-03-25 11:51:40 +00:00
André Cruz
6286cfac28 Merge branch 'master' into link-install-deps 2014-03-25 11:49:05 +00:00
Sindre Sorhus
1af09da2aa Merge pull request #1188 from chiester/wip-1187-conflictResolvedMessage
Improved conflict resolution message
2014-03-25 09:58:28 +01:00
Chris Hiester
bea533acf8 Improved conflict resolution message
removed test-generated npm-debug.log

fixed semver formatting in message
2014-03-24 13:48:25 -04:00
Mat Scales
5316c2479e Merge pull request #1168 from c960657/prune-production
Add --production switch to "prune" command
2014-03-19 16:37:29 +00:00
Andrey 'lolmaus' Mikhaylov
9895f8c71e readme - links to the range syntax of version numbers
closes #1172
2014-03-14 17:50:11 +01:00
Christian Schmidt
9749a398c0 Add --production switch to "prune" command 2014-03-12 18:38:17 +01:00
Ben Schwarz
2883a278ee Formatting tweaks to the CI section 2014-03-12 10:58:41 +11:00
Ben Schwarz
6bbb9ec956 Merge pull request #1165 from nschonni/ci-readme-update
Add additional CI examples
2014-03-12 10:54:47 +11:00
Mat Scales
630556f7fe Release 1.3.1 2014-03-11 23:39:39 +00:00
Nick Schonning
32a79c4fa1 Add additional CI examples 2014-03-11 19:33:26 -04:00
Paul Irish
eabfee6890 Merge pull request #1163 from nschonni/no-analytics-on-ci
Don't turn on analytics on CI unless explicitly set
2014-03-11 16:27:12 -07:00
Nick Schonning
6b9b283149 Add CI server configuration notes 2014-03-11 19:02:43 -04:00
Paul Irish
a19ad6663c insight in changelog 2014-03-11 13:52:35 -07:00
Nick Schonning
dfae97a8d7 Don't turn on analytics on CI unless explicit 2014-03-11 16:51:41 -04:00
Mat Scales
93d03434eb Merge pull request #1152 from ahmadnassri/svnResolver-test-fix
svnResolver updates
2014-03-11 16:06:54 +00:00
Mat Scales
76fcd341f6 Release 1.3.0 2014-03-10 21:27:56 +00:00
Ahmad Nassri
cea53ddd1f Remove Node 0.8 compatibility. Closes #1154 2014-03-06 16:33:48 +01:00
Ahmad Nassri
cfc061c960 Merge branch 'svn-resolver-fixes' of github.com:jevakallio/bower into svnResolver-test-fix 2014-03-05 14:10:08 -05:00
Ahmad Nassri
09bc1784e4 Fixing svnResolver tests
- duplicate 0.0.1 tag in json config
- `svn list` return directory separator at the end of branch/tag names
2014-03-05 13:51:43 -05:00
Sindre Sorhus
b723cf92e7 Merge pull request #1151 from eventualbuddha/fix-regex-typo
Fix a typo in the regex trying to understand the command.
2014-03-05 12:04:12 +01:00
Brian Donovan
3d7b9f60da Fix a typo in the regex trying to understand the command.
This is almost certainly not trying to replace 's' characters in a command and is, instead, meant to check for nested properties on bower.commands that were not found in bower.abbreviations for some reason. The kicker is that bower.abbreviations already contains the full command names in addition to the shortened ones, so this code path ("Direct lookup") probably never actually results in finding anything and can probably safely be removed.
2014-03-04 15:38:46 -08:00
jevakallio
b049bdb33f Fix problem with dashes and underscores in branch/tag names 2014-02-27 17:31:01 +02:00
Sindre Sorhus
9aa6c2bd9f Merge pull request #1137 from cvrebert/master
spelling fixes in changelog
2014-02-25 02:09:22 +01:00
Chris Rebert
4450cce0e9 spelling fixes in changelog 2014-02-24 14:35:36 -08:00
Paul Irish
523cd421b3 changelog update for permalink. 2014-02-24 14:05:01 -08:00
Paul Irish
88dbbe7bc3 changelog. master --> canary. 2014-02-24 14:03:51 -08:00
Michael McLellan
cc63a633b5 Suppress end output if quiet option is specified 2014-02-18 17:13:04 -05:00
Renan Gonçalves
72578e49dd update changelog #1122 2014-02-17 22:31:39 +01:00
Nicolas Gallagher
83f1edb9da Move @necolas out of core team 2014-02-13 15:49:25 -08:00
Greg Bergé
8016bd4e1f Use new Q() shortcut. 2014-02-13 16:30:39 +01:00
Mat Scales
cb8b25da53 Merge pull request #1104 from SevInf/fix/circular
Allow circular dependencies to be installed
2014-02-11 10:44:58 +00:00
Mat Scales
c517b98bae Merge pull request #1055 from ahmadnassri/master
Subversion Support
2014-02-10 13:22:39 +00:00
Mat Scales
d66876a9c9 Merge pull request #718 from cgross/install-hooks
scripts/hooks functionality added
2014-02-10 13:22:07 +00:00
Sergey Tatarintsev
ad31df09fb Allow circular dependencies to be installed 2014-02-08 12:28:14 +02:00
Chris Gross
1831e507fe scripts/hooks functionality added 2014-02-06 15:40:38 -05:00
Sindre Sorhus
b12a58632d upgrade chalk 2014-02-06 19:25:03 +01:00
Sindre Sorhus
8442373be9 use is-root instead of sudo-block
since sudo-block isn't really used anyways
2014-02-06 19:13:55 +01:00
Sindre Sorhus
0f7a9e5a74 bump deps 2014-02-06 19:05:56 +01:00
André Cruz
74bce91db8 Merge pull request #1100 from chiester/wip-1099-clarifyConflictMessage
Clarify package conflict message displayed in console
2014-02-06 01:46:47 +00:00
Chris Hiester
469b74ffb3 Clarify package conflict message displayed in console 2014-02-05 20:23:46 -05:00
Sindre Sorhus
5dd0775737 Merge pull request #1098 from chiester/wip-1097-fixMisspelledMessage
Fix misspelled message showing in console
2014-02-06 00:09:00 +01:00
Chris Hiester
33ed5402f4 Fix misspelled message showing in console
Change "dependants" to "dependencies"
2014-02-05 16:40:44 -05:00
Sindre Sorhus
aa655dadc4 readme - new bower search url 2014-02-05 17:07:19 +01:00
Sindre Sorhus
0828025450 Merge pull request #1091 from lfender6445/bower_link_error_message_typos
fix error message
2014-02-04 11:30:57 -08:00
Sindre Sorhus
d9db3b695c Merge pull request #1087 from gdi2290/grunt-tasks
add load-grunt-tasks
2014-02-04 11:27:42 -08:00
Luke Fender
c3e6199ecf fix error message 2014-02-04 14:15:54 -05:00
gdi2290
926b9e0bfd add load-grunt-tasks 2014-02-04 08:51:39 -08:00
André Cruz
05ba2ee2f2 Merge pull request #1062 from patrickkettner/dumb-http-fallback
add fallback support to dumb http servers
2014-02-04 01:25:58 -08:00
Paul Irish
cd865e0a0a update copyright year
closes #1089
2014-02-03 22:13:38 -08:00
Patrick Kettner
08af876e01 add fallback support to dumb http servers 2014-02-03 22:32:07 -05:00
Mat Scales
a33fb05946 Merge pull request #1076 from neoziro/fix-cache-directory-reading
Fix reading versions from cache directory.
2014-02-03 13:32:31 -08:00
André Cruz
de52fa564b Merge pull request #1085 from ahutchings/master
Cache output of GitFsResolver.refs and GitRemoteResolver.refs, fixes #1083
2014-02-03 13:27:17 -08:00
Sindre Sorhus
8e13d406d4 Merge pull request #1086 from gdi2290/patch-1
update copyright year
2014-02-03 03:58:06 -08:00
PatrickJS
504f738cff update copyright year 2014-02-03 02:09:46 -08:00
Andrew Hutchings
fde7965bc9 Cache output of GitFsResolver.refs and GitRemoteResolver.refs, fixes #1083 2014-02-01 21:43:50 -05:00
Ahmad Nassri
a1154bf383 Merge remote-tracking branch 'upstream/master'
Conflicts:
	README.md
2014-01-30 16:56:29 -05:00
Sindre Sorhus
c9cf6d157b readme - remove sourcegraph badge. not needed 2014-01-29 12:39:34 +01:00
Mat Scales
ef2f637d01 Merge pull request #1074 from wibblymat/save-by-default
Added a .bowerrc option to make bower install assume --save (#1040)
2014-01-28 04:07:37 -08:00
Greg Bergé
d130c73e77 Fix removing directory. 2014-01-28 12:23:21 +01:00
Greg Bergé
3ecc389e66 Fix concurrent renaming.
We stock the renaming promise in cache to ensure that there is not concurrent renaming.
2014-01-27 23:56:22 +01:00
Greg Bergé
3dbddfba41 Fix reading versions from cache directory.
When we write into directory we use encodeURIComponent, but when we read, we did nothing. Now we use decodeURIComponent to read the correct version and match the correct cache directory.
2014-01-27 22:55:07 +01:00
Mat Scales
e6cb497d1a Added a .bowerrc option to make bower install assume --save (#1040) 2014-01-27 21:00:57 +00:00
Mat Scales
703ac13185 Merge pull request #1073 from earnubs/patch-1
Update Team Meeting time to correct UTC
2014-01-27 12:59:32 -08:00
Stephen
a3e1c86953 Update Team Meeting time to correct UTC 2014-01-27 20:32:04 +00:00
Mat Scales
3d64222655 Merge pull request #1029 from sheerun/semver
Ignore prerelease versions if possible, fixes #1017
2014-01-20 13:52:41 -08:00
Paul Irish
5eb29fa99c Update CONTRIBUTING.md 2014-01-20 13:44:06 -08:00
Paul Irish
84a24b76c7 bower team 2014-01-20 13:34:05 -08:00
Mat Scales
89c2a64008 Merge pull request #961 from sebastianseilund/version-command
Added bower version command, like npm version
2014-01-20 12:23:07 -08:00
Sebastian Seilund
c1f992b11f Added bower version command 2014-01-20 11:29:14 -08:00
Ahmad Nassri
ea60442fd6 fixing failed travis test 2014-01-19 13:22:09 -05:00
Ahmad Nassri
d78c5fd7c2 Merge remote-tracking branch 'upstream/master' 2014-01-19 13:11:52 -05:00
Ahmad Nassri
3b98e4962a final working version, with unit tests update README with examples 2014-01-19 13:09:49 -05:00
Sindre Sorhus
f739a1141d Merge pull request #934 from wibblymat/module-type-init
Add the 'moduleType' property to bower init
2014-01-18 05:37:02 -08:00
Ahmad Nassri
c498e9437e merging SvnResolver with SvnRemoteResolver 2014-01-17 13:59:10 -05:00
Ahmad Nassri
9f9a7c6b38 updating README with Subversion info 2014-01-16 23:57:54 -05:00
Ahmad Nassri
e3d2949c46 Merge pull request #1 from kennethklee/feature/svnTests
Feature/svn tests
2014-01-16 20:47:27 -08:00
Kenneth Lee
53f563ec08 added svn+file for local file system checkouts 2014-01-16 20:07:04 +00:00
Kenneth Lee
d27c36c70c fixed the gitResolver tags and associated test 2014-01-16 19:52:02 +00:00
Kenneth Lee
820834111b fixed svn+xxxxx protocol 2014-01-16 19:35:52 +00:00
Kenneth Lee
0c7b09c237 created svn tests in resolver factory 2014-01-16 19:18:27 +00:00
Ahmad Nassri
6eb8bb6241 fully functional
now to prove it with some proper tests
2014-01-15 22:37:47 -05:00
Ahmad Nassri
ce0b18045b jshint fixes 2014-01-15 20:15:27 -05:00
Ahmad Nassri
10d2d566a3 WIP working off trunk and maybe tags, not fully tested, not fully ready for PR 2014-01-15 17:46:43 -05:00
Sindre Sorhus
9c404185e8 Merge pull request #1045 from wibblymat/debug-mode
Enable Q long stack traces when in verbose mode
2014-01-14 14:08:44 -08:00
Mat Scales
3044c16ce1 Enable Q long stack traces when in verbose mode 2014-01-14 13:10:23 +00:00
Joshua Clanton
729ef0190e Note that commit hashes are valid versions
Closes #870
2014-01-08 18:43:45 +01:00
Sindre Sorhus
3aab16f6db Merge pull request #1016 from kud/feat.update-readme
Update docs about private repo. refs #897
2014-01-08 09:41:42 -08:00
André Cruz
0682882ac1 Merge pull request #1031 from yaph/patch-1
Changed to uppercase S as shorthand for save
2014-01-03 05:13:54 -08:00
Mat Scales
da46cd78a3 Merge pull request #1024 from bower/prune_promise_fix
Forgot to return promise, fixes #1023
2014-01-03 03:33:08 -08:00
Ramiro Gómez
f3cca3483d Changed to uppercase S as shorthand for save
Before the help showed lowercase s '-s' as the option to save installed packages, which doesn't work.
2014-01-02 19:12:52 +01:00
Adam Stankiewicz
40dbb0ffaf Ignore prerelease versions if possible, fixes #1017 2013-12-31 15:43:36 +01:00
André Cruz
b11ef97b7d Forgot to return promise, fixes #1023 2013-12-25 23:33:40 +00:00
Sindre Sorhus
634ed4a341 Add note about setting custom install dir
Closes #845
2013-12-22 02:36:52 +01:00
Erwann Mest
52aa87c145 Update docs about private repo. refs #897 2013-12-16 22:16:17 +01:00
Sindre Sorhus
3aea5fb704 Merge pull request #999 from frozzare/patch-1
Updated readme with correct jQuery example script tag
2013-12-07 08:23:33 -08:00
Fredrik Forsmo
ea559592b5 Updated readme with correct jQuery example script tag
The example script tag has `jquery/index.js` but jQuery have `jquery.js` as main file, so the readme should have the correct main file in the example.
2013-12-07 11:17:12 +01:00
Sindre Sorhus
a74b925173 Merge pull request #996 from kuatsure/list/help
Mentions updates in `bower list -h` command
2013-12-05 13:40:46 -08:00
Pascal Hartig
e84f71cd37 Basic usage tracking with insight
The user is prompted with a question whether he would like to provide usage data
    on the first start, when run in interactive mode. The current implementation
    tracks calls to `install`, `info`, `search` and `uninstall` when run with at
    least one parameter.

    Fixes #260

Closes gh-796.
2013-12-05 22:37:32 +01:00
Jarrett Drouillard
bc6428536c Mentions updates in bower list -h command 2013-12-05 15:47:07 -05:00
Mat Scales
1c47a0e1a0 Release 1.2.8: Update the changelog and bump the version 2013-12-02 13:42:25 +00:00
Mat Scales
4d3e2f4fd3 Merge pull request #983 from wibblymat/issue-981
A queueing system for the cmd module to limit the number of open handles (closes #981)
2013-12-02 05:11:04 -08:00
Mat Scales
a3430f7ce6 Be less specific with the dependency version 2013-12-02 12:39:41 +00:00
Mat Scales
247b7ee1ab Let's use a library instead of rolling our own solution 2013-11-28 13:09:26 +00:00
Mat Scales
58eafce947 Slightly reworked the code for readability 2013-11-27 10:06:51 +00:00
Mat Scales
ed25cf9d23 A queueing system for the cmd module to limit the number of open handles. 2013-11-26 22:26:58 +00:00
Mat Scales
8992c74d32 Merge pull request #980 from wibblymat/infinite-dependency-recursion
Fixed an infinite recursion bug
2013-11-26 08:43:53 -08:00
Sindre Sorhus
01f56c515b readme - trying out SourceGraph hit counter - 90' is back!
This is what we got since GitHub won't share stats with us :/
2013-11-26 09:15:29 +01:00
André Cruz
864d3a3b2f Fixed a bug caused by recently throw added in semver
The newest semver module version throws when calling `toComparators` with an invalid semver range (`invalid comparator`).
2013-11-25 17:56:31 +00:00
Mat Scales
d68d2a4b18 Fixed an infinite recursion bug 2013-11-22 13:40:04 +00:00
André Cruz
d24da615b3 Merge pull request #976 from hidinginabunker/allowroot_env_conf
Added config fallback for allow-root check
2013-11-20 17:57:37 -08:00
Gabriel Herrera
84c693ad22 Added config fallback for allow-root check 2013-11-19 16:43:15 -05:00
Sindre Sorhus
747cbc1442 Add Bower logo to readme 2013-11-07 13:46:54 +01:00
Mat Scales
722cc54338 Merge pull request #944 from gergelyke/master
bower's cache added to readme
2013-11-05 09:10:59 -08:00
Gergely Nemeth
21abf0d215 typo fix in --offline 2013-10-29 09:34:09 +01:00
Gergely Nemeth
83cb1b05d9 typo fix in --offline 2013-10-29 09:33:30 +01:00
Gergely Nemeth
83d4c04646 typo fix in --offline 2013-10-29 09:30:51 +01:00
Gergely Nemeth
0a7f601dec -offline added 2013-10-29 09:29:51 +01:00
Sindre Sorhus
1d079c0879 remove unicode space symbol thingy 2013-10-25 18:31:59 +02:00
Sindre Sorhus
3df4b5b28f readme - add link to blog post explaining why checking in dependencies is recommended
Lots of people ask about this and doesn't bother to Google it...
2013-10-23 13:39:15 +02:00
Mat Scales
14dc86e9ea Add the 'moduleType' property to bower init 2013-10-21 15:14:08 +01:00
André Cruz
b64d8bfab3 Merge pull request #912 from beardtwizzle/master
Allow for existence of query-string parameters in URL
2013-10-17 19:44:24 -07:00
André Cruz
b41c8ad364 Merge pull request #926 from bower/improve-list-message-for-missing-packages
Improve `list` message for missing packages
2013-10-17 19:39:38 -07:00
Nicolas Gallagher
03c6709254 Improve list message for missing packages
'missing' -> 'not installed'
2013-10-16 13:03:25 -07:00
Paul Irish
7a6fb46841 Merge pull request #905 from eddiemonge/patch-1
Docs: Move registering package
2013-10-14 17:56:18 -07:00
Paul Irish
5b43407388 Merge pull request #906 from eddiemonge/patch-2
Docs: Proposal: Move warnings sections
2013-10-14 17:55:24 -07:00
Paul Irish
0e05301588 Merge pull request #908 from eddiemonge/patch-4
Docs: Code isnt code when it isnt code
2013-10-14 17:52:51 -07:00
Jonathan Mahoney
7a501164a9 Test for b632984 (Allow for existence of query-string parameters in URL) 2013-10-14 09:02:18 +01:00
Jonathan Mahoney
b632984874 Allow for existence of query-string parameters in URL 2013-10-09 13:00:50 +01:00
Eddie Monge Jr.
94407079bf Code isnt code when it isnt code
Moved code that should be titles to titles instead of in the code
2013-10-07 14:58:32 -07:00
Eddie Monge
57ab7ee6f2 Proposal: Move warnings sections
Installation instructions should be prominent with
warnings following as they are mostly edge cases.
2013-10-07 14:46:08 -07:00
Eddie Monge Jr.
62cf372d81 Move registering package
Seems like it should be with defining a package instead of usage
2013-10-07 14:35:24 -07:00
Mat Scales
d3991ee672 Close GH-900: Merge all the decompress-zip work and add small improvements. Fixes #873, Fixes #896 2013-10-07 00:29:29 +01:00
André Cruz
b4603069bf Merge pull request #901 from bower/absolute-path-slash
Fix absolute paths ending with / not going through the FsResolver, fixes #898
2013-10-04 05:01:38 -07:00
André Cruz
60c522a696 Fix absolute paths ending with / not going through the FsResolver, fixes #898. 2013-10-04 12:38:08 +01:00
André Cruz
99b0c8974b Merge branch 'master' of github.com:bower/bower into link-install-deps 2013-10-04 12:10:49 +01:00
André Cruz
6ae2c8497f Merge branches 'master' and 'master' of github.com:bower/bower 2013-10-04 12:09:36 +01:00
André Cruz
01b7509e61 Little fix when synching up dependencies. 2013-10-04 11:25:06 +01:00
André Cruz
194a09f21d Some fixes. 2013-09-30 18:25:54 +01:00
André Cruz
3207365e3a Typo. 2013-09-30 02:44:44 +01:00
André Cruz
b87b8ea931 Install linked package dependencies, closes #673.
Also add a "unlink" alias to "uninstall".
2013-09-30 02:33:06 +01:00
André Cruz
58d21ddc15 Add NOTE. 2013-09-29 23:09:00 +01:00
André Cruz
80c1fee406 Bump version. 2013-09-29 23:04:39 +01:00
André Cruz
a595c1ceaf Update change log. 2013-09-29 23:04:11 +01:00
André Cruz
cc9c440614 Give priority to mime type header, except for octet-stream. 2013-09-29 22:50:06 +01:00
Sven Lito
37425713ba Merge pull request #889 from bower/sync-up-dissected
Sync up dependencies and dependants after dissection, fixes #879.
2013-09-28 08:34:10 -07:00
Sven Lito
8e76d67bdf Merge pull request #888 from bower/fix-872
Fix bower not looking tags when target looks like a semver, fixes #872.
2013-09-28 08:33:16 -07:00
André Cruz
99cb553cbd Sync up dependencies and dependants after dissection, fixes #879. 2013-09-28 14:34:33 +01:00
André Cruz
dcc396234c Fix bower not looking tags when target looks like a semver, fixes #872. 2013-09-28 12:08:46 +01:00
Sven Lito
715de1b18c Merge pull request #887 from bower/init-ignore-dir
Add configured directory in bowerrc to the ignores in bower init.
2013-09-28 02:14:16 -07:00
André Cruz
7c2384cb94 Improve last commit. 2013-09-28 01:51:22 +01:00
André Cruz
5b1dd15749 Replace dir separators with dashes before storing into the resolve cache. 2013-09-28 01:34:49 +01:00
André Cruz
1f8417041d Add configured directory in bowerrc to the ignores in bower init. 2013-09-28 00:31:37 +01:00
André Cruz
7ecd70a90b Fix bower update not correctly catching up branch commits. 2013-09-10 18:07:26 +01:00
André Cruz
6d85ae8e8e Upgrade mout. 2013-09-09 00:52:36 +01:00
André Cruz
f405877a16 Oops. 2013-09-09 00:29:43 +01:00
André Cruz
d22c6f9d66 Only convert & warn if necessary. 2013-09-09 00:28:35 +01:00
André Cruz
c87f0495c0 Typo. 2013-09-07 17:05:39 +01:00
André Cruz
15fbf44948 Close GH-862: Store resolution when --force-latest is specified, fixes #861. 2013-09-07 15:52:23 +01:00
André Cruz
10963bd808 Merge pull request #850 from bower/promise-swallow
Do not swallow sync errors from commands event handlers, fixes #849.
2013-09-06 09:32:48 -07:00
André Cruz
0866c93573 Do not swallow sync errors from commands event handlers, fixes #849. 2013-09-04 09:58:50 +01:00
André Cruz
48e475c9f6 Add missing entry in change log. 2013-09-04 02:50:30 +01:00
André Cruz
2c5ab19374 Bump version. 2013-09-04 02:43:08 +01:00
André Cruz
9d50c31d2c Update change log. 2013-09-04 02:42:42 +01:00
André Cruz
b8b03c88e4 Close GH-844: Ensure that the whole file was downloaded.. Fixes #792, Fixes #824, Fixes #830 2013-09-01 22:25:20 +01:00
André Cruz
a65f57e0f5 Merge pull request #835 from bower/private-registry
Do not translate urls for custom registry servers, fixes #832
2013-08-29 12:10:14 -07:00
André Cruz
491027b048 Do not translate urls for custom registry servers. 2013-08-29 19:56:29 +01:00
André Cruz
5e1971a951 Merge branch 'master' of github.com:bower/bower 2013-08-29 19:55:43 +01:00
André Cruz
f64f672b20 Reuse code. 2013-08-29 09:14:16 +01:00
André Cruz
7c0a5d60ef Typo. 2013-08-28 22:50:03 +01:00
André Cruz
1f7bd9fd47 Update to latest version of request-progress, to report downloaded size even without content-length. 2013-08-28 22:38:16 +01:00
André Cruz
2a7c3eb2e6 Bump version. 2013-08-28 22:04:14 +01:00
André Cruz
32aeba2bb8 Merge branch 'master' of github.com:bower/bower 2013-08-28 22:03:29 +01:00
André Cruz
20e376c214 Update change log. 2013-08-28 22:02:28 +01:00
André Cruz
a0c1552726 Merge pull request #829 from bower/no-home
Warn if home is not set, fixes #828
2013-08-28 13:51:08 -07:00
André Cruz
46783e32d5 Warn if home is not set. 2013-08-28 21:45:39 +01:00
André Cruz
2eafac3ff6 Bump deps. 2013-08-28 21:32:48 +01:00
André Cruz
9bd85b7023 Remove unneeded comment. 2013-08-28 21:28:50 +01:00
André Cruz
eb9dcce30b Close GH-819: Fix resolutions not working properly when resolving to branches, closes #818.. Fixes #818 2013-08-28 21:22:18 +01:00
André Cruz
742ef58d89 Merge pull request #825 from stephenplusplus/master
(words) Fix post-register wording.
2013-08-27 15:19:03 -07:00
Stephen Sawchuk
c98bef5fc7 (words) Fix post-register wording. 2013-08-27 16:33:56 -04:00
André Cruz
23044b3758 Fix throttling issue that causes progress to fire after end. 2013-08-26 14:09:17 +01:00
André Cruz
107ce78b12 Doc typo. 2013-08-23 23:23:03 +01:00
André Cruz
5f81b8f0c1 Bump version. 2013-08-23 23:12:32 +01:00
André Cruz
83d75e704b Update change log. 2013-08-23 23:12:16 +01:00
André Cruz
a860e3eaf7 Merge pull request #816 from bower/ignore-nested
Fix ignore of nested folders, fixes #814.
2013-08-23 15:09:11 -07:00
André Cruz
3d84bcc7ad Fix ignore of nested folders, fixes #814. 2013-08-23 13:57:15 +01:00
André Cruz
c31504220d Small typo. 2013-08-22 19:19:11 +01:00
André Cruz
f8501cf7f4 Improve changelog. 2013-08-22 19:14:38 +01:00
André Cruz
81ef5a1dd2 Bump version. 2013-08-22 19:07:34 +01:00
André Cruz
29ec793386 Update changelog. 2013-08-22 19:07:13 +01:00
André Cruz
187457df13 Fix interactive not being set correctly on node 0.8.x, fixes #802. 2013-08-22 18:52:42 +01:00
André Cruz
2eea214a42 Fix linked dependencies in windows, fixes #813. 2013-08-22 18:45:34 +01:00
André Cruz
639c2290b7 Do not list versions if a property was requested, #684. 2013-08-22 18:27:41 +01:00
André Cruz
3044dcd3af Merge branch 'master' of github.com:bower/bower 2013-08-22 13:07:22 +01:00
André Cruz
3d64b16227 Fix extraneous being incorrectly set for saved dev dependencies in some cases. 2013-08-22 13:07:03 +01:00
André Cruz
7bd22a5103 Fix update notice when using --json. 2013-08-21 19:30:47 +01:00
André Cruz
dcdd8cb3db Typo. 2013-08-20 23:39:43 +01:00
André Cruz
e93c5f8265 Forgot date. 2013-08-20 23:34:41 +01:00
André Cruz
77be4520dc Bump version. 2013-08-20 23:32:58 +01:00
André Cruz
cf76bcb2aa Update changelog. 2013-08-20 23:31:33 +01:00
André Cruz
b272a61eac Merge branch 'master' of github.com:bower/bower 2013-08-20 22:58:06 +01:00
André Cruz
4ed5f278f8 Ignore remote tags. 2013-08-20 22:57:42 +01:00
André Cruz
6a16850960 Merge pull request #805 from tschaub/shallow-failure
Try without shallow clone if fatal error
2013-08-20 13:50:35 -07:00
Tim Schaub
0eac8bb96e Try without shallow clone if fatal error
$ git --version
    git version 1.7.12.4

    $ git clone https://code.google.com/p/closure-library -b master --progress . --depth 1
    Cloning into '.'...
    fatal: expected shallow/unshallow, got Error: internal server error
2013-08-20 12:43:01 -04:00
André Cruz
46ae1e759c Improve non-interactive shell error message. 2013-08-20 09:26:01 +01:00
André Cruz
e0c1308c2a Update deps. 2013-08-20 01:04:26 +01:00
André Cruz
430dc04b09 Add another missing default value for confirm prompt. 2013-08-20 00:53:10 +01:00
André Cruz
39209f3bda Standardise prompting behaviour between standard/json renderers. 2013-08-20 00:51:01 +01:00
André Cruz
0eaa43c05d Missing default answer for the "Looks good?" question. 2013-08-20 00:50:21 +01:00
André Cruz
5eadd36107 Merge branch 'master' of github.com:bower/bower 2013-08-19 19:38:42 +01:00
André Cruz
c6a370309d Project install now accepts decomposed endpoints instead of endpoints. 2013-08-19 19:38:30 +01:00
André Cruz
13e2514830 Simplify. 2013-08-19 19:26:44 +01:00
André Cruz
0b8be97fb5 Typo. 2013-08-19 19:24:11 +01:00
119 changed files with 7910 additions and 1508 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,6 +1,25 @@
language: node_js
node_js:
- "0.10"
- "0.8"
before_script:
- npm install grunt-cli -g
env:
- NODE_VERSION=0.10
- NODE_VERSION=0.11
install:
- test $TRAVIS_OS_NAME = "osx" && brew install nvm && source $(brew --prefix nvm)/nvm.sh || test $TRAVIS_OS_NAME = "linux"
- nvm install $NODE_VERSION
- node --version
- npm --version
- git --version
- svn --version | head -n 1
- npm install -g grunt-cli
- npm install
os:
- osx
- linux
matrix:
allow_failures:
- os: osx
- env: "NODE_VERSION=0.11"
script:
- grunt travis

View File

@@ -1,6 +1,177 @@
# Changelog
## 1.2.0 - 2013-08-19
## 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.
## 1.3.0 - 2014-03-10
- **Removed support for node 0.8.** It may still work but we will no longer fix bugs for older versions of node.
- Add **Bower Insight** for opt-in analytics integration to help improve tool and gain insight on community trends
- Old overview of [Insight](https://github.com/yeoman/yeoman/wiki/Insight), [Issue #260](https://github.com/bower/bower/issues/260)
- Reporting to GA. Public Dashboard is in progress.
- [Turn off interactive mode](https://github.com/bower/bower/issues/1162) if you run Bower in a CI environment
- Add `moduleType` property to bower init ([#934](https://github.com/bower/bower/pull/934))
- Fix prune command to log only after cleanup is completed ([#1023](https://github.com/bower/bower/issues/1023))
- Fix git resolver to ignore pre-release versions ([#1017](https://github.com/bower/bower/issues/1017))
- Fix shorthand flag for `save` option on `uninstall` command ([#1031](https://github.com/bower/bower/pull/1031))
- Add `bower version` command ([#961](https://github.com/bower/bower/pull/961))
- Add .bowerrc option to use `--save` by default when using `bower install` command ([#1074](https://github.com/bower/bower/pull/1074))
- Fix git resolver caching ([#1083](https://github.com/bower/bower/issues/1083))
- Fix reading versions from cache directory ([#1076](https://github.com/bower/bower/pull/1076))
- Add svn support ([#1055](https://github.com/bower/bower/pull/1055))
- Allow circular dependencies to be installed ([#1104](https://github.com/bower/bower/pull/1104))
- Add scripts/hooks support ([#718](https://github.com/bower/bower/pull/718))
_NOTE_: It's advisable that users use `--config.interactive=false` on automated scripts.
## 1.2.8 - 2013-12-02
- Fix absolute paths ending with / not going through the FsResolver, ([#898](https://github.com/bower/bower/issues/898))
- Allow query string parameters in package URLs
- Swapped 'unzip' module for 'decompress-zip', and some other small unzipping fixes([#873](https://github.com/bower/bower/issues/873), [#896](https://github.com/bower/bower/issues/896))
- Allow the root-check to be overridden when calling bower programmatically.
- Fixed some bugs relating to packages with a very large dependency tree
- Fix a bug caused by a recent change to semver
## 1.2.7 - 2013-09-29
- Do not swallow sync errors when using the programmatic API ([#849](https://github.com/bower/bower/issues/849))
- Fix resolutions not being saved if `--force-latest` is specified ([#861](https://github.com/bower/bower/issues/861))
- Fix `bower register` warning about URL conversion, even if no conversion occurred
- Fix `bower update` not correctly catching up branch commits
- Add configured directory in `.bowerrc` to the ignores in `bower init` ([#854](https://github.com/bower/bower/issues/854))
- Fix some case sensitive issues with data stored in registry cache (e.g.: jquery/jQuery, [#859](https://github.com/bower/bower/issues/859))
- Fix bower not checking out a tag if it looks like a semver (e.g.: 1.0, [#872](https://github.com/bower/bower/issues/872))
- Fix install & update commands printing the wrong versions in some cases ([#879](https://github.com/bower/bower/issues/879))
- Give priority to mime type headers when deciding if a package need to be extracted, except if it is `octet-stream`
_NOTE_: It's advisable that users run `bower cache clean`.
## 1.2.6 - 2013-09-04
- Bower now reports download progress even for servers that do not respond with `content-length` header.
- Do not translate endpoints when registering a package to a private registry server ([#832](https://github.com/bower/bower/issues/832))
- Detect corrupted downloads by comparing downloaded bytes with `content-length` header if possible; this fixes Bower silently failing on unstable networks ([#824](https://github.com/bower/bower/issues/824) and [#792](https://github.com/bower/bower/issues/792))
- Fix quotes in fields causing Bower to crash in the `init` command ([#841](https://github.com/bower/bower/issues/841))
## 1.2.5 - 2013-08-28
- Fix persistent conflict resolutions not working correctly for branches ([#818](https://github.com/bower/bower/issues/818))
- Fix Bower failing to run if HOME is not set ([#826](https://github.com/bower/bower/issues/826))
- Bower now prints a warning if HOME is not set ([#827](https://github.com/bower/bower/issues/827))
- Fix progress message being fired after completion of long running `git clone` commands
- Other minor improvements
## 1.2.4 - 2013-08-23
- Fix ignored nested folders not being correctly handled in some cases ([#814](https://github.com/bower/bower/issues/814))
## 1.2.3 - 2013-08-22
- Fix read of environment variables that map to config properties with dashes and also support nested ones ([#8@bower-config](https://github.com/bower/config/issues/8))
- Fix `bower info <package> <property>` printing the available versions (it shouldn't!)
- Fix interactive shell not being correctly detected in node `0.8.x` ([#802](https://github.com/bower/bower/issues/802))
- Fix `extraneous` flag in the `list` command being incorrectly set for saved dev dependencies in some cases
- Fix linked dependencies not being read in `bower list` on Windows ([#813](https://github.com/bower/bower/issues/813))
- Fix update notice not working with `--json`
## 1.2.2 - 2013-08-20
- Standardize prompt behaviour with and without `--json`
- Improve detection of `git` servers that do not support shallow clones ([#805](https://github.com/bower/bower/issues/805))
- Ignore remote tags (tags ending with ^{})
- Fix bower not saving the correct endpoint in some edge cases ([#806](https://github.com/bower/bower/issues/806))
## 1.2.1 - 2013-08-19
- Fix bower throwing on non-semver targets ([#800](https://github.com/bower/bower/issues/800))
@@ -23,7 +194,7 @@
- Ignore `component.json` if it looks like a component(1) file ([#556](https://github.com/bower/bower/issues/556))
- Fix multi-user usage on bower when it creates temporary directories to hold some files
- Fix prompting causing an invalid JSON output when running commands with `--json`
- When running Bower commands programmatically, prompting is now disabled by default (see the updated progammatic [usage](https://github.com/bower/bower#programmatic-api) for more info)
- When running Bower commands programmatically, prompting is now disabled by default (see the updated programmatic [usage](https://github.com/bower/bower#programmatic-api) for more info)
- Other minor improvements and fixes
Fix for `#788` requires installed components to be re-installed.
@@ -85,7 +256,7 @@ Fix for `#788` requires installed components to be re-installed.
- Fix `Bower` not working when calling `.bat`/`.cmd` commands on Windows; it affected people using `Git portable` ([#626](https://github.com/bower/bower/issues/626))
- Fix `bower list --paths` not resolving all files to absolute paths when the `main` property contained multiple files ([660](https://github.com/bower/bower/issues/660))
- Fix `Bower` renaming `bower.json` and `component.json` files to `index.json` when it was the only file in the folder ([#674](https://github.com/bower/bower/issues/674))
- Ignore symlinks when copying/extracting since they are not portable, specially accross different hard-drives ([#665](https://github.com/bower/bower/issues/665))
- Ignore symlinks when copying/extracting since they are not portable, specially across different hard-drives ([#665](https://github.com/bower/bower/issues/665))
- Local file/dir endpoints are now exclusively referenced by an absolute path or relative path starting with `.` ([#666](https://github.com/bower/bower/issues/666))
- Linked packages `bower.json` files are now parsed, making `bower list` account linked packages dependencies ([#659](https://github.com/bower/bower/issues/659))
- Bower now fails to run with sudo unless `--allow-root` is passed ([#498](https://github.com/bower/bower/issues/498))
@@ -98,7 +269,7 @@ Fix for `#788` requires installed components to be re-installed.
## 1.0.0 - 2013-07-23
Total rewrite of bower.
The list bellow highlights the most important stuff.
The list bellow highlights the most important stuff.
For a complete list of changes that this rewrite and release brings please read: https://github.com/bower/bower/wiki/Rewrite-state
@@ -121,7 +292,7 @@ Non-backwards compatible changes:
- `--map` and `--sources` from the list command were removed, use `--json` instead
- Programmatic usage changed, specially the commands interface
Users upgrading from `bower-canary` and `bower@~0.x.x` should do a `bower cache clean`.
Users upgrading from `bower-canary` and `bower@~0.x.x` should do a `bower cache clean`.
Additionally you may remove the `~/.bower` folder manually since it's no longer used.
On Windows the folder is located in `AppData/bower`.
@@ -249,7 +420,7 @@ _NOTE_: The `components` folder will still be used if already created, making it
## 0.6.1 - 2012-11-22
- Fix uninstall when the project component.json has no deps saved ([#153](https://github.com/bower/bower/issues/153))
- Fix uncaught errors when using file writter (they are now caught and reported)
- Fix uncaught errors when using file writer (they are now caught and reported)
- Fix temporary directories not being deleted when an exception occurs ([#153](https://github.com/bower/bower/issues/140))
## 0.6.0 - 2012-11-21

View File

@@ -1,7 +1,29 @@
# Contributing to Bower
Please take a moment to review this document in order to make the contribution
process easy and effective for everyone involved.
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)
## 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,

View File

@@ -1,17 +1,30 @@
'use strict';
module.exports = function (grunt) {
require('load-grunt-tasks')(grunt);
grunt.initConfig({
jshint: {
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: '10000'
},
full: {
src: ['test/test.js']
},
full: { src: ['test/test.js'] },
short: {
options: {
reporter: 'dot'
@@ -21,13 +34,16 @@ module.exports = function (grunt) {
},
exec: {
assets: {
command: 'node test/packages.js'
command: 'node test/packages.js && node test/packages-svn.js'
},
'assets-force': {
command: 'node test/packages.js --force'
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: {
@@ -36,13 +52,9 @@ module.exports = function (grunt) {
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-exec');
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');
};

21
HOOKS.md Normal file
View File

@@ -0,0 +1,21 @@
# Install and Uninstall Hooks
Bower provides 3 separate hooks that can be used to trigger other automated tools during Bower usage. Importantly, these hooks are intended to allow external tools to help wire up the newly installed components into the parent project and other similar tasks. These hooks are not intended to provide a post-installation build step for component authors. As such, the configuration for these hooks is provided in the `.bowerrc` file in the parent project's directory.
## Configuring
In `.bowerrc` do:
```js
{
"scripts": {
"preinstall": "<your command here>",
"postinstall": "<your command here>",
"preuninstall": "<your command here>"
}
}
```
The value of each script hook may contain a % character. When your script is called, the % will be replaced with a space-separated list of components being installed or uninstalled.
Your script will also include an environment variable `BOWER_PID` containing the PID of the parent Bower process that triggered the script. This can be used to verify that a `preinstall` and `postinstall` steps are part of the same Bower process.

View File

@@ -1,4 +1,4 @@
Copyright (c) 2012 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

3
LOCKING.md Normal file
View File

@@ -0,0 +1,3 @@
This is stub for feature mentioned in #505.
You can send PR against this branch (feature/shrinkwrap).

361
README.md
View File

@@ -1,265 +1,85 @@
# BOWER [![Build Status](https://secure.travis-ci.org/bower/bower.png?branch=master)](http://travis-ci.org/bower/bower)
# Bower
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.
[![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)
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.).
<img align="right" height="300" src="http://bower.io/img/bower-logo.png">
[View all packages available through Bower's registry](http://sindresorhus.com/bower-components/).
> 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.
## Installing Bower
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.).
Bower depends on [Node](http://nodejs.org/) and [npm](http://npmjs.org/). It's
installed globally using npm:
**View complete docs on [bower.io](http://bower.io)**
```
npm install -g bower
[View all packages available through Bower's registry](http://bower.io/search/).
## Install
```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.
#### Warning
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
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.
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
# install a package and add it to bower.json
$ bower install <package> --save
# install specific version of a package and add it to bower.json
$ bower install <package>#<version> --save
```
# Using the dependencies listed in the current directory's bower.json
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 remote Git endpoint, e.g., `git://github.com/someone/some-package.git`. Can be
public or private. ‡
* A local endpoint, i.e., a folder that's a Git repository. ‡
* 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 use ranges to specify a range of versions.
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.
### 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/index.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.
### 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).
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>
```
### 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
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.
### Windows users
To use Bower on Windows, you must install
[msysgit](http://msysgit.github.io/) correctly. Be sure to check the
option shown below:
![msysgit](http://f.cl.ly/items/2V2O3i1p3R2F1r2v0a12/mysgit.png)
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.
## 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.
## 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` defines several options:
* `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.
* `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",
"version": "1.0.0",
"main": "path/to/main.css",
"ignore": [
".jshintrc",
"**/*.txt"
],
"dependencies": {
"<name>": "<version>",
"<name>": "<folder>",
"<name>": "<package>"
},
"devDependencies": {
"<test-framework-name>": "<version>"
}
}
```
## 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 path-to-name mapping:
```json
{
"backbone": "bower_components/backbone/index.js",
"jquery": "bower_components/jquery/index.js",
"underscore": "bower_components/underscore/index.js"
}
```
Alternatively, every command supports the `--json` option that makes bower
output JSON. Command result is outputted to `stdout` and error/logs to
`stderr`.
## Programmatic API
Bower provides a powerful, programmatic API. All commands can be accessed
through the `bower.commands` object.
```js
var bower = require('bower');
bower.commands
.install(['jquery'], { save: true }, { /* custom config */ })
.on('end', function (installed) {
console.log(installed);
});
bower.commands
.search('jquery', {})
.on('end', function (results) {
console.log(results);
});
```
Commands emit four types of events: `log`, `prompt`, `end`, `error`.
* `log` is emitted to report the state/progress of the command.
* `prompt` is emitted whenever the user needs to be prompted.
* `error` will only be emitted if something goes wrong.
* `end` is emitted when the command successfully ends.
For a better of idea how this works, you may want to check out [our bin
file](https://github.com/bower/bower/blob/master/bin/bower).
When using bower programmatically, prompting is disabled by default. Though you can enable it when calling commands with `interactive: true` in the config.
This requires you to listen for the `prompt` event and handle the prompting yourself. The easiest way is to use the [inquirer](https://npmjs.org/package/inquirer) npm module like so:
```js
var inquirer = require('inquirer');
bower.commands
.install(['jquery'], { save: true }, { interactive: true })
// ..
.on('prompt', function (prompts, callback) {
inquirer.prompt(prompts, callback);
});
```
Bower can be configured using JSON in a `.bowerrc` file. Read over available options at [bower.io/docs/config](http://bower.io/docs/config).
## Completion (experimental)
@@ -272,37 +92,21 @@ 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
```sh
$ bower completion >> ~/.bash_profile
```
## A note for Windows users
To use Bower on Windows, you must install
[msysgit](http://code.google.com/p/msysgit/) correctly. Be sure to check the
option shown below:
![msysgit](http://f.cl.ly/items/2V2O3i1p3R2F1r2v0a12/mysgit.png)
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.
## 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 of all kinds from anyone. Please take a moment to
review the [guidelines for contributing](CONTRIBUTING.md).
* [Bug reports](CONTRIBUTING.md#bugs)
@@ -310,48 +114,33 @@ review the [guidelines for contributing](CONTRIBUTING.md).
* [Pull requests](CONTRIBUTING.md#pull-requests)
## Authors
Note that on Windows for tests to pass you need to configure Git before cloning:
```
git config --global core.autocrlf input
```
## Bower Team
Bower is made by lots of people across the globe, contributions large and small. Our thanks to everyone who has played a part.
### Core team
* [@satazor](https://github.com/satazor)
* [@wibblymat](https://github.com/wibblymat)
* [@paulirish](https://github.com/paulirish)
* [@benschwarz](https://github.com/benschwarz)
* [@svnlto](https://github.com/svnlto)
* [@sheerun](https://github.com/sheerun)
### Bower Alumni
* [@fat](https://github.com/fat)
* [@maccman](https://github.com/maccman)
* [@satazor](https://github.com/satazor)
Thanks for assistance and contributions:
[@addyosmani](https://github.com/addyosmani),
[@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),
[@marcelombc](https://github.com/marcelombc),
[@marcooliveira](https://github.com/marcooliveira),
[@mklabs](https://github.com/mklabs),
[@MrDHat](https://github.com/MrDHat),
[@necolas](https://github.com/necolas),
[@paulirish](https://github.com/paulirish),
[@richo](https://github.com/richo),
[@rvagg](https://github.com/rvagg),
[@sindresorhus](https://github.com/sindresorhus),
[@SlexAxton](https://github.com/SlexAxton),
[@sstephenson](https://github.com/sstephenson),
[@svnlto](https://github.com/svnlto),
[@tomdale](https://github.com/tomdale),
[@uzquiano](https://github.com/uzquiano),
[@visionmedia](https://github.com/visionmedia),
[@wagenet](https://github.com/wagenet),
[@wibblymat](https://github.com/wibblymat),
[@wycats](https://github.com/wycats)
## License
Copyright 2012 Twitter, Inc.
Copyright (c) 2015 Twitter and other contributors
Licensed under the MIT License

40
appveyor.yml Normal file
View File

@@ -0,0 +1,40 @@
# 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 subversion
- choco install svn
# 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}"

137
bin/bower
View File

@@ -1,25 +1,24 @@
#!/usr/bin/env node
'use strict';
process.title = 'bower';
process.bin = process.title = 'bower';
var path = require('path');
var Q = require('q');
var mout = require('mout');
var updateNotifier = require('update-notifier');
var Logger = require('bower-logger');
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 emitter;
var notifier;
var logger;
var levels = Logger.LEVELS;
options = cli.readOptions({
@@ -42,6 +41,7 @@ if (bower.config.silent) {
loglevel = levels.error;
} else if (bower.config.verbose) {
loglevel = -Infinity;
Q.longStackSupport = true;
} else if (bower.config.quiet) {
loglevel = levels.warn;
} else {
@@ -58,7 +58,7 @@ while (options.argv.remain.length) {
break;
}
command = command.replace(/s/g, '.');
command = command.replace(/\s/g, '.');
// Direct lookup
if (mout.object.has(bower.commands, command)) {
@@ -68,66 +68,75 @@ while (options.argv.remain.length) {
options.argv.remain.pop();
}
// Execute the command
commandFunc = command && mout.object.get(bower.commands, command);
command = command && command.replace(/\./g, ' ');
// Ask for Insights on first run.
analytics.setup(bower.config).then(function () {
// Execute the command
commandFunc = command && mout.object.get(bower.commands, command);
command = command && command.replace(/\./g, ' ');
// If no command was specified, show bower help
// Do the same if the command is unknown
if (!commandFunc) {
emitter = bower.commands.help();
command = 'help';
// If the user requested help, show the command's help
// Do the same if the actual command is a group of other commands (e.g.: cache)
} else if (options.help || !commandFunc.line) {
emitter = bower.commands.help(command);
command = 'help';
// Call the line method
} else {
emitter = commandFunc.line(process.argv);
// If the method failed to interpret the process arguments
// show the command help
if (!emitter) {
emitter = bower.commands.help(command);
// If no command was specified, show bower help
// Do the same if the command is unknown
if (!commandFunc) {
logger = bower.commands.help();
command = 'help';
}
}
// If the user requested help, show the command's help
// Do the same if the actual command is a group of other commands (e.g.: cache)
} else if (options.help || !commandFunc.line) {
logger = bower.commands.help(command);
command = 'help';
// Call the line method
} else {
logger = commandFunc.line(process.argv);
// Get the renderer and configure it with the executed command
renderer = cli.getRenderer(command, emitter.json, bower.config);
emitter
.on('end', function (data) {
if (!bower.config.silent) {
renderer.end(data);
}
})
.on('error', function (err) {
if (levels.error >= loglevel) {
renderer.error(err);
// If the method failed to interpret the process arguments
// show the command help
if (!logger) {
logger = bower.commands.help(command);
command = 'help';
}
}
process.exit(1);
})
.on('log', function (log) {
if (levels[log.level] >= loglevel) {
renderer.log(log);
}
})
.on('prompt', function (prompt, callback) {
renderer.prompt(prompt)
.then(function (answer) {
callback(answer);
// Get the renderer and configure it with the executed command
renderer = cli.getRenderer(command, logger.json, bower.config);
logger
.on('end', function (data) {
if (!bower.config.silent && !bower.config.quiet) {
renderer.end(data);
}
})
.on('error', function (err) {
if (levels.error >= loglevel) {
renderer.error(err);
}
process.exit(1);
})
.on('log', function (log) {
if (levels[log.level] >= loglevel) {
renderer.log(log);
}
})
.on('prompt', function (prompt, callback) {
renderer.prompt(prompt)
.then(function (answer) {
callback(answer);
});
});
});
// Check for newer version of Bower
notifier = updateNotifier({
packageName: pkg.name,
packageVersion: pkg.version
});
// Warn if HOME is not SET
if (!userHome) {
logger.warn('no-home', 'HOME environment variable not set. User config will not be loaded.');
}
if (notifier.update && levels.info >= loglevel) {
renderer.updateNotice(notifier.update);
}
if (bower.config.interactive) {
var updateNotifier = require('update-notifier');
// Check for newer version of Bower
var notifier = updateNotifier({pkg: pkg});
if (notifier.update && levels.info >= loglevel) {
notifier.notify();
}
}
});

View File

@@ -3,20 +3,17 @@ var path = require('path');
var mout = require('mout');
var Q = require('q');
var rimraf = require('rimraf');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var PackageRepository = require('../../core/PackageRepository');
var semver = require('../../util/semver');
var cli = require('../../util/cli');
var defaultConfig = require('../../config');
function clean(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,19 +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) {
logger.emit('end', entries);
})
.fail(function (error) {
logger.emit('error', error);
return entries;
});
return logger;
}
function clearPackages(decEndpoints, config, logger) {
@@ -179,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
@@ -27,28 +24,20 @@ function list(packages, options, config) {
});
}
logger.emit('end', entries);
})
.fail(function (error) {
logger.emit('error', error);
return entries;
});
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 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
@@ -31,13 +27,13 @@ function home(name, config) {
} else {
decEndpoint = endpointParser.decompose(name);
promise = project.getPackageRepository().fetch(decEndpoint)
.spread(function (canonicalDir, pkgMeta) {
.spread(function (canonicalDir, pkgMeta) {
return pkgMeta;
});
}
// Get homepage and open it
promise.then(function (pkgMeta) {
return promise.then(function (pkgMeta) {
var homepage = pkgMeta.homepage;
if (!homepage) {
@@ -45,30 +41,18 @@ function home(name, config) {
}
open(homepage);
logger.emit('end', homepage);
})
.fail(function (error) {
logger.emit('error', error);
return homepage;
});
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,17 +1,76 @@
var Q = require('q');
var Logger = require('bower-logger');
/**
* Require commands only when called.
*
* Running `commandFactory(id)` is equivalent to `require(id)`. Both calls return
* a command function. The difference is that `cmd = commandFactory()` and `cmd()`
* return as soon as possible and load and execute the command asynchronously.
*/
function commandFactory(id) {
if (process.env.STRICT_REQUIRE) {
require(id);
}
function command() {
var commandArgs = [].slice.call(arguments);
return withLogger(function (logger) {
commandArgs.unshift(logger);
return require(id).apply(undefined, commandArgs);
});
}
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 () {
var args = [].slice.call(arguments);
args.unshift('end');
logger.emit.apply(logger, args);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
command.line = runFromArgv;
return command;
}
module.exports = {
cache: require('./cache'),
completion: require('./completion'),
help: require('./help'),
home: require('./home'),
info: require('./info'),
init: require('./init'),
install: require('./install'),
link: require('./link'),
list: require('./list'),
lookup: require('./lookup'),
prune: require('./prune'),
register: require('./register'),
search: require('./search'),
update: require('./update'),
uninstall: require('./uninstall')
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'),
lookup: commandFactory('./lookup'),
prune: commandFactory('./prune'),
register: commandFactory('./register'),
search: commandFactory('./search'),
update: commandFactory('./update'),
uninstall: commandFactory('./uninstall'),
version: commandFactory('./version')
};

View File

@@ -1,41 +1,41 @@
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 logger = new Logger();
var tracker;
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 === '*' ? repository.versions(decEndpoint.source) : null
decEndpoint.target === '*' && !property ? repository.versions(decEndpoint.source) : null
])
.spread(function (pkgMeta, versions) {
if (versions) {
return logger.emit('end', {
return {
name: decEndpoint.source,
versions: versions,
latest: pkgMeta
});
};
}
logger.emit('end', pkgMeta);
})
.fail(function (error) {
logger.emit('error', error);
return pkgMeta;
});
return logger;
}
function getPkgMeta(repository, decEndpoint, property) {
@@ -56,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

@@ -2,52 +2,40 @@ var mout = require('mout');
var fs = require('graceful-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 = defaultConfig(config);
// This command requires interactive to be enabled
if (!config.interactive) {
process.nextTick(function () {
logger.emit('error', createError('Register requires interactive', 'ENOINT'));
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
.then(promptUser.bind(null, logger))
// Set ignore based on the response
.spread(setIgnore)
.spread(setIgnore.bind(null, config))
// Set dependencies based on the response
.spread(setDependencies.bind(null, project))
// All done!
.spread(saveJson.bind(null, project, logger))
.then(function (json) {
logger.emit('end', json);
})
.fail(function (error) {
logger.emit('error', error);
});
return logger;
.spread(saveJson.bind(null, project, logger));
}
function readJson(project, logger) {
@@ -74,7 +62,8 @@ function saveJson(project, logger, json) {
// Confirm the json with the user
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message: 'Looks good?'
message: 'Looks good?',
default: true
})
.then(function (good) {
if (!good) {
@@ -199,6 +188,12 @@ function promptUser(logger, json) {
'default': json.main,
'type': 'input'
},
{
'name': 'moduleType',
'message': 'what types of modules does this package expose?',
'type': 'checkbox',
'choices': ['amd', 'es6', 'globals', 'node', 'yui']
},
{
'name': 'keywords',
'message': 'keywords',
@@ -249,6 +244,7 @@ function promptUser(logger, json) {
json.version = answers.version;
json.description = answers.description;
json.main = answers.main;
json.moduleType = answers.moduleType;
json.keywords = toArray(answers.keywords);
json.authors = toArray(answers.authors, ',');
json.license = answers.license;
@@ -275,12 +271,13 @@ function toArray(value, splitter) {
return arr.length ? arr : null;
}
function setIgnore(json, answers) {
function setIgnore(config, json, answers) {
if (answers.ignore) {
json.ignore = mout.array.combine(json.ignore || [], [
'**/.*',
'node_modules',
'bower_components',
config.directory,
'test',
'tests'
]);
@@ -319,16 +316,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,51 +1,49 @@
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 logger = new Logger();
var decEndpoints;
var tracker;
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
project = new Project(config, logger);
// If endpoints is an empty array, null them
if (endpoints && !endpoints.length) {
endpoints = null;
config = defaultConfig(config);
if (options.save === undefined) {
options.save = config.defaultSave;
}
project = new Project(config, logger);
tracker = new Tracker(config);
project.install(endpoints, options)
.then(function (installed) {
logger.emit('end', installed);
})
.fail(function (error) {
logger.emit('error', error);
// Convert endpoints to decomposed endpoints
endpoints = endpoints || [];
decEndpoints = endpoints.map(function (endpoint) {
return endpointParser.decompose(endpoint);
});
tracker.trackDecomposedEndpoints('install', decEndpoints);
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 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);
@@ -27,74 +31,54 @@ function linkSelf(config) {
return createLink(src, dst);
})
.then(function () {
logger.emit('end', {
return {
src: src,
dst: dst
});
};
});
})
.fail(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;
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);
})
// Install linked package deps
.then(function () {
logger.emit('end', {
src: src,
dst: dst
});
return project.update([localName]);
})
.fail(function (error) {
logger.emit('error', error);
.then(function (installed) {
return {
src: src,
dst: dst,
installed: installed
};
});
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()
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);
@@ -57,27 +52,20 @@ function list(options, config) {
// Render paths?
if (options.paths) {
return logger.emit('end', paths(flattened));
return paths(flattened);
}
// Do not check for new versions?
if (config.offline) {
return logger.emit('end', tree);
return tree;
}
// Check for new versions
return checkVersions(project, tree, logger)
.then(function () {
logger.emit('end', tree);
return tree;
});
})
.fail(function (error) {
logger.emit('error', error);
});
logger.json = !!options.paths;
return logger;
}
function checkVersions(project, tree, logger) {
@@ -85,9 +73,11 @@ function checkVersions(project, tree, logger) {
var nodes = [];
var repository = project.getPackageRepository();
// Gather all nodes
// Gather all nodes, ignoring linked nodes
project.walkTree(tree, function (node) {
nodes.push(node);
if (!node.linked) {
nodes.push(node);
}
}, true);
if (nodes.length) {
@@ -160,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;

View File

@@ -1,54 +1,38 @@
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
logger.emit('end', !entry ? null : {
return !entry ? null : {
name: name,
url: entry && entry.url
});
})
.fail(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,40 +1,30 @@
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(config) {
function prune(logger, options, config) {
var project;
var logger = new Logger();
config = mout.object.deepFillIn(config || {}, defaultConfig);
options = options || {};
config = defaultConfig(config);
project = new Project(config, logger);
clean(project)
.then(function (removed) {
logger.emit('end', removed);
})
.fail(function (error) {
logger.emit('error', error);
});
return logger;
return clean(project, options);
}
function clean(project, removed) {
function clean(project, options, removed) {
removed = removed || {};
// Continually call clean until there is no more extraneous
// dependencies to remove
return project.getTree()
return project.getTree(options)
.spread(function (tree, flattened, extraneous) {
var names = extraneous.map(function (extra) {
return extra.endpoint.name;
});
// Uninstall extraneous
project.uninstall(names)
return project.uninstall(names, options)
.then(function (uninstalled) {
// Are we done?
if (!mout.object.size(uninstalled)) {
@@ -43,23 +33,23 @@ function clean(project, removed) {
// Not yet, recurse!
mout.object.mixIn(removed, uninstalled);
return clean(project, removed);
return clean(project, options, removed);
});
});
}
// -------------------
prune.line = function () {
return prune();
};
prune.readOptions = function (argv) {
var cli = require('../util/cli');
prune.options = function (argv) {
return cli.readOptions(argv);
};
var options = cli.readOptions({
'production': { type: Boolean, shorthand: 'p' },
}, argv);
prune.completion = function () {
// TODO:
delete options.argv;
return [options];
};
module.exports = prune;

View File

@@ -2,135 +2,114 @@ var mout = require('mout');
var Q = require('q');
var chalk = require('chalk');
var PackageRepository = require('../core/PackageRepository');
var Logger = require('bower-logger');
var cli = require('../util/cli');
var Config = require('bower-config');
var Tracker = require('../util/analytics').Tracker;
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 logger = new Logger();
var tracker;
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'));
return Q.try(function () {
// Verify name and url
if (!name || !url) {
throw createError('Usage: bower register <name> <url>', 'EINVFORMAT');
}
// Convert URLs
url = convertUrl(url, logger);
// 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);
// Ensure the URL starts with git://
// TODO: After the registry server is rewritten this might change
if (!mout.string.startsWith(url, 'git://')) {
return logger.emit('error', createError('The registry only accepts URLs starting with git://', 'EINVFORMAT'));
if (!mout.string.startsWith(url, 'git://')) {
throw createError('The registry only accepts URLs starting with git://', 'EINVFORMAT');
}
}
tracker.track('register');
// 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');
}
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');
}
// If non interactive or user forced, bypass confirmation
if (!config.interactive || force) {
return true;
}
// 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);
})
.then(function (result) {
logger.emit('end', result);
})
.fail(function (error) {
logger.emit('error', error);
// 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;
}
return logger;
// Register
registryClient = repository.getRegistryClient();
logger.action('register', url, {
name: name,
url: url
});
return Q.nfcall(registryClient.register.bind(registryClient), name, url);
});
}
function convertUrl(url, logger) {
var matches;
var pair;
var newUrl;
// Convert GitHub ssh urls
matches = url.match(/^git@github\.com:([^\/]+\/[^\/]+)\.git$/);
if (matches) {
newUrl = 'git://github.com/' + matches[1] + '.git';
logger.warn('convert', 'Converted ' + url + ' to ' + newUrl);
return 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);
}
}
// Convert GitHub https urls
matches = url.match(/^https?:\/\/github\.com\/([^\/]+\/[^\/]+)\.git$/);
if (matches) {
newUrl = 'git://github.com/' + matches[1] + '.git';
logger.warn('convert', 'Converted ' + url + ' to ' + newUrl);
return newUrl;
}
return url;
return newUrl || 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,52 +1,36 @@
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 logger = new Logger();
var tracker;
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
config.cache = config.storage.registry;
registryClient = new RegistryClient(config, logger);
tracker = new Tracker(config);
tracker.track('search', name);
// 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
.then(function (results) {
logger.emit('end', results);
})
.fail(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,19 +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 logger = new Logger();
var tracker;
options = options || {};
config = mout.object.deepFillIn(config || {}, defaultConfig);
config = defaultConfig(config);
project = new Project(config, logger);
tracker = new Tracker(config);
project.getTree()
tracker.trackNames('uninstall', names);
return project.getTree(options)
.spread(function (tree, flattened) {
// Uninstall nodes
return project.uninstall(names, options)
@@ -32,15 +38,7 @@ function uninstall(names, options, config) {
// Clean them!
return clean(project, children, uninstalled);
});
})
.then(function (uninstalled) {
logger.emit('end', uninstalled);
})
.fail(function (error) {
logger.emit('error', error);
});
return logger;
}
function clean(project, names, removed) {
@@ -49,6 +47,7 @@ function clean(project, names, removed) {
return project.getTree()
.spread(function (tree, flattened) {
var nodes = [];
var dependantsCounter = {};
// Grab the nodes of each specified name
mout.object.forOwn(flattened, function (node) {
@@ -57,9 +56,18 @@ function clean(project, names, removed) {
}
});
// Filter out those that have dependants
// Walk the down the tree, gathering dependants of the packages
project.walkTree(tree, function (node, nodeName) {
if (names.indexOf(nodeName) !== -1) {
dependantsCounter[nodeName] = dependantsCounter[nodeName] || 0;
dependantsCounter[nodeName] += node.nrDependants;
}
}, true);
// Filter out those that have no dependants
nodes = nodes.filter(function (node) {
return !node.nrDependants;
return !dependantsCounter[node.endpoint.name];
});
// Are we done?
@@ -94,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

@@ -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,33 +13,24 @@ function update(names, options, config) {
names = null;
}
project.update(names, options)
.then(function (installed) {
logger.emit('end', installed);
})
.fail(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;

127
lib/commands/version.js Normal file
View File

@@ -0,0 +1,127 @@
var semver = require('semver');
var which = require('which');
var fs = require('fs');
var path = require('path');
var Q = require('q');
var execFile = require('child_process').execFile;
var Project = require('../core/Project');
var defaultConfig = require('../config');
var createError = require('../util/createError');
function version(logger, versionArg, options, config) {
var project;
options = options || {};
config = defaultConfig(config);
project = new Project(config, 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(cwd)
.then(function (hasGit) {
doGitCommit = hasGit;
})
.then(project.getJson.bind(project))
.then(function (json) {
newVersion = getNewVersion(json.version, versionArg);
json.version = newVersion;
})
.then(project.saveJson.bind(project))
.then(function () {
if (doGitCommit) {
return gitCommitAndTag(cwd, newVersion, message);
}
})
.then(function () {
console.log('v' + newVersion);
});
}
function getNewVersion(currentVersion, versionArg) {
var newVersion = semver.valid(versionArg);
if (!newVersion) {
newVersion = semver.inc(currentVersion, versionArg);
}
if (!newVersion) {
throw createError('Invalid version argument: `' + versionArg + '`. Usage: `bower version [<newversion> | major | minor | patch]`', 'EINVALIDVERSION');
}
if (currentVersion === newVersion) {
throw createError('Version not changed', 'EVERSIONNOTCHANGED');
}
return newVersion;
}
function checkGit(cwd) {
var gitDir = path.join(cwd, '.git');
return Q.nfcall(fs.stat, gitDir)
.then(function (stat) {
if (stat.isDirectory()) {
return checkGitStatus(cwd);
}
return false;
}, function () {
//Ignore not found .git directory
return false;
});
}
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, cwd: cwd});
})
.then(function (value) {
var stdout = value[0];
var lines = filterModifiedStatusLines(stdout);
if (lines.length) {
throw createError('Git working directory not clean.\n' + lines.join('\n'), 'EWORKINGDIRECTORYDIRTY');
}
return true;
});
}
function filterModifiedStatusLines(stdout) {
return stdout.trim().split('\n')
.filter(function (line) {
return line.trim() && !line.match(/^\?\? /);
}).map(function (line) {
return line.trim();
});
}
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, cwd: cwd})
.then(function () {
return Q.nfcall(execFile, 'git', ['commit', '-m', message], {env: process.env, cwd: cwd});
})
.then(function () {
return Q.nfcall(execFile, 'git', ['tag', tag, '-am', message], {env: process.env, cwd: cwd});
});
}
// -------------------
version.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions({
'message': { type: String, shorthand: 'm'}
}, argv);
return [options.argv.remain[1], options];
};
module.exports = version;

View File

@@ -1,26 +1,58 @@
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');
// Delete the json attribute because it is no longer supported
// and conflicts with --json
delete config.json;
var cachedConfigs = {};
// If interactive is auto (null), guess its value
if (config.interactive == null) {
config.interactive = process.title === 'bower' && tty.isatty(1);
function defaultConfig(config) {
config = config || {};
var cachedConfig = readCachedConfig(config.cwd || process.cwd());
return object.merge(cachedConfig, 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 readCachedConfig(cwd) {
if (cachedConfigs[cwd]) {
return cachedConfigs[cwd];
}
module.exports = config;
var config = cachedConfigs[cwd] = bowerConfig.read(cwd);
// Delete the json attribute because it is no longer supported
// and conflicts with --json
delete config.json;
// If interactive is auto (null), guess its value
if (config.interactive == null) {
config.interactive = (
process.bin === 'bower' &&
tty.isatty(1) &&
!process.env.CI
);
}
// Merge common CLI options into the config
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;
}
function resetCache () {
cachedConfigs = {};
}
module.exports = defaultConfig;
module.exports.reset = resetCache;

View File

@@ -9,6 +9,7 @@ var PackageRepository = require('./PackageRepository');
var semver = require('../util/semver');
var copy = require('../util/copy');
var createError = require('../util/createError');
var scripts = require('./scripts');
function Manager(config, logger) {
this._config = config;
@@ -108,7 +109,41 @@ Manager.prototype.resolve = function () {
}.bind(this));
};
Manager.prototype.install = function () {
Manager.prototype.preinstall = function (json) {
var that = this;
var componentsDir = path.join(this._config.cwd, this._config.directory);
// If nothing to install, skip the code bellow
if (mout.lang.isEmpty(that._dissected)) {
return Q.resolve({});
}
return Q.nfcall(mkdirp, componentsDir)
.then(function () {
return scripts.preinstall(
that._config, that._logger, that._dissected, that._installed, json
);
});
};
Manager.prototype.postinstall = function (json) {
var that = this;
var componentsDir = path.join(this._config.cwd, this._config.directory);
// If nothing to install, skip the code bellow
if (mout.lang.isEmpty(that._dissected)) {
return Q.resolve({});
}
return Q.nfcall(mkdirp, componentsDir)
.then(function () {
return scripts.postinstall(
that._config, that._logger, that._dissected, that._installed, json
);
});
};
Manager.prototype.install = function (json) {
var componentsDir;
var that = this;
@@ -117,6 +152,11 @@ Manager.prototype.install = function () {
return Q.reject(createError('Already working', 'EWORKING'));
}
// If nothing to install, skip the code bellow
if (mout.lang.isEmpty(that._dissected)) {
return Q.resolve({});
}
componentsDir = path.join(this._config.cwd, this._config.directory);
return Q.nfcall(mkdirp, componentsDir)
.then(function () {
@@ -161,6 +201,24 @@ Manager.prototype.install = function () {
return Q.all(promises);
})
.then(function () {
// Sync up dissected dependencies and dependants
// See: https://github.com/bower/bower/issues/879
mout.object.forOwn(that._dissected, function (pkg) {
// Sync dependencies
mout.object.forOwn(pkg.dependencies, function (dependency, name) {
var dissected = this._dissected[name] || (this._resolved[name] ? this._resolved[name][0] : dependency);
pkg.dependencies[name] = dissected;
}, this);
// Sync dependants
pkg.dependants = pkg.dependants.map(function (dependant) {
var name = dependant.name;
var dissected = this._dissected[name] || (this._resolved[name] ? this._resolved[name][0] : dependant);
return dissected;
}, this);
}, that);
// Resolve with meaningful data
return mout.object.map(that._dissected, function (decEndpoint) {
return this.toData(decEndpoint);
@@ -171,11 +229,13 @@ Manager.prototype.install = function () {
}.bind(this));
};
Manager.prototype.toData = function (decEndpoint, extraKeys) {
Manager.prototype.toData = function (decEndpoint, extraKeys, upperDeps) {
var names;
var extra;
var data = {};
upperDeps = upperDeps || [];
data.endpoint = mout.object.pick(decEndpoint, ['name', 'source', 'target']);
if (decEndpoint.canonicalDir) {
@@ -198,7 +258,14 @@ Manager.prototype.toData = function (decEndpoint, extraKeys) {
// by dependency names
names = Object.keys(decEndpoint.dependencies).sort();
names.forEach(function (name) {
data.dependencies[name] = this.toData(decEndpoint.dependencies[name], extraKeys);
// Prevent from infinite recursion when installing cyclic
// dependencies
if (!mout.array.contains(upperDeps, name)) {
data.dependencies[name] = this.toData(decEndpoint.dependencies[name],
extraKeys,
upperDeps.concat(decEndpoint.name));
}
}, this);
}
@@ -348,6 +415,8 @@ Manager.prototype._failFast = function () {
};
Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta, jsonKey) {
var pending = [];
decEndpoint.dependencies = decEndpoint.dependencies || {};
// Parse package dependencies
@@ -403,10 +472,7 @@ Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta, jsonKey)
}, this);
if (compatible) {
compatible.promise
.then(function () {
this._parseDependencies(decEndpoint, pkgMeta, jsonKey);
}.bind(this));
pending.push(compatible.promise);
return;
}
}
@@ -419,6 +485,13 @@ Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta, jsonKey)
childDecEndpoint.dependants = [decEndpoint];
this._fetch(childDecEndpoint);
}, this);
if (pending.length > 0) {
Q.all(pending)
.then(function () {
this._parseDependencies(decEndpoint, pkgMeta, jsonKey);
}.bind(this));
}
};
Manager.prototype._dissect = function () {
@@ -443,7 +516,7 @@ Manager.prototype._dissect = function () {
var semvers;
var nonSemvers;
// Filter semver ones
// Filter out non-semver ones
semvers = decEndpoints.filter(function (decEndpoint) {
return !!decEndpoint.pkgMeta.version;
});
@@ -498,13 +571,11 @@ Manager.prototype._dissect = function () {
return;
}
this._logger.info('resolution', 'Removed unnecessary ' + name + '#' + resolution + ' resolution', {
this._logger.warn('extra-resolution', 'Unnecessary resolution: ' + name + '#' + resolution, {
name: name,
resolution: resolution,
action: 'delete'
});
delete this._resolutions[name];
}, this);
// Filter only packages that need to be installed
@@ -513,12 +584,8 @@ Manager.prototype._dissect = function () {
var installedMeta = this._installed[name];
var dst;
// Analyse a few props
if (installedMeta &&
installedMeta._target === decEndpoint.target &&
installedMeta._originalSource === decEndpoint.source &&
installedMeta._release === decEndpoint.pkgMeta._release
) {
// Skip linked dependencies
if (decEndpoint.linked) {
return false;
}
@@ -528,12 +595,16 @@ Manager.prototype._dissect = function () {
return false;
}
return true;
}, this);
// Analyse a few props
if (installedMeta &&
installedMeta._target === decEndpoint.target &&
installedMeta._originalSource === decEndpoint.source &&
installedMeta._release === decEndpoint.pkgMeta._release
) {
return this._config.force;
}
// Resolve with meaningful data
return mout.object.map(this._dissected, function (decEndpoint) {
return this.toData(decEndpoint);
return true;
}, this);
}.bind(this))
.then(this._deferred.resolve, this._deferred.reject);
@@ -561,8 +632,8 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
}
picks.push.apply(picks, nonSemvers);
// If there are only semver ones, figure out the which one
// is compatible with every requirement
// If there are only semver ones, figure out which one is
// compatible with every requirement
} else {
suitable = mout.array.find(semvers, function (subject) {
return semvers.every(function (decEndpoint) {
@@ -634,14 +705,21 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
});
if (resolution && !unresolvable) {
suitable = -1;
// Range resolution
if (semver.validRange(resolution)) {
suitable = mout.array.findIndex(picks, function (pick) {
return pick.pkgMeta.version &&
semver.satisfies(pick.pkgMeta.version, resolution);
});
} else {
}
// Exact match resolution (e.g. branches/tags)
if (suitable === -1) {
suitable = mout.array.findIndex(picks, function (pick) {
return pick.pkgMeta._release === resolution;
return pick.target === resolution ||
pick.pkgMeta._release === resolution;
});
}
@@ -673,6 +751,10 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
suitable: dataPicks[suitable],
forced: true
});
// Save resolution
this._storeResolution(picks[suitable]);
return Q.resolve(picks[suitable]);
}
@@ -693,7 +775,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(), '!'));
@@ -706,7 +788,6 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
})
.then(function (choice) {
var pick;
var resolution;
// Sanitize choice
choice = choice.trim();
@@ -714,30 +795,55 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
choice = Number(mout.string.trim(choice, '!'));
pick = picks[choice - 1];
// Store choice into resolutions
if (pick.target === '*') {
resolution = pick.pkgMeta._release || '*';
} else {
resolution = pick.target;
}
// Save resolution
if (save) {
this._logger.info('resolution', 'Saved ' + name + '#' + resolution + ' as resolution', {
name: name,
resolution: resolution,
action: this._resolutions[name] ? 'edit' : 'add'
});
this._resolutions[name] = resolution;
this._storeResolution(pick);
}
return pick;
}.bind(this));
};
Manager.prototype._storeResolution = function (pick) {
var resolution;
var name = pick.name;
if (pick.target === '*') {
resolution = pick.pkgMeta._release || '*';
} else {
resolution = pick.target;
}
this._logger.info('resolution', 'Saved ' + name + '#' + resolution + ' as resolution', {
name: name,
resolution: resolution,
action: this._resolutions[name] ? 'edit' : 'add'
});
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) {
@@ -745,30 +851,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)) {
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;
@@ -804,6 +957,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

@@ -46,7 +46,11 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
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);
@@ -165,19 +169,25 @@ PackageRepository.clearRuntimeCache = function () {
// ---------------------
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

@@ -13,13 +13,14 @@ 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 = defaultConfig(config);
this._logger = logger || new Logger();
this._manager = new Manager(this._config, this._logger);
@@ -28,8 +29,7 @@ function Project(config, logger) {
// -----------------
Project.prototype.install = function (endpoints, options) {
var decEndpoints;
Project.prototype.install = function (decEndpoints, options, config) {
var that = this;
var targets = [];
var resolved = {};
@@ -41,49 +41,49 @@ Project.prototype.install = function (endpoints, 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);
// Ignore linked dependencies because it's too complex to parse them
// Note that this might change in the future: #673
if (node.linked) {
return false;
}
// Add decomposed endpoints as targets
decEndpoints = decEndpoints || [];
decEndpoints.forEach(function (decEndpoint) {
// Mark as new so that a conflict for this target
// always require a choice
// Also allows for the target to be converted in case
// of being *
decEndpoint.newly = true;
targets.push(decEndpoint);
});
// Add endpoints as targets
if (endpoints) {
decEndpoints = endpoints.map(function (endpoint) {
var decEndpoint = endpointParser.decompose(endpoint);
// Mark as new so that a conflict for this target
// always require a choice
// Also allows for the target to be converted in case
// of being *
decEndpoint.newly = true;
targets.push(decEndpoint);
return decEndpoint;
});
} else {
decEndpoints = [];
}
// 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) {
@@ -93,6 +93,10 @@ Project.prototype.install = function (endpoints, options) {
jsonEndpoint = endpointParser.decomposed2json(decEndpoint);
if (that._options.saveExact) {
jsonEndpoint[decEndpoint.name] = decEndpoint.pkgMeta.version;
}
if (that._options.save) {
that._json.dependencies = mout.object.mixIn(that._json.dependencies || {}, jsonEndpoint);
}
@@ -106,7 +110,9 @@ Project.prototype.install = function (endpoints, 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 () {
@@ -136,13 +142,14 @@ Project.prototype.update = function (names, options) {
if (!names) {
// Mark each root dependency as targets
that.walkTree(tree, function (node) {
// Ignore linked extraneous because
// we don't know their real sources
if (node.extraneous && node.linked) {
return false;
// We don't know the real source of linked packages
// Instead we read its dependencies
if (node.linked) {
targets.push.apply(targets, mout.object.values(node.dependencies));
} else {
targets.push(node);
}
targets.push(node);
return false;
}, true);
// Otherwise, selectively update the specified ones
@@ -159,14 +166,15 @@ Project.prototype.update = function (names, options) {
// Add packages whose names are specified to be updated
that.walkTree(tree, function (node, name) {
// Ignore linked extraneous because
// we don't know their real source
if (node.extraneous && node.linked) {
return false;
}
if (names.indexOf(name) !== -1) {
targets.push(node);
// We don't know the real source of linked packages
// Instead we read its dependencies
if (node.linked) {
targets.push.apply(targets, mout.object.values(node.dependencies));
} else {
targets.push(node);
}
return false;
}
}, true);
@@ -180,22 +188,24 @@ Project.prototype.update = function (names, options) {
} else {
resolved[name] = node;
}
// Ignore linked dependencies because it's too complex to parse them
// Note that this might change in the future: #673
if (node.linked) {
return false;
}
}, true);
}
// 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;
});
});
});
})
@@ -287,7 +297,8 @@ Project.prototype.uninstall = function (names, options) {
// Prompt the user
return Q.nfcall(that._logger.prompt.bind(that._logger), {
type: 'confirm',
message: 'Continue anyway?'
message: 'Continue anyway?',
default: true
})
.then(function (confirmed) {
// If the user decided to skip it, remove from the array so that it won't
@@ -313,7 +324,9 @@ Project.prototype.uninstall = function (names, options) {
});
};
Project.prototype.getTree = function () {
Project.prototype.getTree = function (options) {
this._options = options || {};
return this._analyse()
.spread(function (json, tree, flattened) {
var extraneous = [];
@@ -368,7 +381,7 @@ Project.prototype.walkTree = function (node, fn, onlyOnce) {
while (queue.length) {
node = queue.shift();
result = fn(node, node.name);
result = fn(node, node.endpoint ? node.endpoint.name : node.name);
// Abort traversal if result is false
if (result === false) {
@@ -455,7 +468,6 @@ Project.prototype._analyse = function () {
.spread(function (json, installed, links) {
var root;
var jsonCopy = mout.lang.deepClone(json);
var flattened = mout.object.mixIn({}, installed, links);
root = {
name: json.name,
@@ -466,41 +478,51 @@ Project.prototype._analyse = function () {
root: true
};
mout.object.mixIn(installed, links);
// Mix direct extraneous as dependencies
// (dependencies installed without --save/--save-dev)
jsonCopy.dependencies = jsonCopy.dependencies || {};
jsonCopy.devDependencies = jsonCopy.devDependencies || {};
mout.object.forOwn(installed, function (decEndpoint, key) {
var pkgMeta = decEndpoint.pkgMeta;
var isSaved = jsonCopy.dependencies[key] || jsonCopy.devDependencies[key];
// The _direct propery is saved by the manager when .newly is specified
if (!jsonCopy.dependencies[key] && pkgMeta._direct) {
// It may happen pkgMeta is undefined if package is uninstalled
if (!isSaved && pkgMeta && pkgMeta._direct) {
decEndpoint.extraneous = true;
jsonCopy.dependencies[key] = (pkgMeta._originalSource || pkgMeta._source) + '#' + pkgMeta._target;
if (decEndpoint.linked) {
jsonCopy.dependencies[key] = pkgMeta.version || '*';
} else {
jsonCopy.dependencies[key] = (pkgMeta._originalSource || pkgMeta._source) + '#' + pkgMeta._target;
}
}
});
// Restore the original dependencies cross-references,
// that is, the parent-child relationships
this._restoreNode(root, flattened, 'dependencies');
this._restoreNode(root, installed, 'dependencies');
// Do the same for the dev dependencies
if (!this._options.production) {
this._restoreNode(root, flattened, 'devDependencies');
this._restoreNode(root, installed, 'devDependencies');
}
// Restore the rest of the extraneousv (not installed directly)
mout.object.forOwn(flattened, function (decEndpoint, name) {
// Restore the rest of the extraneous (not installed directly)
mout.object.forOwn(installed, function (decEndpoint, name) {
if (!decEndpoint.dependants) {
decEndpoint.extraneous = true;
this._restoreNode(decEndpoint, flattened, 'dependencies');
this._restoreNode(decEndpoint, installed, 'dependencies');
// Note that it has no dependants, just dependencies!
root.dependencies[name] = decEndpoint;
}
}, this);
// Remove root from the flattened tree
delete flattened[json.name];
delete installed[json.name];
return [json, root, flattened];
return [json, root, installed];
}.bind(this));
};
@@ -527,9 +549,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));
}.bind(this));
};
Project.prototype._readJson = function () {
@@ -682,43 +702,47 @@ Project.prototype._removePackages = function (packages) {
var that = this;
var promises = [];
mout.object.forOwn(packages, function (dir, name) {
var promise;
return scripts.preuninstall(that._config, that._logger, packages, that._installed, that._json)
.then(function () {
// Delete directory
if (!dir) {
promise = Q.resolve();
that._logger.warn('not-installed', name, {
name: name
});
} else {
promise = Q.nfcall(rimraf, dir);
that._logger.action('uninstall', name, {
name: name,
dir: dir
});
}
mout.object.forOwn(packages, function (dir, name) {
var promise;
// Remove from json only if successfully deleted
if (that._options.save && that._json.dependencies) {
promise = promise
.then(function () {
delete that._json.dependencies[name];
});
}
// Delete directory
if (!dir) {
promise = Q.resolve();
that._logger.warn('not-installed', '\'' + name + '\'' + ' cannot be uninstalled as it is not currently installed', {
name: name
});
} else {
promise = Q.nfcall(rimraf, dir);
that._logger.action('uninstall', name, {
name: name,
dir: dir
});
}
if (that._options.saveDev && that._json.devDependencies) {
promise = promise
.then(function () {
delete that._json.devDependencies[name];
});
}
// Remove from json only if successfully deleted
if (that._options.save && that._json.dependencies) {
promise = promise
.then(function () {
delete that._json.dependencies[name];
});
}
promises.push(promise);
});
if (that._options.saveDev && that._json.devDependencies) {
promise = promise
.then(function () {
delete that._json.devDependencies[name];
});
}
return Q.all(promises)
// Save json
promises.push(promise);
});
return Q.all(promises);
})
.then(function () {
return that.saveJson();
})
@@ -730,7 +754,7 @@ Project.prototype._removePackages = function (packages) {
});
};
Project.prototype._restoreNode = function (node, flattened, jsonKey) {
Project.prototype._restoreNode = function (node, flattened, jsonKey, processed) {
var deps;
// Do not restore if the node is missing
@@ -740,10 +764,11 @@ Project.prototype._restoreNode = function (node, flattened, jsonKey) {
node.dependencies = node.dependencies || {};
node.dependants = node.dependants || {};
processed = processed || {};
// Only process deps that are yet 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) {
@@ -779,21 +804,28 @@ Project.prototype._restoreNode = function (node, flattened, jsonKey) {
}
// Check if source changed, marking as different if it did
originalSource = mout.object.get(local, 'pkgMeta._originalSource');
restored.different = originalSource && originalSource !== json.source;
// We only do this for direct root dependencies that are compatible
if (node.root && compatible) {
originalSource = mout.object.get(local, 'pkgMeta._originalSource');
if (originalSource && originalSource !== json.source) {
restored.different = true;
}
}
}
// Cross reference
node.dependencies[key] = restored;
processed[node.name + ':' + key] = true;
restored.dependants = restored.dependants || {};
restored.dependants[node.name] = node;
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

@@ -5,6 +5,7 @@ var Q = require('q');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var LRU = require('lru-cache');
var lockFile = require('lockfile');
var semver = require('../util/semver');
var readJson = require('../util/readJson');
var copy = require('../util/copy');
@@ -18,6 +19,9 @@ function ResolveCache(config) {
// - etc..
this._config = config;
this._dir = this._config.storage.packages;
this._lockDir = this._config.storage.packages;
mkdirp.sync(this._lockDir);
// Cache is stored/retrieved statically to ensure singularity
// among instances
@@ -76,7 +80,7 @@ ResolveCache.prototype.retrieve = function (source, target) {
}
// Resolve with canonical dir and package meta
canonicalDir = path.join(dir, version);
canonicalDir = path.join(dir, encodeURIComponent(version));
return that._readPkgMeta(canonicalDir)
.then(function (pkgMeta) {
return [canonicalDir, pkgMeta];
@@ -97,6 +101,7 @@ ResolveCache.prototype.store = function (canonicalDir, pkgMeta) {
var sourceId;
var release;
var dir;
var pkgLock;
var promise;
var that = this;
@@ -107,36 +112,41 @@ ResolveCache.prototype.store = function (canonicalDir, pkgMeta) {
sourceId = md5(pkgMeta._source);
release = that._getPkgRelease(pkgMeta);
dir = path.join(that._dir, sourceId, release);
pkgLock = path.join(that._lockDir, sourceId + '-' + release + '.lock');
// Check if directory exists
// Check if destination directory exists to prevent issuing lock at all times
return Q.nfcall(fs.stat, dir)
.then(function () {
// If it does exists, remove it
return Q.nfcall(rimraf, dir);
}, function (err) {
// If directory does not exists, ensure its basename
// is created
if (err.code === 'ENOENT') {
return Q.nfcall(mkdirp, path.dirname(dir));
}
.fail(function (err) {
var lockParams = { wait: 250, retries: 25, stale: 60000 };
return Q.nfcall(lockFile.lock, pkgLock, lockParams).then(function () {
// Ensure other process didn't start copying files before lock was created
return Q.nfcall(fs.stat, dir)
.fail(function (err) {
// If stat fails, it is expected to return ENOENT
if (err.code !== 'ENOENT') {
throw err;
}
throw err;
})
// Move the canonical to sourceId/target
.then(function () {
return Q.nfcall(fs.rename, canonicalDir, dir)
.fail(function (err) {
// If error is EXDEV it means that we are trying to rename
// across different drives, so we copy and remove it instead
if (err.code !== 'EXDEV') {
throw err;
}
// Create missing directory and copy files there
return Q.nfcall(mkdirp, path.dirname(dir)).then(function () {
return Q.nfcall(fs.rename, canonicalDir, dir)
.fail(function (err) {
// If error is EXDEV it means that we are trying to rename
// across different drives, so we copy and remove it instead
if (err.code !== 'EXDEV') {
throw err;
}
return copy.copyDir(canonicalDir, dir)
.then(function () {
return Q.nfcall(rimraf, canonicalDir);
return copy.copyDir(canonicalDir, dir);
});
});
});
}).finally(function () {
lockFile.unlockSync(pkgLock);
});
}).finally(function () {
// Ensure no tmp dir is left on disk.
return Q.nfcall(rimraf, canonicalDir);
});
})
.then(function () {
@@ -315,7 +325,12 @@ ResolveCache.clearRuntimeCache = function () {
// ------------------------
ResolveCache.prototype._getPkgRelease = function (pkgMeta) {
return pkgMeta.version || (pkgMeta._target === '*' ? '_wildcard' : pkgMeta._target);
var release = pkgMeta.version || (pkgMeta._target === '*' ? '_wildcard' : pkgMeta._target);
// Encode some dangerous chars such as / and \
release = encodeURIComponent(release);
return release;
};
ResolveCache.prototype._readPkgMeta = function (dir) {
@@ -341,6 +356,7 @@ ResolveCache.prototype._getVersions = function (sourceId) {
.then(function (versions) {
// Sort and cache in memory
that._sortVersions(versions);
versions = versions.map(decodeURIComponent);
that._cache.set(sourceId, versions);
return [versions, false];
}, function (err) {

View File

@@ -45,6 +45,13 @@ function getConstructor(source, config, registryClient) {
});
}
// 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];
});
}
// URL case
if (/^https?:\/\//i.exec(source)) {
return Q.fcall(function () {
@@ -60,7 +67,7 @@ function getConstructor(source, config, registryClient) {
// If source is ./ or ../ or an absolute path
absolutePath = path.resolve(config.cwd, source);
if (/^\.\.?[\/\\]/.test(source) || /^~\//.test(source) || path.normalize(source) === absolutePath) {
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()) {
@@ -71,6 +78,19 @@ function getConstructor(source, config, registryClient) {
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) {
if (stats.isDirectory()) {
return function () {
return Q.resolve([resolvers.Svn, absolutePath]);
};
}
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)

View File

@@ -19,6 +19,12 @@ function FsResolver(decEndpoint, config, logger) {
if (this._target !== '*') {
throw createError('File system sources can\'t resolve targets', 'ENORESTARGET');
}
// If the name was guessed
if (this._guessedName) {
// Remove extension
this._name = this._name.substr(0, this._name.length - path.extname(this._name).length);
}
}
util.inherits(FsResolver, Resolver);

View File

@@ -69,7 +69,7 @@ GitFsResolver.refs = function (source) {
// Store the promise to be reused until it resolves
// to a specific value
this._cache.refs.set(source);
this._cache.refs.set(source, value);
return value;
};

View File

@@ -35,6 +35,9 @@ function GitHubResolver(decEndpoint, config, logger) {
if (this._config.proxy || this._config.httpsProxy) {
this._source = this._source.replace('git://', 'https://');
}
// Enable shallow clones for GitHub repos
this._shallowClone = true;
}
util.inherits(GitHubResolver, GitRemoteResolver);
@@ -81,9 +84,11 @@ GitHubResolver.prototype._checkout = function () {
}
// Progress
msg = 'received ' + (state.received / 1024 / 1024).toFixed(1) + 'MB ';
msg += 'of ' + (state.total / 1024 / 1024).toFixed(1) + 'MB downloaded, ';
msg += state.percent + '%';
msg = 'received ' + (state.received / 1024 / 1024).toFixed(1) + 'MB';
if (state.total) {
msg += ' of ' + (state.total / 1024 / 1024).toFixed(1) + 'MB downloaded, ';
msg += state.percent + '%';
}
that._logger.info('progress', msg);
})
.then(function () {

View File

@@ -25,6 +25,9 @@ function GitRemoteResolver(decEndpoint, config, logger) {
} else {
this._host = url.parse(this._source).host;
}
// Disable shallow clones
this._shallowClone = false;
}
util.inherits(GitRemoteResolver, GitResolver);
@@ -54,9 +57,10 @@ GitRemoteResolver.prototype._checkout = function () {
}
// Throttle the progress reporter to 1 time each sec
reporter = mout['function'].throttle(function (data) {
var lines = data.split(/[\r\n]+/);
reporter = mout.fn.throttle(function (data) {
var lines;
lines = data.split(/[\r\n]+/);
lines.forEach(function (line) {
if (/\d{1,3}\%/.test(line)) {
// TODO: There are some strange chars that appear once in a while (\u001b[K)
@@ -80,6 +84,7 @@ GitRemoteResolver.prototype._checkout = function () {
// Clear timer at the end
.fin(function () {
clearTimeout(timer);
reporter.cancel();
});
};
@@ -111,7 +116,7 @@ GitRemoteResolver.prototype._fastClone = function (resolution) {
args = ['clone', this._source, '-b', branch, '--progress', '.'];
// If the host does not support shallow clones, we don't use --depth=1
if (!GitRemoteResolver._noShallow.get(this._host)) {
if (this._shallowClone && !GitRemoteResolver._noShallow.get(this._host)) {
args.push('--depth', 1);
}
@@ -131,7 +136,7 @@ GitRemoteResolver.prototype._fastClone = function (resolution) {
// When that happens, we mark this host and try again
if (!GitRemoteResolver._noShallow.has(that._source) &&
err.details &&
/rpc failed/i.test(err.details)
/(rpc failed|shallow|--depth)/i.test(err.details)
) {
GitRemoteResolver._noShallow.set(that._host, true);
return that._fastClone(resolution);
@@ -185,7 +190,7 @@ GitRemoteResolver.refs = function (source) {
// Store the promise to be reused until it resolves
// to a specific value
this._cache.refs.set(source);
this._cache.refs.set(source, value);
return value;
};

View File

@@ -10,7 +10,6 @@ var mout = require('mout');
var Resolver = require('./Resolver');
var semver = require('../../util/semver');
var createError = require('../../util/createError');
var defaultConfig = require('../../config');
var hasGit;
@@ -22,13 +21,13 @@ try {
hasGit = false;
}
// Set template dir to the empty directory so that user templates are not run
// This environment variable is not multiple config aware but it's not documented
// anyway
mkdirp.sync(defaultConfig.storage.empty);
process.env.GIT_TEMPLATE_DIR = defaultConfig.storage.empty;
function GitResolver(decEndpoint, config, logger) {
// Set template dir to the empty directory so that user templates are not run
// This environment variable is not multiple config aware but it's not documented
// anyway
mkdirp.sync(config.storage.empty);
process.env.GIT_TEMPLATE_DIR = config.storage.empty;
Resolver.call(this, decEndpoint, config, logger);
if (!hasGit) {
@@ -128,10 +127,16 @@ GitResolver.prototype._findResolution = function (target) {
return that._resolution = { type: 'version', tag: version.tag, commit: version.commit };
}
// Check if there's an exact branch with this name as last resort
return self.branches(that._source)
.then(function (branches) {
// Use hasOwn because a branch could have a name like "hasOwnProperty"
// Check if there's an exact branch/tag with this name as last resort
return Q.all([
self.branches(that._source),
self.tags(that._source)
])
.spread(function (branches, tags) {
// Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
if (mout.object.hasOwn(tags, target)) {
return that._resolution = { type: 'tag', tag: target, commit: tags[target] };
}
if (mout.object.hasOwn(branches, target)) {
return that._resolution = { type: 'branch', branch: target, commit: branches[target] };
}
@@ -146,35 +151,44 @@ GitResolver.prototype._findResolution = function (target) {
}
// Otherwise, target is either a tag or a branch
// Start by checking if is a valid tag
return self.tags(this._source)
.then(function (tags) {
return Q.all([
self.branches(that._source),
self.tags(that._source)
])
.spread(function (branches, tags) {
// Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
if (mout.object.hasOwn(tags, target)) {
return that._resolution = { type: 'tag', tag: target, commit: tags[target] };
}
if (mout.object.hasOwn(branches, target)) {
return that._resolution = { type: 'branch', branch: target, commit: branches[target] };
}
// Finally check if is a valid branch
return self.branches(that._source)
.then(function (branches) {
// Use hasOwn because a branch could have a name like "hasOwnProperty"
if (mout.object.hasOwn(branches, target)) {
return that._resolution = { type: 'branch', branch: target, commit: branches[target] };
if ((/^[a-f0-9]{4,40}$/).test(target)) {
if (target.length < 12) {
that._logger.warn(
'short-sha',
'Consider using longer commit SHA to avoid conflicts'
);
}
branches = Object.keys(branches);
tags = Object.keys(tags);
that._resolution = { type: 'commit', commit: target };
return that._resolution;
}
err = createError('Tag/branch ' + target + ' does not exist', 'ENORESTARGET');
err.details = !tags.length ?
'No tags found in ' + that._source :
'Available tags: ' + tags.join(', ');
err.details += '\n';
err.details += !branches.length ?
'No branches found in ' + that._source :
'Available branches: ' + branches.join(', ');
branches = Object.keys(branches);
tags = Object.keys(tags);
throw err;
});
err = createError('Tag/branch ' + target + ' does not exist', 'ENORESTARGET');
err.details = !tags.length ?
'No tags found in ' + that._source :
'Available tags: ' + tags.join(', ');
err.details += '\n';
err.details += !branches.length ?
'No branches found in ' + that._source :
'Available branches: ' + branches.join(', ');
throw err;
});
};
@@ -222,8 +236,12 @@ GitResolver.prototype._savePkgMeta = function (meta) {
delete meta.version;
}
// Save version/commit/branch/tag in the release
meta._release = version || this._resolution.tag || this._resolution.commit.substr(0, 10);
// Save version/tag/commit in the release
// Note that we can't store branches because _release is supposed to be
// an unique id of this ref.
meta._release = version ||
this._resolution.tag ||
this._resolution.commit.substr(0, 10);
// Save resolution to be used in hasNew later
meta._resolution = this._resolution;
@@ -295,15 +313,13 @@ GitResolver.tags = function (source) {
value = this.refs(source)
.then(function (refs) {
var tags = [];
var tags = {};
// For each line in the refs, match only the tags
refs.forEach(function (line) {
var match = line.match(/^([a-f0-9]{40})\s+refs\/tags\/(\S+)/);
var tag;
if (match) {
tag = match[2];
if (match && !mout.string.endsWith(match[2], '^{}')) {
tags[match[2]] = match[1];
}
});

View File

@@ -114,6 +114,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
@@ -149,8 +169,8 @@ Resolver.prototype._createTempDir = function () {
});
}.bind(this))
.then(function (dir) {
this._tempDir = dir;
return dir;
// nfcall may return multiple callback arguments as an array
return this._tempDir = Array.isArray(dir) ? dir[0] : dir;
}.bind(this));
};
@@ -201,26 +221,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

@@ -0,0 +1,404 @@
var util = require('util');
var Q = require('q');
var which = require('which');
var LRU = require('lru-cache');
var mout = require('mout');
var path = require('path');
var Resolver = require('./Resolver');
var semver = require('../../util/semver');
var createError = require('../../util/createError');
var cmd = require('../../util/cmd');
var hasSvn;
// Check if svn is installed
try {
which.sync('svn');
hasSvn = true;
} catch (ex) {
hasSvn = false;
}
function SvnResolver(decEndpoint, config, logger) {
Resolver.call(this, decEndpoint, config, logger);
if (!hasSvn) {
throw createError('svn is not installed or not in the PATH', 'ENOSVN');
}
}
util.inherits(SvnResolver, Resolver);
mout.object.mixIn(SvnResolver, Resolver);
// -----------------
SvnResolver.getSource = function (source) {
var uri = this._source || source;
return uri
.replace(/^svn\+(https?|file):\/\//i, '$1://') // Change svn+http or svn+https or svn+file to http(s), file respectively
.replace('svn://', 'http://') // Change svn to http
.replace(/\/+$/, ''); // Remove trailing slashes
};
SvnResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
var oldResolution = pkgMeta._resolution || {};
return this._findResolution()
.then(function (resolution) {
// Check if resolution types are different
if (oldResolution.type !== resolution.type) {
return true;
}
// If resolved to a version, there is new content if the tags are not equal
if (resolution.type === 'version' && semver.neq(resolution.tag, oldResolution.tag)) {
return true;
}
// As last check, we compare both commit hashes
return resolution.commit !== oldResolution.commit;
});
};
SvnResolver.prototype._resolve = function () {
var that = this;
return this._findResolution()
.then(function () {
return that._export();
});
};
// -----------------
SvnResolver.prototype._export = function () {
var promise;
var timer;
var reporter;
var that = this;
var resolution = this._resolution;
this.source = SvnResolver.getSource(this._source);
this._logger.action('export', resolution.tag || resolution.branch || resolution.commit, {
resolution: resolution,
to: this._tempDir
});
if (resolution.type === 'commit') {
promise = cmd('svn', ['export', '--force', this._source + path.normalize('/trunk'), '-r' + resolution.commit, this._tempDir]);
} else if (resolution.type === 'branch' && resolution.branch === 'trunk') {
promise = cmd('svn', ['export', '--force', this._source + path.normalize('/trunk'), this._tempDir]);
} else if (resolution.type === 'branch') {
promise = cmd('svn', ['export', '--force', this._source + path.normalize('/branches/' + resolution.branch), this._tempDir]);
} else {
promise = cmd('svn', ['export', '--force', this._source + path.normalize('/tags/' + resolution.tag), this._tempDir]);
}
// Throttle the progress reporter to 1 time each sec
reporter = mout.fn.throttle(function (data) {
var lines;
lines = data.split(/[\r\n]+/);
lines.forEach(function (line) {
if (/\d{1,3}\%/.test(line)) {
// TODO: There are some strange chars that appear once in a while (\u001b[K)
// Trim also those?
that._logger.info('progress', line.trim());
}
});
}, 1000);
// Start reporting progress after a few seconds
timer = setTimeout(function () {
promise.progress(reporter);
}, 8000);
return promise
// Add additional proxy information to the error if necessary
.fail(function (err) {
throw err;
})
// Clear timer at the end
.fin(function () {
clearTimeout(timer);
reporter.cancel();
});
};
// -----------------
SvnResolver.prototype._findResolution = function (target) {
var err;
var self = this.constructor;
var that = this;
target = target || this._target || '*';
this._source = SvnResolver.getSource(this._source);
// Target is a revision, so it's a stale target (not a moving target)
// There's nothing to do in this case
if ((/^r\d+/).test(target)) {
target = target.split('r');
this._resolution = { type: 'commit', commit: target[1] };
return Q.resolve(this._resolution);
}
// Target is a range/version
if (semver.validRange(target)) {
return self.versions(this._source, true)
.then(function (versions) {
var versionsArr,
version,
index;
versionsArr = versions.map(function (obj) { return obj.version; });
// If there are no tags and target is *,
// fallback to the latest commit on trunk
if (!versions.length && target === '*') {
return that._findResolution('trunk');
}
versionsArr = versions.map(function (obj) { return obj.version; });
// Find a satisfying version, enabling strict match so that pre-releases
// have lower priority over normal ones when target is *
index = semver.maxSatisfyingIndex(versionsArr, target, true);
if (index !== -1) {
version = versions[index];
return that._resolution = { type: 'version', tag: version.tag, commit: version.commit };
}
// Check if there's an exact branch/tag with this name as last resort
return Q.all([
self.branches(that._source),
self.tags(that._source)
])
.spread(function (branches, tags) {
// Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
if (mout.object.hasOwn(tags, target)) {
return that._resolution = { type: 'tag', tag: target, commit: tags[target] };
}
if (mout.object.hasOwn(branches, target)) {
return that._resolution = { type: 'branch', branch: target, commit: branches[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(', ')
});
});
});
}
// Otherwise, target is either a tag or a branch
return Q.all([
self.branches(that._source),
self.tags(that._source)
])
.spread(function (branches, tags) {
// Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
if (mout.object.hasOwn(tags, target)) {
return that._resolution = { type: 'tag', tag: target, commit: tags[target] };
}
if (mout.object.hasOwn(branches, target)) {
return that._resolution = { type: 'branch', branch: target, commit: branches[target] };
}
branches = Object.keys(branches);
tags = Object.keys(tags);
err = createError('target ' + target + ' does not exist', 'ENORESTARGET');
err.details = !tags.length ?
'No tags found in ' + that._source :
'Available tags: ' + tags.join(', ');
err.details += '\n';
err.details += !branches.length ?
'No branches found in ' + that._source :
'Available branches: ' + branches.join(', ');
throw err;
});
};
SvnResolver.prototype._savePkgMeta = function (meta) {
var version;
if (this._resolution.type === 'version') {
version = semver.clean(this._resolution.tag);
// Warn if the package meta version is different than the resolved one
if (typeof meta.version === 'string' && semver.neq(meta.version, version)) {
this._logger.warn('mismatch', 'Version declared in the json (' + meta.version + ') is different than the resolved one (' + version + ')', {
resolution: this._resolution,
pkgMeta: meta
});
}
// Ensure package meta version is the same as the resolution
meta.version = version;
} else {
// If resolved to a target that is not a version,
// remove the version from the meta
delete meta.version;
}
// Save version/tag/commit in the release
// Note that we can't store branches because _release is supposed to be
// an unique id of this ref.
meta._release = version ||
this._resolution.tag ||
this._resolution.commit;
// Save resolution to be used in hasNew later
meta._resolution = this._resolution;
return Resolver.prototype._savePkgMeta.call(this, meta);
};
// ------------------------------
SvnResolver.versions = function (source, extra) {
source = SvnResolver.getSource(source);
var value = this._cache.versions.get(source);
if (value) {
return Q.resolve(value)
.then(function () {
var versions = this._cache.versions.get(source);
// If no extra information was requested,
// resolve simply with the versions
if (!extra) {
versions = versions.map(function (version) {
return version.version;
});
}
return versions;
}.bind(this));
}
value = this.tags(source)
.then(function (tags) {
var tag;
var version;
var versions = [];
// For each tag
for (tag in tags) {
version = semver.clean(tag);
if (version) {
versions.push({ version: version, tag: tag, commit: tags[tag] });
}
}
// Sort them by DESC order
versions.sort(function (a, b) {
return semver.rcompare(a.version, b.version);
});
this._cache.versions.set(source, versions);
// Call the function again to keep it DRY
return this.versions(source, extra);
}.bind(this));
// Store the promise to be reused until it resolves
// to a specific value
this._cache.versions.set(source, value);
return value;
};
SvnResolver.tags = function (source) {
source = SvnResolver.getSource(source);
var value = this._cache.tags.get(source);
if (value) {
return Q.resolve(value);
}
value = cmd('svn', ['list', source + path.normalize('/tags'), '--verbose'])
.spread(function (stout) {
var tags = SvnResolver.parseSubversionListOutput(stout.toString());
this._cache.tags.set(source, tags);
return tags;
}.bind(this));
// Store the promise to be reused until it resolves
// to a specific value
this._cache.tags.set(source, value);
return value;
};
SvnResolver.branches = function (source) {
source = SvnResolver.getSource(source);
var value = this._cache.branches.get(source);
if (value) {
return Q.resolve(value);
}
value = cmd('svn', ['list', source + '/branches', '--verbose'])
.spread(function (stout) {
var branches = SvnResolver.parseSubversionListOutput(stout.toString());
// trunk is a branch!
branches.trunk = '*';
this._cache.branches.set(source, branches);
return branches;
}.bind(this));
// Store the promise to be reused until it resolves
// to a specific value
this._cache.branches.set(source, value);
return value;
};
SvnResolver.parseSubversionListOutput = function (stout) {
var entries = {};
var lines = stout
.trim()
.split(/[\r\n]+/);
// For each line in the refs, match only the branches
lines.forEach(function (line) {
var match = line.match(/\s+([0-9]+)\s.+\s([\w.$-]+)\//i);
if (match && match[2] !== '.') {
entries[match[2]] = match[1];
}
});
return entries;
};
SvnResolver.clearRuntimeCache = function () {
// Reset cache for branches, tags, etc
mout.object.forOwn(SvnResolver._cache, function (lru) {
lru.reset();
});
};
SvnResolver._cache = {
branches: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
tags: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
versions: new LRU({ max: 50, maxAge: 5 * 60 * 1000 })
};
module.exports = SvnResolver;

View File

@@ -12,8 +12,6 @@ var extract = require('../../util/extract');
var createError = require('../../util/createError');
function UrlResolver(decEndpoint, config, logger) {
var pos;
Resolver.call(this, decEndpoint, config, logger);
// If target was specified, error out
@@ -21,12 +19,12 @@ function UrlResolver(decEndpoint, config, logger) {
throw createError('URL sources can\'t resolve targets', 'ENORESTARGET');
}
// If the name was guessed, remove the ? part
// If the name was guessed
if (this._guessedName) {
pos = this._name.indexOf('?');
if (pos !== -1) {
this._name = path.basename(this._name.substr(0, pos));
}
// Remove the ?xxx part
this._name = this._name.replace(/\?.*$/, '');
// Remove extension
this._name = this._name.substr(0, this._name.length - path.extname(this._name).length);
}
this._remote = url.parse(this._source);
@@ -107,7 +105,8 @@ UrlResolver.prototype._resolve = function () {
// -----------------
UrlResolver.prototype._download = function () {
var file = path.join(this._tempDir, path.basename(this._source));
var fileName = url.parse(path.basename(this._source)).pathname;
var file = path.join(this._tempDir, fileName);
var reqHeaders = {};
var that = this;
@@ -139,9 +138,11 @@ UrlResolver.prototype._download = function () {
}
// Progress
msg = 'received ' + (state.received / 1024 / 1024).toFixed(1) + 'MB ';
msg += 'of ' + (state.total / 1024 / 1024).toFixed(1) + 'MB downloaded, ';
msg += state.percent + '%';
msg = 'received ' + (state.received / 1024 / 1024).toFixed(1) + 'MB';
if (state.total) {
msg += ' of ' + (state.total / 1024 / 1024).toFixed(1) + 'MB downloaded, ';
msg += state.percent + '%';
}
that._logger.info('progress', msg);
})
.then(function (response) {
@@ -197,6 +198,8 @@ UrlResolver.prototype._extract = function (file, response) {
if (mimeType) {
// Clean everything after ; and trim the end result
mimeType = mimeType.split(';')[0].trim();
// Some servers add quotes around the content-type, so we trim that also
mimeType = mout.string.trim(mimeType, ['"', '\'']);
}
if (!extract.canExtract(file, mimeType)) {

View File

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

96
lib/core/scripts.js Normal file
View File

@@ -0,0 +1,96 @@
var mout = require('mout');
var cmd = require('../util/cmd');
var Q = require('q');
var shellquote = require('shell-quote');
var orderByDependencies = function (packages, installed, json) {
var ordered = [];
installed = mout.object.keys(installed);
var depsSatisfied = function (packageName) {
return mout.array.difference(mout.object.keys(packages[packageName].dependencies), installed, ordered).length === 0;
};
var depsFromBowerJson = json && json.dependencies ? mout.object.keys(json.dependencies) : [];
var packageNames = mout.object.keys(packages);
//get the list of the packages that are specified in bower.json in that order
//its nice to maintain that order for users
var desiredOrder = mout.array.intersection(depsFromBowerJson, packageNames);
//then add to the end any remaining packages that werent in bower.json
desiredOrder = desiredOrder.concat(mout.array.difference(packageNames, desiredOrder));
//the desired order isn't necessarily a correct dependency specific order
//so we ensure that below
var resolvedOne = true;
while (resolvedOne) {
resolvedOne = false;
for (var i = 0; i < desiredOrder.length; i++) {
var packageName = desiredOrder[i];
if (depsSatisfied(packageName)) {
ordered.push(packageName);
mout.array.remove(desiredOrder, packageName);
//as soon as we resolve a package start the loop again
resolvedOne = true;
break;
}
}
if (!resolvedOne && desiredOrder.length > 0) {
//if we're here then some package(s) doesn't have all its deps satisified
//so lets just jam those names on the end
ordered = ordered.concat(desiredOrder);
}
}
return ordered;
};
var run = function (cmdString, action, logger, config) {
logger.action(action, cmdString);
//pass env + BOWER_PID so callees can identify a preinstall+postinstall from the same bower instance
var env = mout.object.mixIn({ 'BOWER_PID': process.pid }, process.env);
var args = shellquote.parse(cmdString, env);
var cmdName = args[0];
mout.array.remove(args, cmdName); //no rest() in mout
var options = {
cwd: config.cwd,
env: env
};
var promise = cmd(cmdName, args, options);
promise.progress(function (progress) {
progress.split('\n').forEach(function (line) {
if (line) {
logger.action(action, line);
}
});
});
return promise;
};
var hook = function (action, ordered, config, logger, packages, installed, json) {
if (mout.object.keys(packages).length === 0 || !config.scripts || !config.scripts[action]) {
/*jshint newcap: false */
return Q();
}
var orderedPackages = ordered ? orderByDependencies(packages, installed, json) : mout.object.keys(packages);
var cmdString = mout.string.replace(config.scripts[action], '%', orderedPackages.join(' '));
return run(cmdString, action, logger, config);
};
module.exports = {
preuninstall: mout.function.partial(hook, 'preuninstall', false),
preinstall: mout.function.partial(hook, 'preinstall', true),
postinstall: mout.function.partial(hook, 'postinstall', true),
//only exposed for test
_orderByDependencies: orderByDependencies
};

View File

@@ -1,11 +1,12 @@
var abbrev = require('abbrev');
var mout = require('mout');
var commands = require('./commands');
var PackageRepository = require('./core/PackageRepository');
var pkg = require('../package.json');
var abbreviations = abbrev(expandNames(commands));
abbreviations.i = 'install';
abbreviations.rm = 'uninstall';
abbreviations.unlink = 'uninstall';
abbreviations.ls = 'list';
function expandNames(obj, prefix, stack) {
@@ -29,12 +30,14 @@ function clearRuntimeCache() {
// Note that in edge cases, some architecture components instance's
// in-memory cache might be skipped.
// If that's a problem, you should create and fresh instances instead.
var PackageRepository = require('./core/PackageRepository');
PackageRepository.clearRuntimeCache();
}
module.exports = {
version: pkg.version,
commands: commands,
config: require('./config'),
config: require('./config')(),
abbreviations: abbreviations,
reset: clearRuntimeCache
};

View File

@@ -65,8 +65,9 @@ JsonRenderer.prototype.prompt = function (prompts) {
// Prompt
opts = {
silent: true,
default: prompt.default,
silent: true, // To not mess with JSON output
trim: false, // To allow " " to not assume the default value
default: prompt.default == null ? '' : prompt.default, // If default is null, make it '' so that it does not retry
validator: !prompt.validate ? null : function (value) {
var ret = prompt.validate(value);
@@ -87,6 +88,9 @@ JsonRenderer.prototype.prompt = function (prompts) {
case 'password':
funcName = prompt.type;
break;
case 'checkbox':
funcName = 'prompt';
break;
default:
promise = promise.then(function () {
throw createError('Unknown prompt type', 'ENOTSUP');
@@ -104,6 +108,12 @@ JsonRenderer.prototype.prompt = function (prompts) {
answers[prompt.name] = answer;
});
});
if (prompt.type === 'checkbox') {
promise = promise.then(function () {
answers[prompt.name] = answers[prompt.name].split(',');
});
}
});
return promise.then(function () {
@@ -111,8 +121,6 @@ JsonRenderer.prototype.prompt = function (prompts) {
});
};
JsonRenderer.prototype.updateAvailable = 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) {
@@ -221,6 +229,11 @@ StandardRenderer.prototype._link = function (data) {
level: 'info',
message: data.dst + ' > ' + data.src
});
// Print also a tree of the installed packages
if (data.installed) {
this._install(data.installed);
}
};
StandardRenderer.prototype._register = function (data) {
@@ -389,13 +402,19 @@ StandardRenderer.prototype._write = function (stream, str) {
};
StandardRenderer.prototype._highlightJson = function (json) {
var cardinal = require('cardinal');
return cardinal.highlight(stringifyObject(json, { indent: ' ' }), {
theme: {
String: {
_default: chalk.cyan
_default: function (str) {
return chalk.cyan(str);
}
},
Identifier: {
_default: chalk.green
_default: function (str) {
return chalk.green(str);
}
}
},
json: true
@@ -414,7 +433,7 @@ StandardRenderer.prototype._tree2archy = function (node) {
// State labels
if (node.missing) {
label += chalk.red(' missing');
label += chalk.red(' not installed');
return label;
}
@@ -433,8 +452,6 @@ StandardRenderer.prototype._tree2archy = function (node) {
}
// New versions
// TODO: Improve the new versions message labelling below to be less confusing
// for the user
if (node.update) {
update = '';
@@ -465,6 +482,7 @@ StandardRenderer.prototype._tree2archy = function (node) {
StandardRenderer._wideCommands = [
'install',
'update',
'link',
'info',
'home',
'register'

113
lib/util/analytics.js Normal file
View File

@@ -0,0 +1,113 @@
var Q = require('q');
var mout = require('mout');
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');
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.
//
// This method is called only from bin/bower. Programmatic API skips it.
analytics.setup = function setup (config) {
var deferred = Q.defer();
// No need for asking if analytics is set in bower config
if (config.analytics === undefined) {
ensureInsight();
// For non-interactive call from bin/bower we disable analytics
if (config.interactive) {
if (insight.optOut !== undefined) {
deferred.resolve(!insight.optOut);
} else {
insight.askPermission(null, function(err, optIn) {
// optIn callback param was exactly opposite before 0.4.3
// so we force at least insight@0.4.3 in package.json
deferred.resolve(optIn);
});
}
} else {
// 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 {
// use the specified value
deferred.resolve(config.analytics);
}
return deferred.promise.then(function (enabled) {
enableAnalytics = enabled;
return enabled;
});
};
var Tracker = analytics.Tracker = function Tracker(config) {
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() {
insight.track.apply(insight, arguments);
};
Tracker.prototype.trackDecomposedEndpoints = function trackDecomposedEndpoints(command, endpoints) {
endpoints.forEach(function (endpoint) {
this.track(command, endpoint.source, endpoint.target);
}.bind(this));
};
Tracker.prototype.trackPackages = function trackPackages(command, packages) {
mout.object.forOwn(packages, function (package) {
var meta = package.pkgMeta;
this.track(command, meta.name, meta.version);
}.bind(this));
};
Tracker.prototype.trackNames = function trackNames(command, names) {
names.forEach(function (name) {
this.track(command, name);
}.bind(this));
};

View File

@@ -3,8 +3,18 @@ var path = require('path');
var Q = require('q');
var mout = require('mout');
var which = require('which');
var PThrottler = require('p-throttler');
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. 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;
var isWin = process.platform === 'win32';
@@ -44,7 +54,7 @@ function getWindowsCommand(command) {
// If an error occurs, a meaningful error is generated
// Returns a promise that gets fulfilled if the command succeeds
// or rejected if it fails
function cmd(command, args, options) {
function executeCmd(command, args, options) {
var process;
var stderr = '';
var stdout = '';
@@ -89,7 +99,7 @@ function cmd(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
});
@@ -103,4 +113,8 @@ function cmd(command, args, options) {
return deferred.promise;
}
function cmd(command, args, options) {
return throttler.enqueue(executeCmd.bind(null, command, args, options));
}
module.exports = cmd;

View File

@@ -17,7 +17,7 @@ function createLink(src, dst, type) {
.fail(function (error) {
if (error.code === 'ENOENT') {
throw createError('Failed to create link to ' + path.basename(src), 'ENOENT', {
details: src + ' doest not exists or points to a non-existent file'
details: src + ' does not exist or points to a non-existent file'
});
}

View File

@@ -32,6 +32,8 @@ function download(url, file, options) {
operation.attempt(function () {
var req;
var writeStream;
var contentLength;
var bytesDownloaded = 0;
req = progress(request(url, options), {
delay: progressDelay
@@ -44,10 +46,23 @@ function download(url, file, options) {
}
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) {
var timeout = operation._timeouts[0];

View File

@@ -1,8 +1,8 @@
var path = require('path');
var fs = require('graceful-fs');
var zlib = require('zlib');
var unzip = require('unzip');
var tar = require('tar');
var DecompressZip = require('decompress-zip');
var tar = require('tar-fs');
var Q = require('q');
var mout = require('mout');
var junk = require('junk');
@@ -22,6 +22,8 @@ extractors = {
'.tgz': extractTarGz,
'.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
@@ -32,15 +34,14 @@ extractorTypes = Object.keys(extractors);
function extractZip(archive, dst) {
var deferred = Q.defer();
fs.createReadStream(archive)
new DecompressZip(archive)
.on('error', deferred.reject)
.pipe(unzip.Extract({
.on('extract', deferred.resolve.bind(deferred, dst))
.extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
}))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
});
return deferred.promise;
}
@@ -50,13 +51,13 @@ function extractTar(archive, dst) {
fs.createReadStream(archive)
.on('error', deferred.reject)
.pipe(tar.Extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
.pipe(tar.extract(dst, {
ignore: isSymlink, // Filter symlink files
dmode: 0555, // Ensure dirs are readable
fmode: 0444 // Ensure files are readable
}))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
.on('finish', deferred.resolve.bind(deferred, dst));
return deferred.promise;
}
@@ -68,13 +69,13 @@ function extractTarGz(archive, dst) {
.on('error', deferred.reject)
.pipe(zlib.createGunzip())
.on('error', deferred.reject)
.pipe(tar.Extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
.pipe(tar.extract(dst, {
ignore: isSymlink, // Filter symlink files
dmode: 0555, // Ensure dirs are readable
fmode: 0444 // Ensure files are readable
}))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
.on('finish', deferred.resolve.bind(deferred, dst));
return deferred.promise;
}
@@ -93,6 +94,10 @@ function extractGz(archive, dst) {
return deferred.promise;
}
function isSymlink(entry) {
return entry.type === 'SymbolicLink';
}
function filterSymlinks(entry) {
return entry.type !== 'SymbolicLink';
}
@@ -155,15 +160,11 @@ function moveSingleDirContents(dir) {
// -----------------------------
function canExtract(src, mimeType) {
if (getExtractor(src)) {
return true;
if (mimeType && mimeType !== 'application/octet-stream') {
return !!getExtractor(mimeType);
}
if (mimeType && getExtractor(mimeType)) {
return true;
}
return false;
return !!getExtractor(src);
}
// Available options:

View File

@@ -1,43 +1,58 @@
var util = require('util');
var path = require('path');
var rimraf = require('rimraf');
var IgnoreReader = require('fstream-ignore');
var fstreamIgnore = require('fstream-ignore');
var mout = require('mout');
var Q = require('q');
// Special reader class that only emits entries
// for files that were ignored, instead of the opposite
var IgnoreMatcher = function () {
return IgnoreReader.apply(this, arguments);
};
util.inherits(IgnoreMatcher, IgnoreReader);
// --------
IgnoreMatcher.prototype.applyIgnores = function () {
return !IgnoreReader.prototype.applyIgnores.apply(this, arguments);
};
// --------
function removeIgnores(dir, ignore) {
function removeIgnores(dir, meta) {
var reader;
var applyIgnores;
var deferred = Q.defer();
var files = [];
var ignored = [];
var nonIgnored = ['bower.json'];
reader = new IgnoreMatcher({
// 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;
reader.applyIgnores = function (entry) {
var ret = applyIgnores.apply(this, arguments);
if (!ret) {
ignored.push(path.join(dir, entry));
}
return ret;
};
reader
.on('entry', function (entry) {
files.push(entry.path);
.on('child', function (entry) {
nonIgnored.push(entry.path);
})
.on('error', deferred.reject)
.on('end', function () {
var promises = files.map(function (file) {
var promises;
// Ensure that we are not ignoring files that should not be ignored!
ignored = mout.array.unique(ignored);
ignored = ignored.filter(function (file) {
return nonIgnored.indexOf(file) === -1;
});
// Delete all the ignored files
promises = ignored.map(function (file) {
return Q.nfcall(rimraf, file);
});

View File

@@ -1,8 +1,7 @@
/*jshint multistr:true*/
var sudoBlock = require('sudo-block');
'use strict';
var isRoot = require('is-root');
var createError = require('./createError');
var cli = require('./cli');
var renderer;
@@ -10,7 +9,7 @@ function rootCheck(options, config) {
var errorMsg;
// Allow running the command as root
if (options.allowRoot) {
if (options.allowRoot || config.allowRoot) {
return;
}
@@ -22,7 +21,8 @@ http://www.joyent.com/blog/installing-node-and-npm\n\
https://gist.github.com/isaacs/579814\n\n\
You can however run a command with sudo using --allow-root option';
if (sudoBlock.isRoot) {
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

@@ -7,7 +7,7 @@ function maxSatisfying(versions, range, strictMatch) {
// Filter only valid versions, since semver.maxSatisfying() throws an error
versions = versions.filter(function (version) {
return !!semver.valid(version);
return semver.valid(version);
});
// Exact version & range match
@@ -20,11 +20,11 @@ function maxSatisfying(versions, range, strictMatch) {
}
}
// When strict match is enabled and range is *,
// give priority to non-pre-releases
// We do this by filtering every pre-release version
range = typeof range === 'string' ? range.trim() : range;
if (strictMatch && (!range || range === '*')) {
// When strict match is enabled give priority to non-pre-releases
// We do this by filtering every pre-release version
if (strictMatch) {
filteredVersions = versions.map(function (version) {
return !isPreRelease(version) ? version : null;
});

View File

@@ -5,13 +5,13 @@ function validLink(file) {
// Ensures that a file is a symlink that points
// to a valid file
return Q.nfcall(fs.lstat, file)
.then(function (stat) {
if (!stat.isSymbolicLink()) {
.then(function (lstat) {
if (!lstat.isSymbolicLink()) {
return [false];
}
return Q.nfcall(fs.stat, file)
.then(function () {
.then(function (stat) {
return [stat];
});
})

View File

@@ -1,7 +1,7 @@
{
"name": "bower",
"version": "1.2.1",
"description": "The browser package manager.",
"version": "1.3.12",
"description": "The browser package manager",
"author": "Twitter",
"licenses": [
{
@@ -9,64 +9,72 @@
"url": "https://github.com/bower/bower/blob/master/LICENSE"
}
],
"repository": {
"type": "git",
"url": "git://github.com/bower/bower.git"
},
"repository": "bower/bower",
"main": "lib",
"homepage": "http://bower.io",
"engines": {
"node": ">=0.8.0"
"node": ">=0.10.0"
},
"dependencies": {
"abbrev": "~1.0.4",
"archy": "0.0.2",
"bower-config": "~0.4.0",
"bower-endpoint-parser": "~0.2.0",
"bower-json": "~0.4.0",
"bower-logger": "~0.2.0",
"bower-registry-client": "~0.1.2",
"cardinal": "~0.4.0",
"chalk": "~0.2.0",
"chmodr": "~0.1.0",
"fstream": "~0.1.22",
"fstream-ignore": "~0.0.6",
"glob": "~3.2.1",
"graceful-fs": "~2.0.0",
"handlebars": "~1.0.11",
"inquirer": "~0.2.2",
"junk": "~0.2.0",
"mkdirp": "~0.3.5",
"mout": "~0.6.0",
"nopt": "~2.1.1",
"lru-cache": "~2.3.0",
"open": "~0.0.3",
"promptly": "~0.2.0",
"q": "~0.9.2",
"request": "~2.25.0",
"request-progress": "~0.2.0",
"retry": "~0.6.0",
"rimraf": "~2.2.0",
"semver": "~2.1.0",
"stringify-object": "~0.1.4",
"sudo-block": "~0.2.0",
"tar": "~0.1.17",
"tmp": "~0.0.20",
"unzip": "~0.1.7",
"update-notifier": "~0.1.3",
"which": "~1.0.5"
"abbrev": "^1.0.5",
"archy": "1.0.0",
"bower-config": "^0.5.2",
"bower-endpoint-parser": "^0.2.2",
"bower-json": "^0.4.0",
"bower-logger": "^0.2.2",
"bower-registry-client": "^0.2.1",
"cardinal": "0.4.4",
"chalk": "^1.0.0",
"chmodr": "0.1.0",
"decompress-zip": "^0.1.0",
"fstream": "^1.0.3",
"fstream-ignore": "^1.0.2",
"glob": "^4.3.2",
"graceful-fs": "^3.0.5",
"handlebars": "^2.0.0",
"inquirer": "0.8.0",
"insight": "^0.5.0",
"is-root": "^1.0.0",
"junk": "^1.0.0",
"lockfile": "^1.0.0",
"lru-cache": "^2.5.0",
"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.51.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.0",
"grunt-contrib-jshint": "~0.6.0",
"grunt-exec": "~0.4.2",
"mocha": "~1.12.0",
"nock": "~0.22.0",
"istanbul": "~0.1.42",
"proxyquire": "~0.4.1"
"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": "^0.56.0",
"node-uuid": "^1.4.2",
"proxyquire": "^1.3.0"
},
"scripts": {
"test": "grunt test"

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

@@ -1,6 +1,6 @@
{
"command": "link",
"description": "The link functionality allows developers to easily test their packages.\nLinking is a two-step process.\n\nUsing 'bower link' in a project folder will create a global link.\nThen, in some other package, 'bower link <name>' will create a link in the components folder pointing to the previously created link.\n\nThis allows to easily test a package because changes will be reflected immediately.\nPlease note that bower will not fetch the linked package dependencies.\n\nBower will overwrite the link when installing/updating.",
"description": "The link functionality allows developers to easily test their packages.\nLinking is a two-step process.\n\nUsing 'bower link' in a project folder will create a global link.\nThen, in some other package, 'bower link <name>' will create a link in the components folder pointing to the previously created link.\n\nThis allows to easily test a package because changes will be reflected immediately.\nWhen the link is no longer necessary, simply remove it with 'bower uninstall <name>'.",
"usage": [
"link [<options>]",
"link <name> [<local name>] [<options>]"

View File

@@ -1,6 +1,6 @@
{
"command": "list",
"description": "List local packages.",
"description": "List local packages - and possible updates.",
"usage": [
"list [<options>]"
],

View File

@@ -11,14 +11,14 @@
"description": "Show this help message"
},
{
"shorthand": "-s",
"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": "version",
"description": "Run this in a package directory to bump the version and write the new data back to the bower.json file.\n\nThe newversion argument should be a valid semver string, or a valid second argument to semver.inc (one of \"build\", \"patch\", \"minor\", or \"major\"). In the second case, the existing version will be incremented\nby 1 in the specified field.\n\nIf run in a git repo, it will also create a version commit and tag, and fail if the repo is not clean.\n\nIf supplied with --message (shorthand: -m) config option, bower will use it as a commit message when creating a version commit. If the message config contains %s then that will be replaced with the resulting\nversion number. For example:\n\n bower version patch -m \"Upgrade to %s for reasons\"",
"usage": [
"version [<newversion> | major | minor | patch]"
],
"options": [
{
"shorthand": "-m",
"flag": "--message",
"description": "Custom git commit and tag message"
}
]
}

View File

@@ -10,13 +10,14 @@
"init": "Interactively create a bower.json file",
"install": "Install a package locally",
"link": "Symlink a package folder",
"list": "List local packages",
"list": "List local packages - and possible updates",
"lookup": "Look up a package URL by name",
"prune": "Removes local extraneous packages",
"register": "Register a package",
"search": "Search for a package by name",
"update": "Update a local package",
"uninstall": "Remove a local package"
"uninstall": "Remove a local package",
"version": "Bump a package version"
},
"options": [
{
@@ -57,6 +58,14 @@
{
"flag": "--allow-root",
"description": "Allows running commands as root"
},
{
"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}} depend on {{#cyan}}{{endpoint.name}}#{{endpoint.target}}{{/cyan}}{{#if pkgMeta._release}} which resolved to {{#white}}{{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.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 has {{#white}}{{dependants}}{{/white}} as dependants{{/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

@@ -1,5 +1,5 @@
Package {{#cyan}}{{name}}{{/cyan}} registered successfully!
All valid semver tags on {{#cyan}}{{url}}{{/cyan}} will be available as versions.
To publish a new version, you just need release a valid semver tag.
To publish a new version, just release a valid semver tag.
Run {{#cyan}}bower info {{name}}{{/cyan}} to list the package versions.
Run {{#cyan}}bower info {{name}}{{/cyan}} to list the available versions.

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

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

View File

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

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 helpers = require('../../helpers');
var cacheClean = helpers.command('cache/clean');
var md5 = helpers.require('lib/util/md5');
var object = require('mout/object');
describe('bower cache clean', function () {
// Because directory names are required to be mp5 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 () {
return package.prepareGit({}).then(function() {
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'
]
});
});
});
});
});

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

@@ -0,0 +1,85 @@
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',
version: 'test-version',
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',
version: 'test-version',
homepage: 'test-homepage',
authors: [ 'test-author' ],
description: 'test-description',
moduleType: 'test-moduleType',
keywords: [ 'test-keyword' ],
license: 'test-license'
});
});
});
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'
);
});
});
});

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

@@ -0,0 +1,276 @@
var expect = require('expect.js');
var helpers = require('../helpers');
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': {
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('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': {
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('0.1.0');
});
});
it('does not write to bower.json if only --save-exact flag is used', function() {
package.prepare({
'bower.json': {
version: '1.2.3'
}
});
tempDir.prepare({
'bower.json': {
name: 'test'
}
});
return helpers.run(install, [[package.path], { saveExact: true }]).then(function() {
expect(tempDir.read('bower.json')).to.not.contain('dependencies');
expect(tempDir.read('bower.json')).to.not.contain('devDependencies');
});
});
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('works for git repositories', function () {
return gitPackage.prepareGit({
'1.0.0': {
'bower.json': {
name: 'package'
},
'version.txt': '1.0.0'
},
'1.0.1': {
'bower.json': {
name: 'package'
},
'version.txt': '1.0.1'
}
}).then(function() {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
}
});
return helpers.run(install).then(function() {
expect(tempDir.read('bower_components/package/version.txt')).to.contain('1.0.0');
});
});
});
});

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

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

@@ -0,0 +1,254 @@
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 () {
return 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'
}
}).then(function() {
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 () {
return 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'
}
}).then(function() {
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')
);
});
});
});
});
});

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

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

@@ -0,0 +1,125 @@
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 url is not correct', function () {
return helpers.run(register, ['some-name', 'url'])
.fail(function(reason) {
expect(reason.message).to.be('The registry only accepts URLs starting with git://');
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('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({});
});
});
});

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

@@ -0,0 +1,246 @@
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 gitPackage = new helpers.TempDir();
gitPackage.prepareGit({
'1.0.0': {
'bower.json': {
name: 'package'
},
'version.txt': '1.0.0'
},
'1.0.1': {
'bower.json': {
name: 'package'
},
'version.txt': '1.0.1'
}
});
var package = new helpers.TempDir({
'bower.json': {
name: 'package'
}
}).prepare();
var updateLogger = function(packages, options, config) {
config = object.merge(config || {}, {
cwd: tempDir.path
});
return commands.update(packages, options, config);
};
var update = function(packages, options, config) {
var logger = updateLogger(packages, options, config);
return helpers.expectEvent(logger, 'end');
};
var install = function(packages, options, config) {
config = object.merge(config || {}, {
cwd: tempDir.path
});
var logger = commands.install(
packages, options, config
);
return helpers.expectEvent(logger, 'end');
};
it('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('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('runs preinstall hook when updating a package', function () {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
},
'.bowerrc': {
scripts: {
preinstall: 'node -e \'require("fs").writeFileSync("preinstall.txt", "%")\''
}
}
});
return install().then(function() {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.1'
}
}
});
expect(tempDir.exists('preinstall.txt')).to.be(false);
return update().then(function() {
expect(tempDir.read('preinstall.txt')).to.be('package');
});
});
});
it('runs postinstall hook when updating a package', function () {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.0'
}
},
'.bowerrc': {
scripts: {
preinstall: 'node -e \'require("fs").writeFileSync("preinstall.txt", "%")\'',
postinstall: 'node -e \'require("fs").writeFileSync("postinstall.txt", "%")\''
}
}
});
return install().then(function() {
tempDir.prepare({
'bower.json': {
name: 'test',
dependencies: {
package: gitPackage.path + '#1.0.1'
}
}
});
expect(tempDir.exists('postinstall.txt')).to.be(false);
return update().then(function() {
expect(tempDir.read('postinstall.txt')).to.be('package');
});
});
});
});

91
test/commands/version.js Normal file
View File

@@ -0,0 +1,91 @@
var expect = require('expect.js');
var helpers = require('../helpers');
var version = helpers.require('lib/commands').version;
describe('bower list', function () {
var package = new helpers.TempDir({
'bower.json': {
name: 'foobar',
version: '0.0.0'
}
});
var gitPackage = new helpers.TempDir({
'v0.0.0': {
'bower.json': {
name: 'foobar',
version: '0.0.0'
}
}
});
it('bumps patch version', function() {
package.prepare();
return helpers.run(version, ['patch', {}, { cwd: package.path }]).then(function() {
expect(package.readJson('bower.json').version).to.be('0.0.1');
});
});
it('bumps minor version', function() {
package.prepare();
return helpers.run(version, ['minor', {}, { cwd: package.path }]).then(function() {
expect(package.readJson('bower.json').version).to.be('0.1.0');
});
});
it('bumps major version', function() {
package.prepare();
return helpers.run(version, ['major', {}, { cwd: package.path }]).then(function() {
expect(package.readJson('bower.json').version).to.be('1.0.0');
});
});
it('changes version', function() {
package.prepare();
return helpers.run(version, ['1.2.3', {}, { cwd: package.path }]).then(function() {
expect(package.readJson('bower.json').version).to.be('1.2.3');
});
});
it('bumps patch version, create commit, and tag', function() {
return gitPackage.prepareGit().then(function() {
return helpers.run(version, ['patch', {}, { cwd: gitPackage.path }]).then(function() {
expect(gitPackage.readJson('bower.json').version).to.be('0.0.1');
return gitPackage.git('tag').spread(function(result) {
expect(result).to.be('v0.0.0\nv0.0.1\n');
return gitPackage.git('log', '--pretty=format:%s', '-n1').spread(function(result) {
expect(result).to.be('v0.0.1');
});
});
});
});
});
it('bumps with custom commit message', function() {
return gitPackage.prepareGit().then(function() {
return helpers.run(version, ['patch', { message: 'Bumping %s, because what'}, { cwd: gitPackage.path }]).then(function() {
expect(gitPackage.readJson('bower.json').version).to.be('0.0.1');
return gitPackage.git('tag').spread(function(result) {
expect(result).to.be('v0.0.0\nv0.0.1\n');
return gitPackage.git('log', '--pretty=format:%s', '-n1').spread(function(result) {
expect(result).to.be('Bumping 0.0.1, because what');
});
});
});
});
});
});

270
test/core/Manager.js Normal file
View File

@@ -0,0 +1,270 @@
var expect = require('expect.js');
var path = require('path');
var rimraf = require('rimraf');
var Logger = require('bower-logger');
var Manager = require('../../lib/core/Manager');
var defaultConfig = require('../../lib/config');
describe('Manager', function () {
var manager;
var packagesCacheDir =
path.join(__dirname, '../assets/temp-resolve-cache');
var registryCacheDir =
path.join(__dirname, '../assets/temp-registry-cache');
after(function () {
rimraf.sync(registryCacheDir);
rimraf.sync(packagesCacheDir);
});
beforeEach(function (next) {
var logger = new Logger();
var config = defaultConfig({
storage: {
packages: packagesCacheDir,
registry: registryCacheDir
}
});
manager = new Manager(config, logger);
next();
});
describe('_areCompatible', function () {
describe('resolved is being fetched', function() {
it('accepts endpoints with same targets', function () {
expect(manager._areCompatible(
{ name: 'foo', target: 'xxx' },
{ name: 'bar', target: 'xxx' }
)).to.be(true);
});
it('rejects endpoints with different targets', function () {
expect(manager._areCompatible(
{ name: 'foo', target: 'xxx' },
{ name: 'bar', target: 'yyy' }
)).to.be(false);
});
it('accepts with version and matching range', function () {
expect(manager._areCompatible(
{ name: 'foo', target: '0.1.2' },
{ name: 'bar', target: '~0.1.0' }
)).to.be(true);
});
it('rejects with version and non-matching range', function () {
expect(manager._areCompatible(
{ name: 'foo', target: '0.1.2' },
{ name: 'bar', target: '~0.1.3' }
)).to.be(false);
});
it('accepts with matching range and version', function () {
expect(manager._areCompatible(
{ name: 'foo', target: '~0.1.0' },
{ name: 'bar', target: '0.1.2' }
)).to.be(true);
});
it('accepts with non-matching range and version', function () {
expect(manager._areCompatible(
{ name: 'foo', target: '~0.1.3' },
{ name: 'bar', target: '0.1.2' }
)).to.be(false);
});
it('accepts with matching ranges', function () {
expect(manager._areCompatible(
{ name: 'foo', target: '~0.1.0' },
{ name: 'bar', target: '~0.1.3' }
)).to.be(true);
});
it('rejects with non-matching ranges', function () {
expect(manager._areCompatible(
{ name: 'foo', target: '~0.1.0' },
{ name: 'bar', target: '~0.2.3' }
)).to.be(false);
});
it('rejects with non-matching ranges', function () {
expect(manager._areCompatible(
{ name: 'foo', target: '~0.1.0' },
{ name: 'bar', target: 'xxx' }
)).to.be(false);
});
});
describe('resolved is already fetched', function () {
var resolved = {
name: 'foo',
target: '~1.2.1',
pkgMeta: {
version: '1.2.3'
}
};
it('accepts if the same version as resolved', function () {
expect(manager._areCompatible(
{ name: 'foo', target: '1.2.3' },
resolved
)).to.be(true);
});
it('rejects if different version than resolved', function () {
expect(manager._areCompatible(
{ name: 'foo', target: '1.2.4' },
resolved
)).to.be(false);
});
it('accepts if range matches resolved version', function () {
expect(manager._areCompatible(
{ name: 'foo', target: '~1.2.1' },
resolved
)).to.be(true);
});
it('rejects if range does not match', function () {
expect(manager._areCompatible(
{ name: 'foo', target: '~1.2.4' },
resolved
)).to.be(false);
});
});
});
describe('_getCap', function () {
it('finds highest bound', function () {
var highest = manager._getCap(
[['2.1.1-0', '<2.2.0-0'], '<3.2.0'],
'highest'
);
expect(highest).to.eql({
version: '3.2.0',
comparator: '<'
});
});
it('finds lowest bound', function () {
var highest = manager._getCap(
[['2.1.1-0', '<2.2.0-0'], '<3.2.0'],
'lowest'
);
expect(highest).to.eql({
version: '2.1.1-0',
comparator: ''
});
});
it('defaults to highest bound', function () {
var highest = manager._getCap(
['1.0.0', '2.0.0']
);
expect(highest).to.eql({
version: '2.0.0',
comparator: ''
});
});
it('ignores non-semver elements', function () {
var highest = manager._getCap(
['0.9', '>1.0.1', ['<1.0.0', 'lol']]
);
expect(highest).to.eql({
version: '1.0.1',
comparator: '>'
});
});
it('returns empty object if cap is not found', function () {
var highest = manager._getCap(
['0.9'] // Not a semver
);
expect(highest).to.eql({});
});
});
describe('_uniquify', function () {
it('leaves last unique element', function () {
var unique = manager._uniquify([
{ name: 'foo', id: 1 },
{ name: 'foo', id: 2 }
]);
expect(unique).to.eql([
{ name: 'foo', id: 2 }
]);
});
it('compares by name first', function () {
var unique = manager._uniquify([
{ name: 'foo', source: 'google.com' },
{ name: 'foo', source: 'facebook.com' }
]);
expect(unique).to.eql([
{ name: 'foo', source: 'facebook.com' }
]);
});
it('compares by source if name is not available', function () {
var unique = manager._uniquify([
{ source: 'facebook.com' },
{ source: 'facebook.com' }
]);
expect(unique).to.eql([
{ source: 'facebook.com' }
]);
});
it('leaves different targets intact', function() {
var unique = manager._uniquify([
{ source: 'facebook.com', target: 'a1b2c3' },
{ source: 'facebook.com', target: 'ffffff' }
]);
expect(unique).to.eql([
{ source: 'facebook.com', target: 'a1b2c3' },
{ source: 'facebook.com', target: 'ffffff' }
]);
});
it('removes if same targets', function() {
var unique = manager._uniquify([
{ source: 'facebook.com', target: 'ffffff' },
{ source: 'facebook.com', target: 'ffffff' }
]);
expect(unique).to.eql([
{ source: 'facebook.com', target: 'ffffff' }
]);
});
it('ignores other fields', function() {
var unique = manager._uniquify([
{ source: 'facebook.com', foo: 12 },
{ source: 'facebook.com', bar: 13 }
]);
expect(unique).to.eql([
{ source: 'facebook.com', bar: 13 }
]);
});
});
});

View File

@@ -11,6 +11,7 @@ var defaultConfig = require('../../lib/config');
var ResolveCache = require('../../lib/core/ResolveCache');
var resolvers = require('../../lib/core/resolvers');
var copy = require('../../lib/util/copy');
var helpers = require('../helpers');
describe('PackageRepository', function () {
var packageRepository;
@@ -18,10 +19,12 @@ describe('PackageRepository', function () {
var resolverFactoryHook;
var resolverFactoryClearHook;
var testPackage = path.resolve(__dirname, '../assets/package-a');
var tempPackage = path.resolve(__dirname, '../assets/temp');
var packagesCacheDir = path.join(__dirname, '../assets/temp-resolve-cache');
var registryCacheDir = path.join(__dirname, '../assets/temp-registry-cache');
var mockSource = 'file://' + testPackage;
var tempPackage = path.resolve(__dirname, '../tmp/temp-package');
var packagesCacheDir = path.join(__dirname, '../tmp/temp-resolve-cache');
var registryCacheDir = path.join(__dirname, '../tmp/temp-registry-cache');
var mockSource = helpers.localSource(testPackage);
var forceCaching = true;
after(function () {
rimraf.sync(registryCacheDir);
@@ -34,7 +37,7 @@ describe('PackageRepository', function () {
var logger = new Logger();
// Config
config = mout.object.deepMixIn({}, defaultConfig, {
config = defaultConfig({
storage: {
packages: packagesCacheDir,
registry: registryCacheDir
@@ -51,12 +54,20 @@ describe('PackageRepository', function () {
decEndpoint.source = mockSource;
resolver = new resolvers.GitRemote(decEndpoint, _config, _logger);
if (forceCaching) {
// Force to use cache even for local resources
resolver.isCacheable = function () {
return true;
};
}
resolverFactoryHook(resolver);
return Q.resolve(resolver);
}
resolverFactory.getConstructor = function () {
return Q.resolve([resolvers.GitRemote, 'file://' + testPackage, false]);
return Q.resolve([resolvers.GitRemote, helpers.localSource(testPackage), false]);
};
resolverFactory.clearRuntimeCache = function () {
resolverFactoryClearHook();
@@ -140,7 +151,7 @@ describe('PackageRepository', function () {
});
it('should attempt to retrieve a resolved package from the resolve package', function (next) {
var called;
var called = false;
var originalRetrieve = packageRepository._resolveCache.retrieve;
packageRepository._resolveCache.retrieve = function (source) {
@@ -161,6 +172,31 @@ describe('PackageRepository', function () {
.done();
});
it('should avoid using cache for local resources', function (next) {
forceCaching = false;
var called = false;
var originalRetrieve = packageRepository._resolveCache.retrieve;
packageRepository._resolveCache.retrieve = function (source) {
called = true;
expect(source).to.be(mockSource);
return originalRetrieve.apply(this, arguments);
};
packageRepository.fetch({ name: '', source: helpers.localSource(testPackage), target: '~0.1.0' })
.spread(function (canonicalDir, pkgMeta) {
expect(called).to.be(false);
expect(fs.existsSync(canonicalDir)).to.be(true);
expect(pkgMeta).to.be.an('object');
expect(pkgMeta.name).to.be('package-a');
expect(pkgMeta.version).to.be('0.1.1');
forceCaching = true;
next();
})
.done();
});
it('should just call the resolver resolve method if no appropriate package was found in the resolve cache', function (next) {
var called = [];
@@ -269,7 +305,7 @@ describe('PackageRepository', function () {
expect(fs.existsSync(canonicalDir)).to.be(true);
expect(pkgMeta).to.be.an('object');
expect(pkgMeta.name).to.be('a');
expect(pkgMeta.version).to.be('0.2.1');
expect(pkgMeta.version).to.be('0.2.2');
next();
});
})

View File

@@ -14,16 +14,16 @@ var md5 = require('../../lib/util/md5');
describe('ResolveCache', function () {
var resolveCache;
var testPackage = path.resolve(__dirname, '../assets/package-a');
var tempPackage = path.resolve(__dirname, '../assets/temp');
var tempPackage2 = path.resolve(__dirname, '../assets/temp2');
var cacheDir = path.join(__dirname, '../assets/temp-resolve-cache');
var tempPackage = path.resolve(__dirname, '../tmp/temp-package');
var tempPackage2 = path.resolve(__dirname, '../tmp/temp2-package');
var cacheDir = path.join(__dirname, '../tmp/temp-resolve-cache');
before(function (next) {
// Delete cache folder
rimraf.sync(cacheDir);
// Instantiate resolver cache
resolveCache = new ResolveCache(mout.object.deepMixIn(defaultConfig, {
resolveCache = new ResolveCache(defaultConfig({
storage: {
packages: cacheDir
}
@@ -55,7 +55,7 @@ describe('ResolveCache', function () {
});
function initialize(cacheDir) {
return new ResolveCache(mout.object.deepMixIn(defaultConfig, {
return new ResolveCache(defaultConfig({
storage: {
packages: cacheDir
}
@@ -138,30 +138,6 @@ describe('ResolveCache', function () {
.done();
});
it('should overwrite if the exact same package source/version exists', function (next) {
var cachePkgDir = path.join(cacheDir, md5('foo'), '1.0.0-rc.blehhh');
mkdirp.sync(cachePkgDir);
fs.writeFileSync(path.join(cachePkgDir, '_bleh'), 'w00t');
resolveCache.store(tempPackage, {
name: 'foo',
version: '1.0.0-rc.blehhh',
_source: 'foo',
_target: '*'
})
.then(function (dir) {
expect(dir).to.equal(cachePkgDir);
expect(fs.existsSync(dir)).to.be(true);
expect(fs.existsSync(path.join(dir, 'baz'))).to.be(true);
expect(fs.existsSync(tempPackage)).to.be(false);
expect(fs.existsSync(path.join(cachePkgDir, '_bleh'))).to.be(false);
next();
})
.done();
});
it('should read the package meta if not present', function (next) {
var pkgMeta = path.join(tempPackage, '.bower.json');
@@ -241,14 +217,14 @@ describe('ResolveCache', function () {
resolveCache.store(tempPackage, {
name: 'foo',
_source: 'foo',
_source: 'foobar',
_target: 'some-branch'
})
.then(function (dir) {
// Ensure mock was called
expect(hittedMock).to.be(true);
expect(dir).to.equal(path.join(cacheDir, md5('foo'), 'some-branch'));
expect(dir).to.equal(path.join(cacheDir, md5('foobar'), 'some-branch'));
expect(fs.existsSync(dir)).to.be(true);
expect(fs.existsSync(path.join(dir, 'baz'))).to.be(true);
expect(fs.existsSync(tempPackage)).to.be(false);
@@ -293,6 +269,47 @@ describe('ResolveCache', function () {
})
.done();
});
it('should url encode target when storing to the fs', function (next) {
resolveCache.store(tempPackage, {
name: 'foo',
_source: 'foo',
_target: 'foo/bar'
})
.then(function (dir) {
expect(dir).to.equal(path.join(cacheDir, md5('foo'), 'foo%2Fbar'));
expect(fs.existsSync(dir)).to.be(true);
expect(fs.existsSync(path.join(dir, 'baz'))).to.be(true);
expect(fs.existsSync(tempPackage)).to.be(false);
next();
})
.done();
});
it('should be possible to store two package at same time', function (next) {
var store = resolveCache.store.bind(resolveCache, tempPackage, {
name: 'foo',
_source: 'foo',
_target: 'foo/bar'
});
var store2 = resolveCache.store.bind(resolveCache, tempPackage2, {
name: 'foo',
_source: 'foo',
_target: 'foo/bar'
});
Q.all([store(), store2()]).then(function (dirs) {
var dir = dirs[0];
expect(dir).to.equal(path.join(cacheDir, md5('foo'), 'foo%2Fbar'));
expect(fs.existsSync(dir)).to.be(true);
expect(fs.existsSync(path.join(dir, 'baz'))).to.be(true);
expect(fs.existsSync(tempPackage)).to.be(false);
expect(fs.existsSync(tempPackage2)).to.be(false);
next();
}).done();
});
});
describe('.versions', function () {
@@ -512,6 +529,7 @@ describe('ResolveCache', function () {
var sourceId = md5(source);
var sourceDir = path.join(cacheDir, sourceId);
var json = { name: 'foo' };
var encoded;
// Create some versions
fs.mkdirSync(sourceDir);
@@ -521,22 +539,25 @@ describe('ResolveCache', function () {
fs.writeFileSync(path.join(sourceDir, '0.1.0', '.bower.json'), JSON.stringify(json, null, ' '));
json.version = '0.1.0+build.4';
fs.mkdirSync(path.join(sourceDir, '0.1.0+build.4'));
fs.writeFileSync(path.join(sourceDir, '0.1.0+build.4', '.bower.json'), JSON.stringify(json, null, ' '));
encoded = encodeURIComponent('0.1.0+build.4');
fs.mkdirSync(path.join(sourceDir, encoded));
fs.writeFileSync(path.join(sourceDir, encoded, '.bower.json'), JSON.stringify(json, null, ' '));
json.version = '0.1.0+build.5';
fs.mkdirSync(path.join(sourceDir, '0.1.0+build.5'));
fs.writeFileSync(path.join(sourceDir, '0.1.0+build.5', '.bower.json'), JSON.stringify(json, null, ' '));
encoded = encodeURIComponent('0.1.0+build.5');
fs.mkdirSync(path.join(sourceDir, encoded));
fs.writeFileSync(path.join(sourceDir, encoded, '.bower.json'), JSON.stringify(json, null, ' '));
json.version = '0.1.0+build.6';
fs.mkdirSync(path.join(sourceDir, '0.1.0+build.6'));
fs.writeFileSync(path.join(sourceDir, '0.1.0+build.6', '.bower.json'), JSON.stringify(json, null, ' '));
encoded = encodeURIComponent('0.1.0+build.6');
fs.mkdirSync(path.join(sourceDir, encoded));
fs.writeFileSync(path.join(sourceDir, encoded, '.bower.json'), JSON.stringify(json, null, ' '));
resolveCache.retrieve(source, '0.1.0+build.5')
.spread(function (canonicalDir, pkgMeta) {
expect(pkgMeta).to.be.an('object');
expect(pkgMeta.version).to.equal('0.1.0+build.5');
expect(canonicalDir).to.equal(path.join(sourceDir, '0.1.0+build.5'));
expect(canonicalDir).to.equal(path.join(sourceDir, encodeURIComponent('0.1.0+build.5')));
next();
})
@@ -887,7 +908,6 @@ describe('ResolveCache', function () {
expect(entries).to.be.an('array');
expectedJson = fs.readFileSync(path.join(__dirname, '../assets/resolve-cache/list-json-1.json'));
expectedJson = expectedJson.toString();
mout.object.forOwn(entries, function (entry) {
// Trim absolute bower path from json
@@ -896,8 +916,7 @@ describe('ResolveCache', function () {
entry.canonicalDir = entry.canonicalDir.replace(/\\/g, '/');
});
json = JSON.stringify(entries, null, ' ');
expect(json).to.equal(expectedJson);
expect(entries).to.eql(JSON.parse(expectedJson));
next();
})

View File

@@ -10,13 +10,14 @@ var Logger = require('bower-logger');
var resolverFactory = require('../../lib/core/resolverFactory');
var resolvers = require('../../lib/core/resolvers');
var defaultConfig = require('../../lib/config');
var helpers = require('../helpers');
describe('resolverFactory', function () {
var tempSource;
var logger = new Logger();
var registryClient = new RegistryClient(mout.object.fillIn({
cache: defaultConfig._registry
}, defaultConfig));
var registryClient = new RegistryClient(defaultConfig({
cache: defaultConfig()._registry
}));
afterEach(function (next) {
logger.removeAllListeners();
@@ -30,11 +31,11 @@ describe('resolverFactory', function () {
});
after(function (next) {
rimraf('dejavu', next);
rimraf('pure', next);
});
function callFactory(decEndpoint, config) {
return resolverFactory(decEndpoint, config || defaultConfig, logger, registryClient);
return resolverFactory(decEndpoint, defaultConfig(config), logger, registryClient);
}
it('should recognize git remote endpoints correctly', function (next) {
@@ -303,6 +304,11 @@ describe('resolverFactory', function () {
temp = path.resolve(__dirname, '../assets/package-a');
endpoints[temp] = temp;
// Absolute path that ends with a /
// See: https://github.com/bower/bower/issues/898
temp = path.resolve(__dirname, '../assets/package-a') + '/';
endpoints[temp] = temp;
// Relative path
endpoints[__dirname + '/../assets/package-a'] = temp;
@@ -334,12 +340,91 @@ describe('resolverFactory', function () {
.done();
});
if (!helpers.hasSvn())
describe.skip('should recognize svn remote endpoints correctly', function() {});
else it('should recognize svn remote endpoints correctly', function (next) {
var promise = Q.resolve();
var endpoints;
endpoints = {
// svn:
'svn://hostname.com/user/project': 'http://hostname.com/user/project',
'svn://hostname.com/user/project/': 'http://hostname.com/user/project',
// svn@:
'svn://svn@hostname.com:user/project': 'http://svn@hostname.com:user/project',
'svn://svn@hostname.com:user/project/': 'http://svn@hostname.com:user/project',
// svn+http
'svn+http://hostname.com/project/blah': 'http://hostname.com/project/blah',
'svn+http://hostname.com/project/blah/': 'http://hostname.com/project/blah',
'svn+http://user@hostname.com/project/blah': 'http://user@hostname.com/project/blah',
'svn+http://user@hostname.com/project/blah/': 'http://user@hostname.com/project/blah',
// svn+https
'svn+https://hostname.com/project/blah': 'https://hostname.com/project/blah',
'svn+https://hostname.com/project/blah/': 'https://hostname.com/project/blah',
'svn+https://user@hostname.com/project/blah': 'https://user@hostname.com/project/blah',
'svn+https://user@hostname.com/project/blah/': 'https://user@hostname.com/project/blah',
// svn+ssh
'svn+ssh://hostname.com/project/blah': 'svn+ssh://hostname.com/project/blah',
'svn+ssh://hostname.com/project/blah/': 'svn+ssh://hostname.com/project/blah',
'svn+ssh://user@hostname.com/project/blah': 'svn+ssh://user@hostname.com/project/blah',
'svn+ssh://user@hostname.com/project/blah/': 'svn+ssh://user@hostname.com/project/blah',
// svn+file
'svn+file:///project/blah': 'file:///project/blah',
'svn+file:///project/blah/': 'file:///project/blah'
};
mout.object.forOwn(endpoints, function (value, key) {
// Test without name and target
promise = promise.then(function () {
return callFactory({ source: key });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.Svn);
expect(resolver).to.not.be(resolvers.GitHub);
expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value);
expect(resolver.getTarget()).to.equal('*');
});
// Test with target
promise = promise.then(function () {
return callFactory({ source: key, target: 'commit-ish' });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.Svn);
expect(resolver).to.not.be(resolvers.GitHub);
expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value);
expect(resolver.getTarget()).to.equal('commit-ish');
});
// Test with name
promise = promise.then(function () {
return callFactory({ name: 'foo', source: key });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.Svn);
expect(resolver).to.not.be(resolvers.GitHub);
expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value);
expect(resolver.getName()).to.equal('foo');
expect(resolver.getTarget()).to.equal('*');
});
});
promise
.then(next.bind(next, null))
.done();
});
it('should recognize local fs files/folder endpoints correctly', function (next) {
var promise = Q.resolve();
var endpoints;
var temp;
tempSource = path.resolve(__dirname, '../assets/tmp');
tempSource = path.resolve(__dirname, '../tmp/tmp');
mkdirp.sync(tempSource);
fs.writeFileSync(path.join(tempSource, '.git'), 'foo');
fs.writeFileSync(path.join(tempSource, 'file.with.multiple.dots'), 'foo');
@@ -349,7 +434,7 @@ describe('resolverFactory', function () {
// Absolute path to folder with .git file
endpoints[tempSource] = tempSource;
// Relative path to folder with .git file
endpoints[__dirname + '/../assets/tmp'] = tempSource;
endpoints[__dirname + '/../tmp/tmp'] = tempSource;
// Absolute path to folder
temp = path.resolve(__dirname, '../assets/test-temp-dir');
@@ -442,31 +527,31 @@ describe('resolverFactory', function () {
});
it('should recognize registry endpoints correctly', function (next) {
// Create a 'dejavu' file at the root to prevent regressions of #666
fs.writeFileSync('dejavu', 'foo');
// Create a 'pure' file at the root to prevent regressions of #666
fs.writeFileSync('pure', 'foo');
callFactory({ source: 'dejavu' })
callFactory({ source: 'pure' })
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitRemote);
expect(resolver.getSource()).to.equal('git://github.com/IndigoUnited/dejavu.git');
expect(resolver.getSource()).to.equal('git://github.com/yui/pure-release.git');
expect(resolver.getTarget()).to.equal('*');
})
.then(function () {
// Test with name
return callFactory({ source: 'dejavu', name: 'foo' })
return callFactory({ source: 'pure', name: 'foo' })
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitRemote);
expect(resolver.getSource()).to.equal('git://github.com/IndigoUnited/dejavu.git');
expect(resolver.getSource()).to.equal('git://github.com/yui/pure-release.git');
expect(resolver.getName()).to.equal('foo');
expect(resolver.getTarget()).to.equal('*');
});
})
.then(function () {
// Test with target
return callFactory({ source: 'dejavu', target: '~2.0.0' })
return callFactory({ source: 'pure', target: '~0.4.0' })
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitRemote);
expect(resolver.getTarget()).to.equal('~2.0.0');
expect(resolver.getTarget()).to.equal('~0.4.0');
next();
});
@@ -489,7 +574,7 @@ describe('resolverFactory', function () {
});
it('should set registry to true on the decomposed endpoint if fetched from the registry', function (next) {
var decEndpoint = { source: 'dejavu' };
var decEndpoint = { source: 'pure' };
callFactory(decEndpoint)
.then(function () {
@@ -502,14 +587,12 @@ describe('resolverFactory', function () {
it('should use the configured shorthand resolver', function (next) {
callFactory({ source: 'bower/bower' })
.then(function (resolver) {
var config;
var config = {
shorthandResolver: 'git://bower.io/{{owner}}/{{package}}/{{shorthand}}'
};
expect(resolver.getSource()).to.equal('git://github.com/bower/bower.git');
config = mout.object.fillIn({
shorthandResolver: 'git://bower.io/{{owner}}/{{package}}/{{shorthand}}'
}, defaultConfig);
return callFactory({ source: 'IndigoUnited/promptly' }, config);
})
.then(function (resolver) {
@@ -534,7 +617,7 @@ describe('resolverFactory', function () {
it('should error out if there\'s no suitable resolver for a given source', function (next) {
resolverFactory({ source: 'some-package-that-will-never-exist' }, defaultConfig, logger)
resolverFactory({ source: 'some-package-that-will-never-exist' }, defaultConfig(), logger)
.then(function () {
throw new Error('Should have failed');
}, function (err) {

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