mirror of
https://github.com/bower/bower.git
synced 2026-04-24 03:00:19 -04:00
Compare commits
641 Commits
v1.2.2
...
fix/window
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a37202dc5 | ||
|
|
12258324d3 | ||
|
|
2adb0b0807 | ||
|
|
19fc84007d | ||
|
|
8fcbd3671d | ||
|
|
0eadbef02d | ||
|
|
107ad1d3fc | ||
|
|
d2ba80e6e9 | ||
|
|
852a586d5c | ||
|
|
e8a2d92785 | ||
|
|
50ed13e4ee | ||
|
|
cb9b737b9d | ||
|
|
700b46162c | ||
|
|
0e1153f610 | ||
|
|
8669ed2aac | ||
|
|
ca0a36abcf | ||
|
|
4c6fdc905f | ||
|
|
cdbc4a123c | ||
|
|
bb7c02b07b | ||
|
|
4f42aeabd7 | ||
|
|
b77517ef64 | ||
|
|
b9c3f750eb | ||
|
|
aaecbfab17 | ||
|
|
c4539aa603 | ||
|
|
7f801319bf | ||
|
|
1a990f4563 | ||
|
|
944a328f30 | ||
|
|
e11b60d812 | ||
|
|
c91e99b782 | ||
|
|
da8ec1e4ab | ||
|
|
e7868f0fb1 | ||
|
|
eca46dbd85 | ||
|
|
67bd5d026f | ||
|
|
51de67cc73 | ||
|
|
3d4f9cd919 | ||
|
|
2f72cd4b7d | ||
|
|
2ff53fc448 | ||
|
|
42cd2e584f | ||
|
|
49de3cca62 | ||
|
|
718db0309f | ||
|
|
89286e628b | ||
|
|
c8042b4781 | ||
|
|
9fdd96c92b | ||
|
|
ce15df27ca | ||
|
|
468657bfe2 | ||
|
|
814180d129 | ||
|
|
93be2fef6d | ||
|
|
3ab71f27ff | ||
|
|
38fa1b6858 | ||
|
|
c6ed215260 | ||
|
|
74eba8d2e8 | ||
|
|
5a72dae2c8 | ||
|
|
6194821643 | ||
|
|
37aab9c72e | ||
|
|
0a0dc8cef9 | ||
|
|
c6d89b79b4 | ||
|
|
8a435dff27 | ||
|
|
459925eba7 | ||
|
|
6614a43658 | ||
|
|
88fd65ae34 | ||
|
|
83da088024 | ||
|
|
51a986d0d4 | ||
|
|
0aefe8fc0e | ||
|
|
2845984169 | ||
|
|
2a91dc5fb1 | ||
|
|
5eca9274ee | ||
|
|
d57d81ca85 | ||
|
|
a5dcf9cc24 | ||
|
|
484c8985ed | ||
|
|
931b0a8905 | ||
|
|
bfd1e93325 | ||
|
|
bf23751549 | ||
|
|
b79034fbb9 | ||
|
|
eaa05ac6c1 | ||
|
|
8f0a3d727e | ||
|
|
b6a524e6b4 | ||
|
|
43d00deb88 | ||
|
|
4836a0cae9 | ||
|
|
302c4ade51 | ||
|
|
75d80e014a | ||
|
|
0f790f4293 | ||
|
|
452217e9fa | ||
|
|
f66c0cfe5c | ||
|
|
30898c13d3 | ||
|
|
2330d59ffa | ||
|
|
1316be57dc | ||
|
|
ff0f2a8f83 | ||
|
|
7e5184d342 | ||
|
|
8fa1fd55e9 | ||
|
|
52463dea09 | ||
|
|
8cf09f5444 | ||
|
|
20a6190ed8 | ||
|
|
402a9f3017 | ||
|
|
af09872fba | ||
|
|
2311d7dc44 | ||
|
|
b261bf8a76 | ||
|
|
140c6d963f | ||
|
|
0c5e457359 | ||
|
|
bfa4295606 | ||
|
|
8ac68ede5d | ||
|
|
a019f887e9 | ||
|
|
eba2c69308 | ||
|
|
0bb1536c99 | ||
|
|
64eb7d598a | ||
|
|
df8e5a16be | ||
|
|
3ce2dd3989 | ||
|
|
99105fbb57 | ||
|
|
96f1e98859 | ||
|
|
d614b057c9 | ||
|
|
aafe02f3e9 | ||
|
|
123779bbd1 | ||
|
|
5365c7b428 | ||
|
|
da961a9c42 | ||
|
|
8ff0e0e2d1 | ||
|
|
ffde6bd228 | ||
|
|
498fe84b99 | ||
|
|
9b8b66ed83 | ||
|
|
9cc3dd4c92 | ||
|
|
bf23d81c9e | ||
|
|
6e1a994c26 | ||
|
|
2ccc05cb98 | ||
|
|
e7d22ffb11 | ||
|
|
4805c3615b | ||
|
|
44a5260050 | ||
|
|
d72d01823d | ||
|
|
1e166189ba | ||
|
|
66310523d1 | ||
|
|
87a041a212 | ||
|
|
9c52ec2751 | ||
|
|
9010269236 | ||
|
|
aac254d275 | ||
|
|
889b54f309 | ||
|
|
5d13ffda09 | ||
|
|
31b6d5971c | ||
|
|
304b6393d4 | ||
|
|
490f63a838 | ||
|
|
64d990ba10 | ||
|
|
cb019c405b | ||
|
|
26f80d25be | ||
|
|
fe9a1bb5fc | ||
|
|
79679f9b08 | ||
|
|
5ef5403d69 | ||
|
|
23afb3a129 | ||
|
|
fac08cf835 | ||
|
|
dd67cc7de0 | ||
|
|
8531534241 | ||
|
|
0993621bb8 | ||
|
|
45fe5c37cc | ||
|
|
35b60d8d89 | ||
|
|
63b4d37207 | ||
|
|
793268ed54 | ||
|
|
a4a05a5413 | ||
|
|
821979bab1 | ||
|
|
69be742619 | ||
|
|
298982b522 | ||
|
|
9f2b3d1cd4 | ||
|
|
800119fc92 | ||
|
|
725fc26880 | ||
|
|
ed27e87540 | ||
|
|
4fc2b5cf76 | ||
|
|
749d46930d | ||
|
|
7acafc26d6 | ||
|
|
2817936b87 | ||
|
|
022c5a8401 | ||
|
|
50fd944a62 | ||
|
|
9c9cd8164b | ||
|
|
8744449016 | ||
|
|
ada6fc18d9 | ||
|
|
50b0186c06 | ||
|
|
2c9d847a1d | ||
|
|
816cdda6bb | ||
|
|
833f97198e | ||
|
|
293d5cc02b | ||
|
|
dbb302f22a | ||
|
|
87d21e7968 | ||
|
|
9dd79a8061 | ||
|
|
a1287416d4 | ||
|
|
00dc877f0a | ||
|
|
335081053d | ||
|
|
4af22cfbc0 | ||
|
|
ab4f8a0e39 | ||
|
|
7e110603b5 | ||
|
|
94455192ad | ||
|
|
c4ca24a537 | ||
|
|
ea1f5d1ff0 | ||
|
|
77b7355433 | ||
|
|
e727566741 | ||
|
|
0f68da4eb8 | ||
|
|
1a7abfd3b7 | ||
|
|
905c2775d2 | ||
|
|
126da9ee12 | ||
|
|
1080cb71c4 | ||
|
|
39a295901a | ||
|
|
7e55b0b099 | ||
|
|
b4aa90b402 | ||
|
|
a1ecf8a413 | ||
|
|
4d59d266c1 | ||
|
|
7e0a2ea4cb | ||
|
|
912808b672 | ||
|
|
a352d51711 | ||
|
|
4ad5ed64d7 | ||
|
|
3838a3b4fb | ||
|
|
7d748ae15e | ||
|
|
9dab389b50 | ||
|
|
260b4adb8c | ||
|
|
182d92f9bd | ||
|
|
61a68a9e38 | ||
|
|
b6e33d70c8 | ||
|
|
7a26bf1a10 | ||
|
|
df1a87eb4e | ||
|
|
e203b1aa9a | ||
|
|
f458114c5b | ||
|
|
8db09d2fed | ||
|
|
0ce7053598 | ||
|
|
6a96815c44 | ||
|
|
d7b0db41f4 | ||
|
|
9c9f3e7055 | ||
|
|
2f6d680b6c | ||
|
|
639f7939a3 | ||
|
|
95ce9cbf0c | ||
|
|
1e0ef941c7 | ||
|
|
7e5bd64885 | ||
|
|
de023bc6ea | ||
|
|
c7df6f50ca | ||
|
|
df71d251f0 | ||
|
|
0b9acc18cc | ||
|
|
58a7de3136 | ||
|
|
5bb77b1e03 | ||
|
|
e351322ce4 | ||
|
|
6b53ccc8bd | ||
|
|
33842b6f92 | ||
|
|
5e747b2cfd | ||
|
|
3f8de0efb9 | ||
|
|
63da3e4595 | ||
|
|
a04b69dc6a | ||
|
|
3be4764d54 | ||
|
|
2d4fec01b1 | ||
|
|
85c50eb542 | ||
|
|
ee62d00c96 | ||
|
|
52a32f0887 | ||
|
|
dd30be90ad | ||
|
|
4cb027eb73 | ||
|
|
8e0b8f2faf | ||
|
|
39491b78b1 | ||
|
|
7c82da8389 | ||
|
|
d5c13603a0 | ||
|
|
aad253bfad | ||
|
|
dac055e2ef | ||
|
|
85df5b9983 | ||
|
|
537cd42097 | ||
|
|
9d06fce5f9 | ||
|
|
c029e1005f | ||
|
|
b245a3d611 | ||
|
|
11d89c4268 | ||
|
|
6be84ab93e | ||
|
|
2f2c4d6740 | ||
|
|
06f4d0c117 | ||
|
|
b5e557ffb0 | ||
|
|
8bd6c4a335 | ||
|
|
29eaff9edc | ||
|
|
08afaf7fa5 | ||
|
|
45bab9fe71 | ||
|
|
514eb8f0e3 | ||
|
|
a7baa58c22 | ||
|
|
e548d8b1a5 | ||
|
|
92ff0fe624 | ||
|
|
a464f5a88e | ||
|
|
962a565d30 | ||
|
|
7db50391f2 | ||
|
|
8b0d55a729 | ||
|
|
de6f341f41 | ||
|
|
c9fb530dbf | ||
|
|
836bcd09ec | ||
|
|
52a6836872 | ||
|
|
c00cadb37a | ||
|
|
b26c072f0d | ||
|
|
c99482f59d | ||
|
|
4aa0f567c3 | ||
|
|
4656021902 | ||
|
|
3df6144b77 | ||
|
|
c620004168 | ||
|
|
893ff3e9d7 | ||
|
|
eec9a3cb8f | ||
|
|
d3c8042102 | ||
|
|
e590b44c77 | ||
|
|
e97bf479fb | ||
|
|
fe2f71c9b8 | ||
|
|
ee6c483dd4 | ||
|
|
e83ab86f1f | ||
|
|
ef237fc521 | ||
|
|
fd4d68038b | ||
|
|
8e458c21e9 | ||
|
|
4ab36cb3bc | ||
|
|
2d149f3a09 | ||
|
|
dfd2c7a3d2 | ||
|
|
6637762aec | ||
|
|
615eba3b40 | ||
|
|
dc183125fc | ||
|
|
c3b69d1201 | ||
|
|
3e1a50ab9a | ||
|
|
7565c71eb2 | ||
|
|
d743352bc0 | ||
|
|
75ca72e3a8 | ||
|
|
59d26c6825 | ||
|
|
5eed363e10 | ||
|
|
49b2fdbde9 | ||
|
|
623f6e9542 | ||
|
|
32356f23d3 | ||
|
|
321ddabfd5 | ||
|
|
bf93a6a1ab | ||
|
|
91aa5dc6dd | ||
|
|
56ed46b99a | ||
|
|
daaf21a4fe | ||
|
|
06a8f2afab | ||
|
|
cc04530c4a | ||
|
|
0b6f62977a | ||
|
|
2a2996c22b | ||
|
|
254aba0995 | ||
|
|
23a81b3121 | ||
|
|
c4659f816f | ||
|
|
d1427e7d2e | ||
|
|
5584d1062e | ||
|
|
8cb41fe5fb | ||
|
|
cd893fec15 | ||
|
|
2a01f178da | ||
|
|
a069d1e07d | ||
|
|
76fa7f5200 | ||
|
|
7a0a86d51c | ||
|
|
8e283e43db | ||
|
|
794744d5a3 | ||
|
|
5c3e69b045 | ||
|
|
9cbd595cfd | ||
|
|
a5074eca7d | ||
|
|
9386b117c1 | ||
|
|
7738248230 | ||
|
|
85f1f808d3 | ||
|
|
6dbafa22bb | ||
|
|
cadcd37681 | ||
|
|
a45ea6e1d8 | ||
|
|
187f24de81 | ||
|
|
8df1f48226 | ||
|
|
7dbf332a94 | ||
|
|
c09ddf6de1 | ||
|
|
9b5fa99d6a | ||
|
|
b9abf32007 | ||
|
|
09ecb80625 | ||
|
|
c4e9a0e340 | ||
|
|
b6cf4e1826 | ||
|
|
ecb1619399 | ||
|
|
83f4b7b699 | ||
|
|
9b81ddf4d5 | ||
|
|
4af17f0e40 | ||
|
|
2fbc036e69 | ||
|
|
d236a12b8f | ||
|
|
232be333ad | ||
|
|
192e5af797 | ||
|
|
deb39b8f34 | ||
|
|
197b41d97a | ||
|
|
7b11a57c6f | ||
|
|
eac7945fae | ||
|
|
1177d2263f | ||
|
|
b9478a1f65 | ||
|
|
025cf91679 | ||
|
|
442aa72ccc | ||
|
|
1c09f9c82d | ||
|
|
c6d2f633ea | ||
|
|
17bd60e3e9 | ||
|
|
1d24e82276 | ||
|
|
e42d3d5620 | ||
|
|
5c83972401 | ||
|
|
b8df18481f | ||
|
|
1690dd4728 | ||
|
|
a86087ed7c | ||
|
|
745060f0b5 | ||
|
|
0a0a490a38 | ||
|
|
f816a5b0da | ||
|
|
2f02e49716 | ||
|
|
a98a0b1ac2 | ||
|
|
034326c984 | ||
|
|
a965b05400 | ||
|
|
6c2de4a359 | ||
|
|
e9588279c8 | ||
|
|
efe3a78499 | ||
|
|
31969a4939 | ||
|
|
7a00c5ac71 | ||
|
|
23fbbb5191 | ||
|
|
a58b1ccfa5 | ||
|
|
37b0a5c04c | ||
|
|
3fb4f60192 | ||
|
|
635fa84731 | ||
|
|
410bf4f0cd | ||
|
|
eebe115b78 | ||
|
|
70eb9f0678 | ||
|
|
b9de179fe8 | ||
|
|
ac29e24c1b | ||
|
|
d83572803d | ||
|
|
87faa4f108 | ||
|
|
ea3a0d64c1 | ||
|
|
8288d2f38b | ||
|
|
ff817dad0d | ||
|
|
ba554d5f45 | ||
|
|
90922a0ce0 | ||
|
|
a91cb546f9 | ||
|
|
3cb21d128f | ||
|
|
1b8d5d0648 | ||
|
|
03f035cdf0 | ||
|
|
ac95654409 | ||
|
|
ba33bb6aa4 | ||
|
|
2898c0507e | ||
|
|
d59edd6cca | ||
|
|
74d568b1e7 | ||
|
|
0316fedd4c | ||
|
|
5247d93d7b | ||
|
|
bda5312917 | ||
|
|
e8c071304c | ||
|
|
4e155caae9 | ||
|
|
aedb7e44ba | ||
|
|
c58109c1b4 | ||
|
|
2167c7b6d4 | ||
|
|
22eef989f6 | ||
|
|
edf387363d | ||
|
|
14ffb7443f | ||
|
|
c21ba1e64c | ||
|
|
46b8069c6d | ||
|
|
2fb697f452 | ||
|
|
73f3293314 | ||
|
|
53fc25550b | ||
|
|
961d1775e5 | ||
|
|
bdaa359d11 | ||
|
|
a90cc018dd | ||
|
|
e06f784d35 | ||
|
|
5ce1b6a4c1 | ||
|
|
86d579a7ae | ||
|
|
d031ab5ceb | ||
|
|
dcf7230494 | ||
|
|
20aca45b39 | ||
|
|
98ea43eca9 | ||
|
|
cd541c7a20 | ||
|
|
5a5cf31aba | ||
|
|
3074711828 | ||
|
|
aa97c928e9 | ||
|
|
5b4d6f2fee | ||
|
|
7120ad5014 | ||
|
|
6286cfac28 | ||
|
|
1af09da2aa | ||
|
|
bea533acf8 | ||
|
|
5316c2479e | ||
|
|
9895f8c71e | ||
|
|
9749a398c0 | ||
|
|
2883a278ee | ||
|
|
6bbb9ec956 | ||
|
|
630556f7fe | ||
|
|
32a79c4fa1 | ||
|
|
eabfee6890 | ||
|
|
6b9b283149 | ||
|
|
a19ad6663c | ||
|
|
dfae97a8d7 | ||
|
|
93d03434eb | ||
|
|
76fcd341f6 | ||
|
|
cea53ddd1f | ||
|
|
cfc061c960 | ||
|
|
09bc1784e4 | ||
|
|
b723cf92e7 | ||
|
|
3d7b9f60da | ||
|
|
b049bdb33f | ||
|
|
9aa6c2bd9f | ||
|
|
4450cce0e9 | ||
|
|
523cd421b3 | ||
|
|
88dbbe7bc3 | ||
|
|
cc63a633b5 | ||
|
|
72578e49dd | ||
|
|
83f1edb9da | ||
|
|
8016bd4e1f | ||
|
|
cb8b25da53 | ||
|
|
c517b98bae | ||
|
|
d66876a9c9 | ||
|
|
ad31df09fb | ||
|
|
1831e507fe | ||
|
|
b12a58632d | ||
|
|
8442373be9 | ||
|
|
0f7a9e5a74 | ||
|
|
74bce91db8 | ||
|
|
469b74ffb3 | ||
|
|
5dd0775737 | ||
|
|
33ed5402f4 | ||
|
|
aa655dadc4 | ||
|
|
0828025450 | ||
|
|
d9db3b695c | ||
|
|
c3e6199ecf | ||
|
|
926b9e0bfd | ||
|
|
05ba2ee2f2 | ||
|
|
cd865e0a0a | ||
|
|
08af876e01 | ||
|
|
a33fb05946 | ||
|
|
de52fa564b | ||
|
|
8e13d406d4 | ||
|
|
504f738cff | ||
|
|
fde7965bc9 | ||
|
|
a1154bf383 | ||
|
|
c9cf6d157b | ||
|
|
ef2f637d01 | ||
|
|
d130c73e77 | ||
|
|
3ecc389e66 | ||
|
|
3dbddfba41 | ||
|
|
e6cb497d1a | ||
|
|
703ac13185 | ||
|
|
a3e1c86953 | ||
|
|
3d64222655 | ||
|
|
5eb29fa99c | ||
|
|
84a24b76c7 | ||
|
|
89c2a64008 | ||
|
|
c1f992b11f | ||
|
|
ea60442fd6 | ||
|
|
d78c5fd7c2 | ||
|
|
3b98e4962a | ||
|
|
f739a1141d | ||
|
|
c498e9437e | ||
|
|
9f9a7c6b38 | ||
|
|
e3d2949c46 | ||
|
|
53f563ec08 | ||
|
|
d27c36c70c | ||
|
|
820834111b | ||
|
|
0c7b09c237 | ||
|
|
6eb8bb6241 | ||
|
|
ce0b18045b | ||
|
|
10d2d566a3 | ||
|
|
9c404185e8 | ||
|
|
3044c16ce1 | ||
|
|
729ef0190e | ||
|
|
3aab16f6db | ||
|
|
0682882ac1 | ||
|
|
da46cd78a3 | ||
|
|
f3cca3483d | ||
|
|
40dbb0ffaf | ||
|
|
b11ef97b7d | ||
|
|
634ed4a341 | ||
|
|
52aa87c145 | ||
|
|
3aea5fb704 | ||
|
|
ea559592b5 | ||
|
|
a74b925173 | ||
|
|
e84f71cd37 | ||
|
|
bc6428536c | ||
|
|
1c47a0e1a0 | ||
|
|
4d3e2f4fd3 | ||
|
|
a3430f7ce6 | ||
|
|
247b7ee1ab | ||
|
|
58eafce947 | ||
|
|
ed25cf9d23 | ||
|
|
8992c74d32 | ||
|
|
01f56c515b | ||
|
|
864d3a3b2f | ||
|
|
d68d2a4b18 | ||
|
|
d24da615b3 | ||
|
|
84c693ad22 | ||
|
|
747cbc1442 | ||
|
|
722cc54338 | ||
|
|
21abf0d215 | ||
|
|
83cb1b05d9 | ||
|
|
83d4c04646 | ||
|
|
0a7f601dec | ||
|
|
1d079c0879 | ||
|
|
3df4b5b28f | ||
|
|
14dc86e9ea | ||
|
|
b64d8bfab3 | ||
|
|
b41c8ad364 | ||
|
|
03c6709254 | ||
|
|
7a6fb46841 | ||
|
|
5b43407388 | ||
|
|
0e05301588 | ||
|
|
7a501164a9 | ||
|
|
b632984874 | ||
|
|
94407079bf | ||
|
|
57ab7ee6f2 | ||
|
|
62cf372d81 | ||
|
|
d3991ee672 | ||
|
|
b4603069bf | ||
|
|
60c522a696 | ||
|
|
99b0c8974b | ||
|
|
6ae2c8497f | ||
|
|
01b7509e61 | ||
|
|
194a09f21d | ||
|
|
3207365e3a | ||
|
|
b87b8ea931 | ||
|
|
58d21ddc15 | ||
|
|
80c1fee406 | ||
|
|
a595c1ceaf | ||
|
|
cc9c440614 | ||
|
|
37425713ba | ||
|
|
8e76d67bdf | ||
|
|
99cb553cbd | ||
|
|
dcc396234c | ||
|
|
715de1b18c | ||
|
|
7c2384cb94 | ||
|
|
5b1dd15749 | ||
|
|
1f8417041d | ||
|
|
7ecd70a90b | ||
|
|
6d85ae8e8e | ||
|
|
f405877a16 | ||
|
|
d22c6f9d66 | ||
|
|
c87f0495c0 | ||
|
|
15fbf44948 | ||
|
|
10963bd808 | ||
|
|
0866c93573 | ||
|
|
48e475c9f6 | ||
|
|
2c5ab19374 | ||
|
|
9d50c31d2c | ||
|
|
b8b03c88e4 | ||
|
|
a65f57e0f5 | ||
|
|
491027b048 | ||
|
|
5e1971a951 | ||
|
|
f64f672b20 | ||
|
|
7c0a5d60ef | ||
|
|
1f7bd9fd47 | ||
|
|
2a7c3eb2e6 | ||
|
|
32aeba2bb8 | ||
|
|
20e376c214 | ||
|
|
a0c1552726 | ||
|
|
46783e32d5 | ||
|
|
2eafac3ff6 | ||
|
|
9bd85b7023 | ||
|
|
eb9dcce30b | ||
|
|
742ef58d89 | ||
|
|
c98bef5fc7 | ||
|
|
23044b3758 | ||
|
|
107ce78b12 | ||
|
|
5f81b8f0c1 | ||
|
|
83d75e704b | ||
|
|
a860e3eaf7 | ||
|
|
3d84bcc7ad | ||
|
|
c31504220d | ||
|
|
f8501cf7f4 | ||
|
|
81ef5a1dd2 | ||
|
|
29ec793386 | ||
|
|
187457df13 | ||
|
|
2eea214a42 | ||
|
|
639c2290b7 | ||
|
|
3044dcd3af | ||
|
|
3d64b16227 | ||
|
|
7bd22a5103 | ||
|
|
dcdd8cb3db | ||
|
|
e93c5f8265 |
@@ -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
5
.gitignore
vendored
@@ -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
|
||||
|
||||
27
.jscsrc
Normal file
27
.jscsrc
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
'validateIndentation': 4,
|
||||
'requireCamelCaseOrUpperCaseIdentifiers': true,
|
||||
'requireParenthesesAroundIIFE': true,
|
||||
'requireCapitalizedConstructors': true,
|
||||
'disallowEmptyBlocks': false,
|
||||
'validateQuoteMarks': "'",
|
||||
'requireOperatorBeforeLineBreak': false,
|
||||
'requireCommaBeforeLineBreak': true,
|
||||
'disallowMultipleLineStrings': true,
|
||||
'requireDotNotation': true,
|
||||
'disallowTabs': true,
|
||||
'disallowNewlineBeforeBlockStatements': true,
|
||||
'disallowTrailingWhitespace': true,
|
||||
'disallowMixedSpacesAndTabs': true,
|
||||
'requireSpaceBeforeBinaryOperators': true,
|
||||
'requireSpaceBeforeBlockStatements': true,
|
||||
'requireSpaceBeforeObjectValues': true,
|
||||
'requireSpaceBetweenArguments': true,
|
||||
'requireSpacesInFunctionDeclaration': {
|
||||
'beforeOpeningCurlyBrace': true
|
||||
},
|
||||
'requireSpacesInNamedFunctionExpression': {
|
||||
'beforeOpeningCurlyBrace': true,
|
||||
'beforeOpeningRoundBrace': true
|
||||
}
|
||||
}
|
||||
12
.jshintrc
12
.jshintrc
@@ -9,7 +9,6 @@
|
||||
"beforeEach"
|
||||
],
|
||||
|
||||
"indent": 4,
|
||||
"node": true,
|
||||
"devel": true,
|
||||
|
||||
@@ -17,26 +16,19 @@
|
||||
"curly": false,
|
||||
"eqeqeq": true,
|
||||
"forin": false,
|
||||
"immed": true,
|
||||
"latedef": false,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": false,
|
||||
"nonew": true,
|
||||
"plusplus": false,
|
||||
"regexp": false,
|
||||
"undef": true,
|
||||
"unused": "vars",
|
||||
"quotmark": "single",
|
||||
"strict": false,
|
||||
"trailing": true,
|
||||
"camelcase": true,
|
||||
|
||||
"asi": false,
|
||||
"boss": true,
|
||||
"debug": false,
|
||||
"eqnull": true,
|
||||
"es5": false,
|
||||
"esnext": false,
|
||||
"evil": false,
|
||||
"expr": false,
|
||||
@@ -44,16 +36,12 @@
|
||||
"globalstrict": false,
|
||||
"iterator": false,
|
||||
"lastsemic": false,
|
||||
"laxbreak": true,
|
||||
"laxcomma": false,
|
||||
"loopfunc": true,
|
||||
"multistr": false,
|
||||
"onecase": true,
|
||||
"regexdash": false,
|
||||
"scripturl": false,
|
||||
"smarttabs": false,
|
||||
"shadow": false,
|
||||
"sub": false,
|
||||
"supernew": true,
|
||||
"validthis": false,
|
||||
|
||||
|
||||
37
.travis.yml
37
.travis.yml
@@ -1,6 +1,31 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.8"
|
||||
before_script:
|
||||
- npm install grunt-cli -g
|
||||
sudo: false
|
||||
|
||||
env:
|
||||
- NODE_VERSION=0.10
|
||||
- NODE_VERSION=0.11
|
||||
- NODE_VERSION=0.12
|
||||
- NODE_VERSION=4.0
|
||||
- NODE_VERSION=5.0
|
||||
|
||||
install:
|
||||
- test $TRAVIS_OS_NAME = "osx" && brew install nvm && source $(brew --prefix nvm)/nvm.sh || test $TRAVIS_OS_NAME = "linux"
|
||||
- nvm install $NODE_VERSION
|
||||
- npm install -g npm@^2.0.0
|
||||
- node --version
|
||||
- npm --version
|
||||
- git --version
|
||||
- svn --version | head -n 1
|
||||
- npm install -g grunt-cli
|
||||
- npm install
|
||||
|
||||
os:
|
||||
- osx
|
||||
- linux
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- os: osx
|
||||
- env: "NODE_VERSION=0.11"
|
||||
|
||||
script:
|
||||
- grunt travis
|
||||
|
||||
290
CHANGELOG.md
290
CHANGELOG.md
@@ -1,11 +1,287 @@
|
||||
# Changelog
|
||||
|
||||
## 1.2.2
|
||||
## 1.6.8 - 2015-11-27
|
||||
|
||||
- Use fs-write-stream-atomic for downloads
|
||||
- Improved downloader that properly cleans after itself
|
||||
- Fix shallow host detection ([#2040](https://github.com/bower/bower/pull/2040))
|
||||
- Upgrade to ([bower-config#1.2.3](https://github.com/bower/config/releases/tag/1.2.3))
|
||||
- Properly restore env variables if they are undefined at the beginning
|
||||
- Properly handle `default` setting for config.ca
|
||||
- Display proper error if .bowerrc is a directory instead of file
|
||||
|
||||
## 1.6.7 - 2015-11-26
|
||||
|
||||
- Bundless all the dependencies again
|
||||
|
||||
## 1.6.6 - 2015-11-25
|
||||
|
||||
- Fixes regression with the published npm version
|
||||
|
||||
## 1.6.5 - 2015-10-24
|
||||
|
||||
- Updates to tests and documentation
|
||||
- Fixes passing options when requesting downloads
|
||||
|
||||
## 1.6.4 - 2015-10-24
|
||||
|
||||
- Fix ignoring dependencies on multiple install run ([#1970](https://github.com/bower/bower/pull/1970))
|
||||
- Use --non-interactive when running svn client ([#1969](https://github.com/bower/bower/pull/1969))
|
||||
- Fix downloading of URLs ending with slash ([#1956](https://github.com/bower/bower/pull/1956))
|
||||
- Add user-agent field for downloads by Bower ([#1960](https://github.com/bower/bower/pull/1960))
|
||||
|
||||
## 1.6.3 - 2015-10-16
|
||||
|
||||
Fixes regression issues introduced with 1.6.2, specifically:
|
||||
|
||||
- Allow for bower_components to be a symlink
|
||||
- Allow setting custom registry in .bowerrc
|
||||
|
||||
## 1.6.2 - 2015-10-15
|
||||
|
||||
Fix dependency issues of 1.6.1. First published release of 1.6.x.
|
||||
|
||||
## 1.6.1 - 2015-10-15
|
||||
|
||||
Fix dependency issues of 1.6.0. Reverted release.
|
||||
|
||||
## 1.6.0 - 2015-10-15
|
||||
|
||||
- Shrinkwrap all dependencies and add them to bundledDependencies ([#1948](https://github.com/bower/bower/pull/1948))
|
||||
- Allow for ignoring of child dependencies ([#1394](https://github.com/bower/bower/pull/1394))
|
||||
- Allow passing `--config.resolvers` through CLI ([#1922](https://github.com/bower/bower/pull/1922))
|
||||
- Use defaults values from package.json if it exists (bower init) ([#1731](https://github.com/bower/bower/issues/1731))
|
||||
- Properly use cerificates set in .bowerrc ([#1869](https://github.com/bower/bower/pull/1869))
|
||||
- Include package name when version conflict occurs ([#1917](https://github.com/bower/bower/pull/1917))
|
||||
- Add timeout for permission check ([yeoman/insight#35](https://github.com/yeoman/insight/pull/35))
|
||||
- Close file-handles when possible. Prevents all sorts of permission issues on Windows ([0bb1536](https://github.com/bower/bower/commit/0bb1536c9972e13f3be06bea9a8619632966c664))
|
||||
- Prevent ENOENT error on Windows when in VM environment ([isaacs/chmodr#8](https://github.com/isaacs/chmodr/pull/8))
|
||||
|
||||
Reverted release.
|
||||
|
||||
## 1.5.4 - 2015-11-24
|
||||
|
||||
- [fix] Lock lru-cache dependency to 2.7.0
|
||||
|
||||
## 1.5.3 - 2015-09-24
|
||||
|
||||
- Revert auto sorting of bower dependencies, fixes ([#1897](https://github.com/bower/bower/issues/1897))
|
||||
- Fix --save-exact feature for github endpoints, fixes ([#1925](https://github.com/bower/bower/issues/1925))
|
||||
- Fix `bower init` to support private flag again ([#1819](https://github.com/bower/bower/pull/1819))
|
||||
- Bump insight dependency to support prompt timeout ([#1102](https://github.com/bower/bower/issues/1102))
|
||||
|
||||
## 1.5.2 - 2015-08-25
|
||||
|
||||
- Revert update semver version from 2.x to 5.x, fixes ([#1896](https://github.com/bower/bower/issues/1896))
|
||||
- Make bower commands work from subdirectories, fixes ([#1893](https://github.com/bower/bower/issues/1893))
|
||||
- Put auto shallow cloning for git behind a flag, fixes ([#1764](https://github.com/bower/bower/issues/1764))
|
||||
|
||||
## 1.5.1 - 2015-08-24
|
||||
|
||||
- If cwd provided explicitly, force using it, fixes #1866
|
||||
|
||||
## 1.5.0 - 2015-08-24
|
||||
|
||||
- Pluggable Resolvers! http://bower.io/docs/pluggable-resolvers/
|
||||
- Update semver version from 2.x to 5.x ([#1852](https://github.com/bower/bower/issues/1852))
|
||||
- Auto-sort dependencies alphabetically ([#1381](https://github.com/bower/bower/issues/1381))
|
||||
- Make bower commands work from subdirectories ([#1866](https://github.com/bower/bower/issues/1866))
|
||||
- No longer prefer installing bower as global module ([#1865](https://github.com/bower/bower/issues/1865))
|
||||
|
||||
## 1.4.2 - 2015-11-24
|
||||
|
||||
- [fix] Lock lru-cache dependency to 2.7.0
|
||||
|
||||
## 1.4.1 - 2015-04-01
|
||||
|
||||
- [fix] Reading .bowerrc upwards directory tree ([#1763](https://github.com/bower/bower/issues/1763))
|
||||
- [fix] Update bower-registry-client so it uses the same bower-config as bower
|
||||
|
||||
## 1.4.0 - 2015-03-30
|
||||
|
||||
- Add login and unregister commands ([#1719](https://github.com/bower/bower/issues/1719))
|
||||
- Automatically detecting smart Git hosts ([#1628](https://github.com/bower/bower/issues/1628))
|
||||
- [bower/config#23] Allow npm config variables ([#1711](https://github.com/bower/bower/issues/1711))
|
||||
- [bower/config#24] Merge .bowerrc files upwards directory tree ([#1689](https://github.com/bower/bower/issues/1689))
|
||||
- Better homedir detection (514eb8f)
|
||||
- Add --save-exact flag ([#1654](https://github.com/bower/bower/issues/1654))
|
||||
- Ensure extracted files are readable (tar-fs) ([#1548](https://github.com/bower/bower/issues/1548))
|
||||
- The version command in the programmatic API now returns the new version ([#1755](https://github.com/bower/bower/issues/1755))
|
||||
- Some minor fixes: #1639, #1620, #1576, #1557, 962a565, a464f5a
|
||||
- Improved Windows support (AppVeyor CI, tests actually passing on Windows)
|
||||
- OSX testing enabled on TravisCI
|
||||
|
||||
It also includes improved test coverage (~60% -> ~85%) and many refactors.
|
||||
|
||||
## 1.3.12 - 2014-09-28
|
||||
|
||||
- [stability] Fix versions for unstable dependencies ([#1532](https://github.com/bower/bower/pull/1532))
|
||||
- [fix] Update tar-fs to support old tar format ([#1537](https://github.com/bower/bower/issues/1537))
|
||||
- [fix] Make analytics work again ([#1529](https://github.com/bower/bower/pull/1529))
|
||||
- [fix] Always disable analytics for non-interactive mode ([#1529](https://github.com/bower/bower/pull/1529))
|
||||
- [fix] Bower init can create private packages again ([#1522](https://github.com/bower/bower/issues/1522))
|
||||
- [fix] Show again missing newline for bower search output ([#1538](https://github.com/bower/bower/issues/1538))
|
||||
|
||||
## 1.3.11 - 2014-09-17
|
||||
|
||||
- [fix] Restore install missing dependencies on update ([1519](https://github.com/bower/bower/pull/1519))
|
||||
|
||||
## 1.3.10 - 2014-09-13
|
||||
|
||||
- [fix] Back down concurrency from 50 to 5 ([#1483](https://github.com/bower/bower/pull/1483))
|
||||
- [fix] Read .bowerrc from specified cwd ([#1301](https://github.com/bower/bower/pull/1301))
|
||||
- [fix] Disable shallow clones except those from GitHub ([#1393](https://github.com/bower/bower/pull/1393))
|
||||
- [fix] Expose bower version ([#1478](https://github.com/bower/bower/pull/1478))
|
||||
- [fix] Bump dependencies, including "request" ([#1467](https://github.com/bower/bower/pull/1467))
|
||||
- [fix] Prevent an error when piping bower output to head ([#1508](https://github.com/bower/bower/pull/1508))
|
||||
- [fix] Disable removing unnecessary resolutions ([#1061](https://github.com/bower/bower/pull/1061))
|
||||
- [fix] Display the output of hooks again ([#1484](https://github.com/bower/bower/issues/1484))
|
||||
- [fix] analytics: true in .bowerrc prevents user prompt ([#1470](https://github.com/bower/bower/pull/1470))
|
||||
- [perf] Use `tar-fs` instead of `tar` for faster TAR extraction ([#1490](https://github.com/bower/bower/pull/1490))
|
||||
|
||||
## 1.3.9 - 2014-08-06
|
||||
|
||||
- [fix] Handle `tmp` sometimes returning an array ([#1434](https://github.com/bower/bower/pull/1434))
|
||||
|
||||
## 1.3.8 - 2014-7-11
|
||||
|
||||
- [fix] Lock down `tmp` package dep ([#1403](https://github.com/bower/bower/pull/1403), [#1407](https://github.com/bower/bower/pull/1407))
|
||||
|
||||
## 1.3.7 - 2014-07-04
|
||||
|
||||
- [fix] callstack error when processing installed packages with circular dependencies ([#1349](https://github.com/bower/bower/issues/1349))
|
||||
- [fix] Prevent bower list --paths` failing with TypeError ([#1383](https://github.com/bower/bower/issues/1383))
|
||||
- "bower install" fails if there's no bower.json in current directory ([#922](https://github.com/bower/bower/issues/922))
|
||||
|
||||
## 1.3.6 - 2014-07-02
|
||||
|
||||
- [fix] Make --force always re-run installation ([#931](https://github.com/bower/bower/issues/931))
|
||||
- [fix] Disable caching for local resources ([#1356](https://github.com/bower/bower/issues/1356))
|
||||
- [fix] Emit errors instead throwing them when using bower.commands API ([#1297](https://github.com/bower/bower/issues/1297))
|
||||
- [fix] Main files and bower.json are never ignored ([#547](https://github.com/bower/bower/issues/547))
|
||||
- [fix] Check if pkgMeta is undefined during uninstall command ([#1329](https://github.com/bower/bower/issues/1329))
|
||||
- [fix] Make custom tmp dir and ignores play well with each other ([#1299](https://github.com/bower/bower/issues/1299))
|
||||
- Warn users when installing package with missing properties ([#694](https://github.com/bower/bower/issues/694))
|
||||
|
||||
|
||||
## 1.3.5 - 2014-06-06
|
||||
- Search compatible versions in fetching packages ([#1147](https://github.com/bower/bower/issues/1147))
|
||||
|
||||
## 1.3.4 - 2014-06-02
|
||||
|
||||
- Resolve a situation in which the install process gets into an infinite loop ([#1169](https://github.com/bower/bower/issues/1169))
|
||||
- Improved CLI output for conflicts ([#1284](https://github.com/bower/bower/issues/1284))
|
||||
- Changed `bower version` to mirror the tag format of `npm version` ([#1278](https://github.com/bower/bower/issues/1278))
|
||||
- Allow short commit SHAs to be used ([#990](https://github.com/bower/bower/issues/990))
|
||||
|
||||
## 1.3.3 - 2014-04-24
|
||||
|
||||
- Do not cache moving targets like branches ([#1242](https://github.com/bower/bower/issues/1242))
|
||||
- Suppress output if --quiet option is specified ([#1124](https://github.com/bower/bower/pull/1124))
|
||||
- Use "svn export" for efficiency ([#1224](https://github.com/bower/bower/pull/1224))
|
||||
- Prevent loading insights and analytics on CI ([#1221](https://github.com/bower/bower/issues/1221))
|
||||
- Make "bower list" respect custom components directory ([#1237](https://github.com/bower/bower/issues/1237))
|
||||
- Improve non-interactive loading performance 2x ([#1238](https://github.com/bower/bower/issues/1238))
|
||||
- Load commands only on demand, improving performance ([#1232](https://github.com/bower/bower/pull/1232))
|
||||
|
||||
## 1.3.2 - 2014-04-05
|
||||
|
||||
- Added yui moduleType [PR #1129](https://github.com/bower/bower/pull/1129)
|
||||
- Fixes for concurrency issues [PR #1211](https://github.com/bower/bower/pull/1211)
|
||||
- `link` now installs package dependencies [PR #891](https://github.com/bower/bower/pull/891)
|
||||
- Improved conflict installation message [Commit](https://github.com/bower/bower/commit/bea533acf87903d4b411bfbaa7df93f852ef46a3)
|
||||
- Add --production switch to "prune" command [PR #1168](https://github.com/bower/bower/pull/1168)
|
||||
|
||||
|
||||
## 1.3.1 - 2014-03-10
|
||||
|
||||
- No longer ask for permission to gather analytics when running on in a CI environment.
|
||||
|
||||
|
||||
## 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 when in some edge cases ([#806](https://github.com/bower/bower/issues/806))
|
||||
- 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
|
||||
@@ -31,7 +307,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.
|
||||
@@ -93,7 +369,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))
|
||||
@@ -106,7 +382,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
|
||||
|
||||
|
||||
@@ -129,7 +405,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`.
|
||||
|
||||
@@ -257,7 +533,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
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
# 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. If you're interested in becoming Bower maintainer or supporting in in any way, please full the following form: http://goo.gl/forms/P1ndzCNoiG. There is more information about [contributing](https://github.com/bower/bower/wiki/Contributor-Guidelines) in the Wiki.
|
||||
|
||||
Following these guidelines helps to communicate that you respect the time of
|
||||
the developers managing and developing this open source project. In return,
|
||||
they should reciprocate that respect in addressing your issue, assessing
|
||||
changes, and helping you finalize your pull requests.
|
||||
## Casual Involvement
|
||||
|
||||
* Improve the bower.io site ([tickets](https://github.com/bower/bower.github.io/issues))
|
||||
* Comment on issues and drive to resolution
|
||||
|
||||
## High-impact Involvement
|
||||
|
||||
* Maintaining the bower client.
|
||||
* Read [Architecture doc](https://github.com/bower/bower/wiki/Rewrite-architecture)
|
||||
* Triage, close, fix and resolve [issues](https://github.com/bower/bower/issues)
|
||||
|
||||
## Team Meetings
|
||||
|
||||
We communicate through a channel on slack: https://gitter.im/bower
|
||||
|
||||
If you'd like to attend the meetings, please fill the [support form](http://goo.gl/forms/P1ndzCNoiG), and you'll get an invite.
|
||||
|
||||
## Using the issue tracker
|
||||
|
||||
@@ -16,7 +26,8 @@ The issue tracker is the preferred channel for [bug reports](#bugs),
|
||||
requests](#pull-requests), but please respect the following restrictions:
|
||||
|
||||
* Please **do not** use the issue tracker for personal support requests. Use
|
||||
[Stack Overflow](http://stackoverflow.com/questions/tagged/bower), our
|
||||
[Stack Overflow](http://stackoverflow.com/questions/tagged/bower)
|
||||
[Gitter Channel](https://gitter.im/bower/bower)
|
||||
[Mailing List](http://groups.google.com/group/twitter-bower)
|
||||
(twitter-bower@googlegroups.com), or
|
||||
[#bower](http://webchat.freenode.net/?channels=bower) on Freenode.
|
||||
@@ -24,7 +35,6 @@ requests](#pull-requests), but please respect the following restrictions:
|
||||
* Please **do not** derail or troll issues. Keep the discussion on topic and
|
||||
respect the opinions of others.
|
||||
|
||||
|
||||
<a name="bugs"></a>
|
||||
## Bug reports
|
||||
|
||||
|
||||
51
Gruntfile.js
51
Gruntfile.js
@@ -1,17 +1,45 @@
|
||||
'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/**/*'
|
||||
]
|
||||
},
|
||||
jscs: {
|
||||
options: {
|
||||
config: '.jscsrc',
|
||||
fix: true
|
||||
},
|
||||
files: [
|
||||
'Gruntfile.js',
|
||||
'bin/*',
|
||||
'lib/**/*.js',
|
||||
'test/**/*.js',
|
||||
'!test/assets/**/*',
|
||||
'!test/reports/**/*',
|
||||
'!test/tmp/**/*'
|
||||
]
|
||||
},
|
||||
simplemocha: {
|
||||
options: {
|
||||
reporter: 'spec',
|
||||
timeout: '5000'
|
||||
timeout: '15000'
|
||||
},
|
||||
full: {
|
||||
src: ['test/test.js']
|
||||
},
|
||||
full: { src: ['test/test.js'] },
|
||||
short: {
|
||||
options: {
|
||||
reporter: 'dot'
|
||||
@@ -21,13 +49,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 +67,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('test', ['jshint', 'jscs', '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
21
HOOKS.md
Normal 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.
|
||||
2
LICENSE
2
LICENSE
@@ -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
|
||||
|
||||
357
README.md
357
README.md
@@ -1,357 +1,128 @@
|
||||
# BOWER [](http://travis-ci.org/bower/bower)
|
||||
# Bower - A package manager for the web
|
||||
|
||||
Bower is a package manager for the web. It offers a generic, unopinionated
|
||||
solution to the problem of **front-end package management**, while exposing the
|
||||
package dependency model via an API that can be consumed by a more opinionated
|
||||
build stack. There are no system wide dependencies, no dependencies are shared
|
||||
between different apps, and the dependency tree is flat.
|
||||
> Bower needs resources for its maintenance. Please fill [<strong>Support Declaration</strong>](http://goo.gl/forms/P1ndzCNoiG) if you think you can help.
|
||||
|
||||
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.).
|
||||
[](https://travis-ci.org/bower/bower)
|
||||
[](https://ci.appveyor.com/project/sheerun/bower/history)
|
||||
[](https://coveralls.io/r/bower/bower?branch=master)
|
||||
[](https://gitter.im/bower/bower?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](http://issuestats.com/github/bower/bower)
|
||||
[](http://issuestats.com/github/bower/bower)
|
||||
|
||||
[View all packages available through Bower's registry](http://sindresorhus.com/bower-components/).
|
||||
<img align="right" height="300" src="http://bower.io/img/bower-logo.png">
|
||||
|
||||
---
|
||||
|
||||
## Installing Bower
|
||||
Bower offers a generic, unopinionated solution to the problem of **front-end package management**, while exposing the package dependency model via an API that can be consumed by a more opinionated build stack. There are no system wide dependencies, no dependencies are shared between different apps, and the dependency tree is flat.
|
||||
|
||||
Bower depends on [Node](http://nodejs.org/) and [npm](http://npmjs.org/). It's
|
||||
installed globally using npm:
|
||||
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.).
|
||||
|
||||
```
|
||||
npm install -g bower
|
||||
**View complete docs on [bower.io](http://bower.io)**
|
||||
|
||||
[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
|
||||
|
||||
## Configuration
|
||||
On `prezto` or `oh-my-zsh`, do not forget to `alias bower='noglob bower'` or `bower install jquery\#1.9.1`
|
||||
|
||||
Bower can be configured using JSON in a `.bowerrc` file.
|
||||
### Never run Bower with sudo
|
||||
|
||||
The current spec can be read
|
||||
[here](https://docs.google.com/document/d/1APq7oA9tNao1UYWyOm8dKqlRP2blVkROYLZ2fLIjtWc/edit#heading=h.4pzytc1f9j8k)
|
||||
in the `Configuration` section.
|
||||
Bower is a user command, there is no need to execute it with superuser permissions.
|
||||
|
||||
|
||||
## 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);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Completion (experimental)
|
||||
|
||||
_NOTE_: Completion is still not implemented for the 1.0.0 release
|
||||
|
||||
Bower now has an experimental `completion` command that is based on, and works
|
||||
similarly to the [npm completion](https://npmjs.org/doc/completion.html). It is
|
||||
not available for Windows users.
|
||||
|
||||
This command will output a Bash / ZSH script to put into your `~/.bashrc`,
|
||||
`~/.bash_profile`, or `~/.zshrc` file.
|
||||
|
||||
```
|
||||
bower completion >> ~/.bash_profile
|
||||
```
|
||||
|
||||
|
||||
## A note for Windows users
|
||||
### 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:
|
||||
[Git for Windows](http://git-for-windows.github.io/) correctly. Be sure to check the
|
||||
options shown below:
|
||||
|
||||

|
||||
<img src="https://cloud.githubusercontent.com/assets/10702007/10532690/d2e8991a-7386-11e5-9a57-613c7f92e84e.png" width="534" height="418" alt="Git for Windows" />
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/10702007/10532694/dbe8857a-7386-11e5-9bd0-367e97644403.png" width="534" height="418" alt="Git for Windows" />
|
||||
|
||||
Note that if you use TortoiseGit and if Bower keeps asking for your SSH
|
||||
password, you should add the following environment variable: `GIT_SSH -
|
||||
C:\Program Files\TortoiseGit\bin\TortoisePlink.exe`. Adjust the `TortoisePlink`
|
||||
path if needed.
|
||||
|
||||
### Ubuntu users
|
||||
|
||||
## Contact
|
||||
To use Bower on Ubuntu, you might need to link `nodejs` executable to `node`:
|
||||
|
||||
Have a question?
|
||||
```
|
||||
sudo ln -s /usr/bin/nodejs /usr/bin/node
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Bower can be configured using JSON in a `.bowerrc` file. Read over available options at [bower.io/docs/config](http://bower.io/docs/config).
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
* [StackOverflow](http://stackoverflow.com/questions/tagged/bower)
|
||||
* [Mailinglist](http://groups.google.com/group/twitter-bower) - twitter-bower@googlegroups.com
|
||||
* [\#bower](http://webchat.freenode.net/?channels=bower) on Freenode
|
||||
|
||||
|
||||
## Contributing to this project
|
||||
## Contributing
|
||||
|
||||
Anyone and everyone is welcome to contribute. Please take a moment to
|
||||
We welcome [contributions](https://github.com/bower/bower/graphs/contributors) of all kinds from anyone. Please take a moment to
|
||||
review the [guidelines for contributing](CONTRIBUTING.md).
|
||||
|
||||
* [Bug reports](CONTRIBUTING.md#bugs)
|
||||
* [Bug reports](https://github.com/bower/bower/wiki/Report-a-Bug)
|
||||
* [Feature requests](CONTRIBUTING.md#features)
|
||||
* [Pull requests](CONTRIBUTING.md#pull-requests)
|
||||
|
||||
|
||||
## Authors
|
||||
|
||||
* [@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)
|
||||
Note that on Windows for tests to pass you need to configure Git before cloning:
|
||||
|
||||
```
|
||||
git config --global core.autocrlf input
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2012 Twitter, Inc.
|
||||
Copyright (c) 2015 Twitter and [other contributors](https://github.com/bower/bower/graphs/contributors)
|
||||
|
||||
Licensed under the MIT License
|
||||
|
||||
42
appveyor.yml
Normal file
42
appveyor.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
# Thanks for Grunt for template of this file!
|
||||
|
||||
# http://www.appveyor.com/docs/appveyor-yml
|
||||
|
||||
# Fix line endings in Windows. (runs before repo cloning)
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
# Test against these versions of Node.js.
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "0.10"
|
||||
- nodejs_version: "0.12"
|
||||
- nodejs_version: "5"
|
||||
- nodejs_version: "4"
|
||||
|
||||
# Allow failing jobs for bleeding-edge Node.js versions.
|
||||
matrix:
|
||||
allow_failures:
|
||||
- nodejs_version: "0.10"
|
||||
|
||||
# Install scripts. (runs after repo cloning)
|
||||
install:
|
||||
# Get the latest stable version of Node 0.STABLE.latest
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
# Output useful info for debugging.
|
||||
- node --version
|
||||
- npm --version
|
||||
- git --version
|
||||
- svn --version
|
||||
# Install all dependencies
|
||||
- npm install
|
||||
|
||||
# Post-install test scripts.
|
||||
test_script:
|
||||
- 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
137
bin/bower
@@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
30
lib/abbreviations.js
Normal file
30
lib/abbreviations.js
Normal file
@@ -0,0 +1,30 @@
|
||||
var abbrev = require('abbrev');
|
||||
var mout = require('mout');
|
||||
|
||||
function expandNames(obj, prefix, stack) {
|
||||
prefix = prefix || '';
|
||||
stack = stack || [];
|
||||
|
||||
mout.object.forOwn(obj, function (value, name) {
|
||||
name = prefix + name;
|
||||
|
||||
stack.push(name);
|
||||
|
||||
if (typeof value === 'object' && !value.line) {
|
||||
expandNames(value, name + ' ', stack);
|
||||
}
|
||||
});
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
module.exports = function(commands) {
|
||||
var abbreviations = abbrev(expandNames(commands));
|
||||
|
||||
abbreviations.i = 'install';
|
||||
abbreviations.rm = 'uninstall';
|
||||
abbreviations.unlink = 'uninstall';
|
||||
abbreviations.ls = 'list';
|
||||
|
||||
return abbreviations;
|
||||
};
|
||||
56
lib/commands/cache/clean.js
vendored
56
lib/commands/cache/clean.js
vendored
@@ -1,22 +1,19 @@
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('../../util/fs');
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
var rimraf = require('rimraf');
|
||||
var Logger = require('bower-logger');
|
||||
var rimraf = require('../../util/rimraf');
|
||||
var endpointParser = require('bower-endpoint-parser');
|
||||
var PackageRepository = require('../../core/PackageRepository');
|
||||
var semver = require('../../util/semver');
|
||||
var cli = require('../../util/cli');
|
||||
var defaultConfig = require('../../config');
|
||||
|
||||
function clean(endpoints, options, config) {
|
||||
var logger = new Logger();
|
||||
function clean(logger, endpoints, options, config) {
|
||||
var decEndpoints;
|
||||
var names;
|
||||
|
||||
options = options || {};
|
||||
config = mout.object.deepFillIn(config || {}, defaultConfig);
|
||||
config = defaultConfig(config);
|
||||
|
||||
// If endpoints is an empty array, null them
|
||||
if (endpoints && !endpoints.length) {
|
||||
@@ -33,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;
|
||||
|
||||
4
lib/commands/cache/index.js
vendored
4
lib/commands/cache/index.js
vendored
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
clean: require('./clean'),
|
||||
list: require('./list')
|
||||
};
|
||||
31
lib/commands/cache/list.js
vendored
31
lib/commands/cache/list.js
vendored
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -1,12 +1,10 @@
|
||||
var Q = require('q');
|
||||
var path = require('path');
|
||||
var fs = require('graceful-fs');
|
||||
var Logger = require('bower-logger');
|
||||
var cli = require('../util/cli');
|
||||
var fs = require('../util/fs');
|
||||
var createError = require('../util/createError');
|
||||
|
||||
function help(name) {
|
||||
function help(logger, name, config) {
|
||||
var json;
|
||||
var logger = new Logger();
|
||||
|
||||
if (name) {
|
||||
json = path.resolve(__dirname, '../../templates/json/help-' + name.replace(/\s+/g, '/') + '.json');
|
||||
@@ -14,39 +12,28 @@ function help(name) {
|
||||
json = path.resolve(__dirname, '../../templates/json/help.json');
|
||||
}
|
||||
|
||||
fs.exists(json, function (exists) {
|
||||
return Q.promise(function (resolve) {
|
||||
fs.exists(json, resolve);
|
||||
})
|
||||
.then(function (exists) {
|
||||
if (!exists) {
|
||||
return logger.emit('error', createError('Unknown command: ' + name, 'EUNKOWNCMD', {
|
||||
throw createError('Unknown command: ' + name, 'EUNKNOWNCMD', {
|
||||
command: name
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
json = require(json);
|
||||
} catch (error) {
|
||||
return logger.emit('error', error);
|
||||
}
|
||||
|
||||
logger.emit('end', json);
|
||||
return require(json);
|
||||
});
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
help.line = function (argv) {
|
||||
var options = help.options(argv);
|
||||
help.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
var options = cli.readOptions(argv);
|
||||
var name = options.argv.remain.slice(1).join(' ');
|
||||
|
||||
return help(options.argv.remain.slice(1).join(' '));
|
||||
};
|
||||
|
||||
help.options = function (argv) {
|
||||
return cli.readOptions(argv);
|
||||
};
|
||||
|
||||
help.completion = function () {
|
||||
// TODO
|
||||
return [name];
|
||||
};
|
||||
|
||||
module.exports = help;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,17 +1,81 @@
|
||||
var Q = require('q');
|
||||
var Logger = require('bower-logger');
|
||||
var config = require('../config');
|
||||
|
||||
/**
|
||||
* 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 () {
|
||||
config.restore();
|
||||
var args = [].slice.call(arguments);
|
||||
args.unshift('end');
|
||||
logger.emit.apply(logger, args);
|
||||
}, function (error) {
|
||||
config.restore();
|
||||
logger.emit('error', error);
|
||||
});
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
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'),
|
||||
login: commandFactory('./login'),
|
||||
lookup: commandFactory('./lookup'),
|
||||
prune: commandFactory('./prune'),
|
||||
register: commandFactory('./register'),
|
||||
search: commandFactory('./search'),
|
||||
update: commandFactory('./update'),
|
||||
uninstall: commandFactory('./uninstall'),
|
||||
unregister: commandFactory('./unregister'),
|
||||
version: commandFactory('./version')
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,55 +1,46 @@
|
||||
var mout = require('mout');
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('../util/fs');
|
||||
var path = require('path');
|
||||
var Q = require('q');
|
||||
var Logger = require('bower-logger');
|
||||
var endpointParser = require('bower-endpoint-parser');
|
||||
var Project = require('../core/Project');
|
||||
var defaultConfig = require('../config');
|
||||
var GitHubResolver = require('../core/resolvers/GitHubResolver');
|
||||
var GitFsResolver = require('../core/resolvers/GitFsResolver');
|
||||
var cli = require('../util/cli');
|
||||
var cmd = require('../util/cmd');
|
||||
var createError = require('../util/createError');
|
||||
|
||||
function init(config) {
|
||||
function init(logger, config) {
|
||||
var project;
|
||||
var logger = new Logger();
|
||||
|
||||
config = mout.object.deepFillIn(config || {}, defaultConfig);
|
||||
config = config || {};
|
||||
|
||||
if (!config.cwd) {
|
||||
config.cwd = process.cwd();
|
||||
}
|
||||
|
||||
config = defaultConfig(config);
|
||||
|
||||
// This command requires interactive to be enabled
|
||||
if (!config.interactive) {
|
||||
process.nextTick(function () {
|
||||
logger.emit('error', createError('Register requires an interactive shell', 'ENOINT', {
|
||||
details: 'Note that you can manually force an interactive shell with --config.interactive'
|
||||
}));
|
||||
throw createError('Register requires an interactive shell', 'ENOINT', {
|
||||
details: 'Note that you can manually force an interactive shell with --config.interactive'
|
||||
});
|
||||
return logger;
|
||||
}
|
||||
|
||||
project = new Project(config, logger);
|
||||
|
||||
// Start with existing JSON details
|
||||
readJson(project, logger)
|
||||
return readJson(project, logger)
|
||||
// Fill in defaults
|
||||
.then(setDefaults.bind(null, config))
|
||||
// Now prompt user to make changes
|
||||
.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) {
|
||||
@@ -66,7 +57,7 @@ function readJson(project, logger) {
|
||||
function saveJson(project, logger, json) {
|
||||
// Cleanup empty props (null values, empty strings, objects and arrays)
|
||||
mout.object.forOwn(json, function (value, key) {
|
||||
if (value == null || mout.lang.isEmpty(value)) {
|
||||
if (!validConfigValue(value)) {
|
||||
delete json[key];
|
||||
}
|
||||
});
|
||||
@@ -89,6 +80,18 @@ function saveJson(project, logger, json) {
|
||||
});
|
||||
}
|
||||
|
||||
// Test if value is of a type supported by bower.json[0] - Object, Array, String, Boolean - or a Number
|
||||
// [0]: https://github.com/bower/bower.json-spec
|
||||
function validConfigValue(val) {
|
||||
return (
|
||||
mout.lang.isObject(val) ||
|
||||
mout.lang.isArray(val) ||
|
||||
mout.lang.isString(val) ||
|
||||
mout.lang.isBoolean(val) ||
|
||||
mout.lang.isNumber(val)
|
||||
);
|
||||
}
|
||||
|
||||
function setDefaults(config, json) {
|
||||
var name;
|
||||
var promise = Q.resolve();
|
||||
@@ -98,19 +101,6 @@ function setDefaults(config, json) {
|
||||
json.name = path.basename(config.cwd);
|
||||
}
|
||||
|
||||
// Version
|
||||
if (!json.version) {
|
||||
// Assume latest semver tag if it's a git repo
|
||||
promise = promise.then(function () {
|
||||
return GitFsResolver.versions(config.cwd)
|
||||
.then(function (versions) {
|
||||
json.version = versions[0] || '0.0.0';
|
||||
}, function () {
|
||||
json.version = '0.0.0';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Main
|
||||
if (!json.main) {
|
||||
// Remove '.js' from the end of the package name if it is there
|
||||
@@ -184,12 +174,6 @@ function promptUser(logger, json) {
|
||||
'default': json.name,
|
||||
'type': 'input'
|
||||
},
|
||||
{
|
||||
'name': 'version',
|
||||
'message': 'version',
|
||||
'default': json.version,
|
||||
'type': 'input'
|
||||
},
|
||||
{
|
||||
'name': 'description',
|
||||
'message': 'description',
|
||||
@@ -202,6 +186,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,9 +239,9 @@ function promptUser(logger, json) {
|
||||
return Q.nfcall(logger.prompt.bind(logger), questions)
|
||||
.then(function (answers) {
|
||||
json.name = answers.name;
|
||||
json.version = answers.version;
|
||||
json.description = answers.description;
|
||||
json.main = answers.main;
|
||||
json.moduleType = answers.moduleType;
|
||||
json.keywords = toArray(answers.keywords);
|
||||
json.authors = toArray(answers.authors, ',');
|
||||
json.license = answers.license;
|
||||
@@ -278,12 +268,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'
|
||||
]);
|
||||
@@ -322,16 +313,8 @@ function setDependencies(project, json, answers) {
|
||||
|
||||
// -------------------
|
||||
|
||||
init.line = function () {
|
||||
return init();
|
||||
};
|
||||
|
||||
init.options = function (argv) {
|
||||
return cli.readOptions(argv);
|
||||
};
|
||||
|
||||
init.completion = function () {
|
||||
// TODO:
|
||||
init.readOptions = function (argv) {
|
||||
return [];
|
||||
};
|
||||
|
||||
module.exports = init;
|
||||
|
||||
@@ -1,54 +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 decEndpoints;
|
||||
var logger = new Logger();
|
||||
var tracker;
|
||||
|
||||
options = options || {};
|
||||
config = mout.object.deepFillIn(config || {}, defaultConfig);
|
||||
config = defaultConfig(config);
|
||||
if (options.save === undefined) {
|
||||
options.save = config.defaultSave;
|
||||
}
|
||||
project = new Project(config, logger);
|
||||
tracker = new Tracker(config);
|
||||
|
||||
// Convert endpoints to decomposed endpoints
|
||||
endpoints = endpoints || [];
|
||||
decEndpoints = endpoints.map(function (endpoint) {
|
||||
return endpointParser.decompose(endpoint);
|
||||
});
|
||||
tracker.trackDecomposedEndpoints('install', decEndpoints);
|
||||
|
||||
project.install(decEndpoints, options)
|
||||
.then(function (installed) {
|
||||
logger.emit('end', installed);
|
||||
})
|
||||
.fail(function (error) {
|
||||
logger.emit('error', error);
|
||||
});
|
||||
|
||||
return logger;
|
||||
return project.install(decEndpoints, options, config);
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
install.line = function (argv) {
|
||||
var options = install.options(argv);
|
||||
return install(options.argv.remain.slice(1), options);
|
||||
};
|
||||
install.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
install.options = function (argv) {
|
||||
return cli.readOptions({
|
||||
var options = cli.readOptions({
|
||||
'force-latest': { type: Boolean, shorthand: 'F'},
|
||||
'production': { type: Boolean, shorthand: 'p' },
|
||||
'save': { type: Boolean, shorthand: 'S' },
|
||||
'save-dev': { type: Boolean, shorthand: 'D' }
|
||||
'save-dev': { type: Boolean, shorthand: 'D' },
|
||||
'save-exact': { type: Boolean, shorthand: 'E' }
|
||||
}, argv);
|
||||
};
|
||||
|
||||
install.completion = function () {
|
||||
// TODO:
|
||||
var packages = options.argv.remain.slice(1);
|
||||
|
||||
delete options.argv;
|
||||
|
||||
return [packages, options];
|
||||
};
|
||||
|
||||
module.exports = install;
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
var path = require('path');
|
||||
var rimraf = require('rimraf');
|
||||
var mout = require('mout');
|
||||
var rimraf = require('../util/rimraf');
|
||||
var Q = require('q');
|
||||
var Logger = require('bower-logger');
|
||||
var Project = require('../core/Project');
|
||||
var createLink = require('../util/createLink');
|
||||
var cli = require('../util/cli');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
function linkSelf(config) {
|
||||
var project;
|
||||
var logger = new Logger();
|
||||
function link(logger, name, localName, config) {
|
||||
if (name) {
|
||||
return linkTo(logger, name, localName, config);
|
||||
} else {
|
||||
return linkSelf(logger, config);
|
||||
}
|
||||
}
|
||||
|
||||
config = mout.object.deepFillIn(config || {}, defaultConfig);
|
||||
function linkSelf(logger, config) {
|
||||
var project;
|
||||
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
project.getJson()
|
||||
return project.getJson()
|
||||
.then(function (json) {
|
||||
var src = config.cwd;
|
||||
var dst = path.join(config.storage.links, json.name);
|
||||
@@ -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;
|
||||
|
||||
@@ -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,13 +73,15 @@ 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) {
|
||||
logger.info('check-new', 'Checking for new versions of the project dependencies..');
|
||||
logger.info('check-new', 'Checking for new versions of the project dependencies...');
|
||||
}
|
||||
|
||||
// Check for new versions for each node
|
||||
@@ -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;
|
||||
|
||||
123
lib/commands/login.js
Normal file
123
lib/commands/login.js
Normal file
@@ -0,0 +1,123 @@
|
||||
var Configstore = require('configstore');
|
||||
var GitHub = require('github');
|
||||
var Q = require('q');
|
||||
|
||||
var createError = require('../util/createError');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
function login(logger, options, config) {
|
||||
var configstore = new Configstore('bower-github');
|
||||
|
||||
config = defaultConfig(config);
|
||||
|
||||
var promise;
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (options.token) {
|
||||
promise = Q.resolve({ token: options.token });
|
||||
} else {
|
||||
// This command requires interactive to be enabled
|
||||
if (!config.interactive) {
|
||||
logger.emit('error', createError('Login requires an interactive shell', 'ENOINT', {
|
||||
details: 'Note that you can manually force an interactive shell with --config.interactive'
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var questions = [
|
||||
{
|
||||
'name': 'username',
|
||||
'message': 'Username',
|
||||
'type': 'input',
|
||||
'default': configstore.get('username')
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'message': 'Password',
|
||||
'type': 'password'
|
||||
}
|
||||
];
|
||||
|
||||
var github = new GitHub({
|
||||
version: '3.0.0'
|
||||
});
|
||||
|
||||
promise = Q.nfcall(logger.prompt.bind(logger), questions)
|
||||
.then(function (answers) {
|
||||
configstore.set('username', answers.username);
|
||||
|
||||
github.authenticate({
|
||||
type: 'basic',
|
||||
username: answers.username,
|
||||
password: answers.password
|
||||
});
|
||||
|
||||
return Q.ninvoke(github.authorization, 'create', {
|
||||
scopes: ['user', 'repo'],
|
||||
note: 'Bower command line client (' + (new Date()).toISOString() + ')'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(function (result) {
|
||||
configstore.set('accessToken', result.token);
|
||||
|
||||
return result;
|
||||
}, function (error) {
|
||||
var message;
|
||||
|
||||
try {
|
||||
message = JSON.parse(error.message).message;
|
||||
} catch (e) {
|
||||
message = 'Authorization failed';
|
||||
}
|
||||
|
||||
var questions = [
|
||||
{
|
||||
'name': 'otpcode',
|
||||
'message': 'Two-Factor Auth Code',
|
||||
'type': 'input'
|
||||
}
|
||||
];
|
||||
|
||||
if (message === 'Must specify two-factor authentication OTP code.') {
|
||||
return Q.nfcall(logger.prompt.bind(logger), questions)
|
||||
.then(function (answers) {
|
||||
return Q.ninvoke(github.authorization, 'create', {
|
||||
scopes: ['user', 'repo'],
|
||||
note: 'Bower command line client (' + (new Date()).toISOString() + ')',
|
||||
headers: {
|
||||
'X-GitHub-OTP': answers.otpcode
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(function (result) {
|
||||
configstore.set('accessToken', result.token);
|
||||
|
||||
return result;
|
||||
}, function () {
|
||||
logger.emit('error', createError(message, 'EAUTH'));
|
||||
});
|
||||
} else {
|
||||
logger.emit('error', createError(message, 'EAUTH'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
login.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
var options = cli.readOptions({
|
||||
token: { type: String, shorthand: 't' },
|
||||
}, argv);
|
||||
|
||||
delete options.argv;
|
||||
|
||||
return [options];
|
||||
};
|
||||
|
||||
module.exports = login;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,136 +1,86 @@
|
||||
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 Tracker = require('../util/analytics').Tracker;
|
||||
var createError = require('../util/createError');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
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);
|
||||
|
||||
// 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'));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// Register
|
||||
registryClient = repository.getRegistryClient();
|
||||
|
||||
logger.action('register', url, {
|
||||
name: name,
|
||||
url: url
|
||||
});
|
||||
|
||||
return Q.nfcall(registryClient.register.bind(registryClient), name, url);
|
||||
});
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
function convertUrl(url, logger) {
|
||||
var matches;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
85
lib/commands/unregister.js
Normal file
85
lib/commands/unregister.js
Normal file
@@ -0,0 +1,85 @@
|
||||
var chalk = require('chalk');
|
||||
var Q = require('q');
|
||||
|
||||
var defaultConfig = require('../config');
|
||||
var PackageRepository = require('../core/PackageRepository');
|
||||
var Tracker = require('../util/analytics').Tracker;
|
||||
var createError = require('../util/createError');
|
||||
|
||||
function unregister(logger, name, config) {
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
var repository;
|
||||
var registryClient;
|
||||
var tracker;
|
||||
var force;
|
||||
|
||||
config = defaultConfig(config);
|
||||
force = config.force;
|
||||
tracker = new Tracker(config);
|
||||
|
||||
// Bypass any cache
|
||||
config.offline = false;
|
||||
config.force = true;
|
||||
|
||||
// Trim name
|
||||
name = name.trim();
|
||||
|
||||
repository = new PackageRepository(config, logger);
|
||||
|
||||
tracker.track('unregister');
|
||||
|
||||
if (!config.accessToken) {
|
||||
return logger.emit('error',
|
||||
createError('Use "bower login" with collaborator credentials', 'EFORBIDDEN')
|
||||
);
|
||||
}
|
||||
|
||||
return Q.resolve()
|
||||
.then(function () {
|
||||
// If non interactive or user forced, bypass confirmation
|
||||
if (!config.interactive || force) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Q.nfcall(logger.prompt.bind(logger), {
|
||||
type: 'confirm',
|
||||
message: 'You are about to remove component "' + chalk.cyan.underline(name) + '" from the bower registry (' + chalk.cyan.underline(config.registry.register) + '). It is generally considered bad behavior to remove versions of a library that others are depending on. Are you really sure?',
|
||||
default: false
|
||||
});
|
||||
})
|
||||
.then(function (result) {
|
||||
// If user response was negative, abort
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
registryClient = repository.getRegistryClient();
|
||||
|
||||
logger.action('unregister', name, { name: name });
|
||||
|
||||
return Q.nfcall(registryClient.unregister.bind(registryClient), name);
|
||||
})
|
||||
.then(function (result) {
|
||||
tracker.track('unregistered');
|
||||
logger.info('Package unregistered', name);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
unregister.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
var options = cli.readOptions(argv);
|
||||
var name = options.argv.remain[1];
|
||||
|
||||
return [name];
|
||||
};
|
||||
|
||||
module.exports = unregister;
|
||||
@@ -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,26 @@ 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' }
|
||||
'production': { type: Boolean, shorthand: 'p' },
|
||||
'save': { type: Boolean, shorthand: 'S' },
|
||||
'save-dev': { type: Boolean, shorthand: 'D' }
|
||||
}, argv);
|
||||
};
|
||||
|
||||
update.completion = function () {
|
||||
// TODO:
|
||||
var names = options.argv.remain.slice(1);
|
||||
|
||||
delete options.argv;
|
||||
|
||||
return [names, options];
|
||||
};
|
||||
|
||||
module.exports = update;
|
||||
|
||||
128
lib/commands/version.js
Normal file
128
lib/commands/version.js
Normal file
@@ -0,0 +1,128 @@
|
||||
var semver = require('semver');
|
||||
var which = require('which');
|
||||
var fs = require('../util/fs');
|
||||
var path = require('path');
|
||||
var Q = require('q');
|
||||
var execFile = require('child_process').execFile;
|
||||
var Project = require('../core/Project');
|
||||
var 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);
|
||||
return 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;
|
||||
@@ -1,26 +1,67 @@
|
||||
var tty = require('tty');
|
||||
var mout = require('mout');
|
||||
var config = require('bower-config').read();
|
||||
var cli = require('./util/cli');
|
||||
var object = require('mout').object;
|
||||
var bowerConfig = require('bower-config');
|
||||
var Configstore = require('configstore');
|
||||
|
||||
// Delete the json attribute because it is no longer supported
|
||||
// and conflicts with --json
|
||||
delete config.json;
|
||||
var current;
|
||||
|
||||
// If interactive is auto (null), guess its value
|
||||
if (config.interactive == null) {
|
||||
config.interactive = process.title === 'bower' && tty.isatty(1);
|
||||
function defaultConfig(config) {
|
||||
config = config || {};
|
||||
|
||||
return readCachedConfig(config.cwd || process.cwd(), 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, overwrites) {
|
||||
current = bowerConfig.create(cwd).load(overwrites);
|
||||
|
||||
module.exports = config;
|
||||
var config = current.toObject();
|
||||
|
||||
var configstore = new Configstore('bower-github').all;
|
||||
|
||||
object.mixIn(config, configstore);
|
||||
|
||||
// Delete the json attribute because it is no longer supported
|
||||
// and conflicts with --json
|
||||
delete config.json;
|
||||
|
||||
// If interactive is auto (null), guess its value
|
||||
if (config.interactive == null) {
|
||||
config.interactive = (
|
||||
process.bin === 'bower' &&
|
||||
tty.isatty(1) &&
|
||||
!process.env.CI
|
||||
);
|
||||
}
|
||||
|
||||
// Merge common CLI options into the config
|
||||
if (process.bin === 'bower') {
|
||||
var cli = require('./util/cli');
|
||||
|
||||
object.mixIn(config, cli.readOptions({
|
||||
force: { type: Boolean, shorthand: 'f' },
|
||||
offline: { type: Boolean, shorthand: 'o' },
|
||||
verbose: { type: Boolean, shorthand: 'V' },
|
||||
quiet: { type: Boolean, shorthand: 'q' },
|
||||
loglevel: { type: String, shorthand: 'l' },
|
||||
json: { type: Boolean, shorthand: 'j' },
|
||||
silent: { type: Boolean, shorthand: 's' }
|
||||
}));
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
function restoreConfig () {
|
||||
if (current) {
|
||||
current.restore();
|
||||
}
|
||||
}
|
||||
|
||||
function resetCache () {
|
||||
restoreConfig();
|
||||
current = undefined;
|
||||
}
|
||||
|
||||
module.exports = defaultConfig;
|
||||
module.exports.restore = restoreConfig;
|
||||
module.exports.reset = resetCache;
|
||||
|
||||
@@ -2,13 +2,14 @@ var Q = require('q');
|
||||
var mout = require('mout');
|
||||
var path = require('path');
|
||||
var mkdirp = require('mkdirp');
|
||||
var rimraf = require('rimraf');
|
||||
var fs = require('graceful-fs');
|
||||
var rimraf = require('../util/rimraf');
|
||||
var fs = require('../util/fs');
|
||||
var endpointParser = require('bower-endpoint-parser');
|
||||
var PackageRepository = require('./PackageRepository');
|
||||
var semver = require('../util/semver');
|
||||
var copy = require('../util/copy');
|
||||
var createError = require('../util/createError');
|
||||
var scripts = require('./scripts');
|
||||
|
||||
function Manager(config, logger) {
|
||||
this._config = config;
|
||||
@@ -22,11 +23,13 @@ function Manager(config, logger) {
|
||||
|
||||
Manager.prototype.configure = function (setup) {
|
||||
var targetsHash = {};
|
||||
|
||||
this._conflicted = {};
|
||||
|
||||
// Targets
|
||||
this._targets = setup.targets || [];
|
||||
// Targets - ignore those specified in ignoredDependencies
|
||||
this._targets = mout.array.reject(setup.targets || [], function(target) {
|
||||
return mout.array.contains(this._config.ignoredDependencies, target.name );
|
||||
}, this);
|
||||
|
||||
this._targets.forEach(function (decEndpoint) {
|
||||
decEndpoint.initialName = decEndpoint.name;
|
||||
decEndpoint.dependants = mout.object.values(decEndpoint.dependants);
|
||||
@@ -79,6 +82,17 @@ Manager.prototype.configure = function (setup) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* It starts recursive fetching of targets. The flow is as follows:
|
||||
*
|
||||
* 1. this._fetch() that calls:
|
||||
* 2. this._onFetchError() or this._onFetchSuccess() that calls:
|
||||
* 3. this._parseDependencies() that calls for all dependencies:
|
||||
* 4. this._fetch() again.
|
||||
*
|
||||
* Fetching and parsing of dependencies continues until all dependencies are fetched.
|
||||
*
|
||||
*/
|
||||
Manager.prototype.resolve = function () {
|
||||
// If already resolving, error out
|
||||
if (this._working) {
|
||||
@@ -108,8 +122,43 @@ 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 componentsDirContents;
|
||||
var that = this;
|
||||
|
||||
// If already resolving, error out
|
||||
@@ -117,20 +166,34 @@ 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 () {
|
||||
var promises = [];
|
||||
componentsDirContents = fs.readdirSync(componentsDir);
|
||||
|
||||
mout.object.forOwn(that._dissected, function (decEndpoint, name) {
|
||||
var promise;
|
||||
var dst;
|
||||
var release = decEndpoint.pkgMeta._release;
|
||||
var exists = mout.array.contains(componentsDirContents, name);
|
||||
|
||||
that._logger.action('install', name + (release ? '#' + release : ''), that.toData(decEndpoint));
|
||||
|
||||
dst = path.join(componentsDir, name);
|
||||
|
||||
if (exists && !that._installed[name] && !that._config.force) {
|
||||
// There is a folder in the components directory that has
|
||||
// this name, but it was not installed by bower.
|
||||
that._logger.warn('skipped', name + ' was not installed because there is already a non-bower directory with that name in the components directory (' + path.relative(that._config.cwd, dst) + '). You can force installation with --force.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove existent and copy canonical dir
|
||||
promise = Q.nfcall(rimraf, dst)
|
||||
.then(copy.copyDir.bind(copy, decEndpoint.canonicalDir, dst))
|
||||
@@ -161,6 +224,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 +252,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 +281,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);
|
||||
}
|
||||
|
||||
@@ -270,7 +360,7 @@ Manager.prototype._onFetchSuccess = function (decEndpoint, canonicalDir, pkgMeta
|
||||
resolved.push(decEndpoint);
|
||||
|
||||
// Parse dependencies
|
||||
this._parseDependencies(decEndpoint, pkgMeta, 'dependencies');
|
||||
this._parseDependencies(decEndpoint, pkgMeta);
|
||||
|
||||
// Check if there are incompatibilities for this package name
|
||||
// If there are, we need to fetch them
|
||||
@@ -347,16 +437,32 @@ Manager.prototype._failFast = function () {
|
||||
}.bind(this), 20000);
|
||||
};
|
||||
|
||||
Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta, jsonKey) {
|
||||
/**
|
||||
* Parses decEndpoint dependencies and fetches missing ones.
|
||||
*
|
||||
* It fetches all non-yet-fetching dependencies with _fetch.
|
||||
*
|
||||
*
|
||||
* @param {string} pkgMeta Metadata of package to resolve dependencies.
|
||||
*/
|
||||
Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta) {
|
||||
var pending = [];
|
||||
|
||||
decEndpoint.dependencies = decEndpoint.dependencies || {};
|
||||
|
||||
// Parse package dependencies
|
||||
mout.object.forOwn(pkgMeta[jsonKey], function (value, key) {
|
||||
mout.object.forOwn(pkgMeta.dependencies, function (value, key) {
|
||||
var resolved;
|
||||
var fetching;
|
||||
var compatible;
|
||||
var childDecEndpoint = endpointParser.json2decomposed(key, value);
|
||||
|
||||
// Check if this depdendency should be skipped.
|
||||
if (mout.array.contains(this._config.ignoredDependencies, childDecEndpoint.name)) {
|
||||
this._logger.action('skipped', childDecEndpoint.name, this.toData(decEndpoint));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if a compatible one is already resolved
|
||||
// If there's one, we don't need to resolve it twice
|
||||
resolved = this._resolved[key];
|
||||
@@ -403,10 +509,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,8 +522,28 @@ 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);
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* After batch of packages has been fetched, the results are stored in _resolved hash.
|
||||
*
|
||||
* {
|
||||
* packageName: [array, of, decEndpoints]
|
||||
* }
|
||||
*
|
||||
* This method takes all possible decEndpoints and selects only one of them per packageName.
|
||||
*
|
||||
* It stores selected packages in the this._dissected array.
|
||||
*
|
||||
* The _dissected array is used later on by install method to move packages to config.directory.
|
||||
*/
|
||||
Manager.prototype._dissect = function () {
|
||||
var err;
|
||||
var componentsDir;
|
||||
@@ -443,7 +566,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 +621,16 @@ Manager.prototype._dissect = function () {
|
||||
return;
|
||||
}
|
||||
|
||||
this._logger.info('resolution', 'Removed unnecessary ' + name + '#' + resolution + ' resolution', {
|
||||
// Packages that didn't have any conflicts in resolution yet have
|
||||
// "resolution" entry in bower.json are deemed unnecessary
|
||||
//
|
||||
// Probably in future we want to use them for force given resolution, but for now...
|
||||
// See: https://github.com/bower/bower/issues/1362
|
||||
this._logger.warn('extra-resolution', 'Unnecessary resolution: ' + name + '#' + resolution, {
|
||||
name: name,
|
||||
resolution: resolution,
|
||||
action: 'delete'
|
||||
});
|
||||
|
||||
delete this._resolutions[name];
|
||||
}, this);
|
||||
|
||||
// Filter only packages that need to be installed
|
||||
@@ -513,27 +639,31 @@ 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;
|
||||
}
|
||||
|
||||
// Skip if source is the same as dest
|
||||
// FIXME: Shoudn't we force installation if force flag is used here?
|
||||
dst = path.join(componentsDir, name);
|
||||
if (dst === decEndpoint.canonicalDir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, this);
|
||||
// We skip installing decEndpoint, if:
|
||||
// 1. We have installed package with the same name
|
||||
// 2. Packages have matching meta fields (needs explanation)
|
||||
// 3. We didn't force the installation
|
||||
if (installedMeta &&
|
||||
installedMeta._target === decEndpoint.target &&
|
||||
installedMeta._originalSource === decEndpoint.source &&
|
||||
installedMeta._release === decEndpoint.pkgMeta._release
|
||||
) {
|
||||
return 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 +691,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 +764,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 +810,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 +834,7 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
|
||||
choices = picks.map(function (pick, index) { return index + 1; });
|
||||
return Q.nfcall(this._logger.prompt.bind(this._logger), {
|
||||
type: 'input',
|
||||
message: 'Answer:',
|
||||
message: 'Answer',
|
||||
validate: function (choice) {
|
||||
choice = Number(mout.string.trim(choice.trim(), '!'));
|
||||
|
||||
@@ -706,7 +847,6 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
|
||||
})
|
||||
.then(function (choice) {
|
||||
var pick;
|
||||
var resolution;
|
||||
|
||||
// Sanitize choice
|
||||
choice = choice.trim();
|
||||
@@ -714,30 +854,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 +910,77 @@ Manager.prototype._areCompatible = function (candidate, resolved) {
|
||||
}
|
||||
|
||||
resolvedVersion = resolved.pkgMeta && resolved.pkgMeta.version;
|
||||
// If there is no pkgMeta, resolvedVersion is downloading now
|
||||
// Check based on target requirements
|
||||
if (!resolvedVersion) {
|
||||
// If one of the targets is range and other is version,
|
||||
// check version against the range
|
||||
if (candidateIsVersion && resolvedIsRange) {
|
||||
return semver.satisfies(candidate.target, resolved.target);
|
||||
}
|
||||
|
||||
if (resolvedIsVersion && candidateIsRange) {
|
||||
return semver.satisfies(resolved.target, candidate.target);
|
||||
}
|
||||
|
||||
if (resolvedIsVersion && candidateIsVersion) {
|
||||
return semver.eq(resolved.target, candidate.target);
|
||||
}
|
||||
|
||||
// If both targets are range, check that both have same
|
||||
// higher cap
|
||||
if (resolvedIsRange && candidateIsRange) {
|
||||
highestCandidate =
|
||||
this._getCap(semver.toComparators(candidate.target), 'highest');
|
||||
highestResolved =
|
||||
this._getCap(semver.toComparators(resolved.target), 'highest');
|
||||
|
||||
// This never happens, but you can't be sure without tests
|
||||
if (!highestResolved.version || !highestCandidate.version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return semver.eq(highestCandidate.version, highestResolved.version) &&
|
||||
highestCandidate.comparator === highestResolved.comparator;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// If target is a version, compare against the resolved version
|
||||
if (semver.valid(candidate.target)) {
|
||||
if (candidateIsVersion) {
|
||||
return semver.eq(candidate.target, resolvedVersion);
|
||||
}
|
||||
|
||||
// If target is a range, check if the max versions of the range are the same
|
||||
// and if the resolved version satisfies the candidate target
|
||||
if (semver.validRange(candidate.target)) {
|
||||
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 +1016,23 @@ Manager.prototype._getCap = function (comparators, side) {
|
||||
return cap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters out unique endpoints, comparing by name and then source.
|
||||
*
|
||||
* It leaves last matching endpoint.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* manager._uniquify([
|
||||
* { name: 'foo', source: 'google.com' },
|
||||
* { name: 'foo', source: 'facebook.com' }
|
||||
* ]);
|
||||
* // => { name: 'foo', source: 'facebook.com' }
|
||||
*
|
||||
* @param {Array.<Endpoint>} decEndpoints
|
||||
* @return {Array.<Endpoint>} Filtered elements of decEndpoints
|
||||
*
|
||||
*/
|
||||
Manager.prototype._uniquify = function (decEndpoints) {
|
||||
var length = decEndpoints.length;
|
||||
|
||||
|
||||
@@ -38,15 +38,18 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
|
||||
that._extendLog(log, info);
|
||||
});
|
||||
|
||||
// Get the appropriate resolver
|
||||
return resolverFactory(decEndpoint, this._config, logger, this._registryClient)
|
||||
return this._getResolver(decEndpoint, logger)
|
||||
// Decide if we retrieve from the cache or not
|
||||
// Also decide if we validate the cached entry or not
|
||||
.then(function (resolver) {
|
||||
info.resolver = resolver;
|
||||
isTargetable = resolver.constructor.isTargetable;
|
||||
|
||||
// If force flag is used, bypass cache
|
||||
if (!resolver.isCacheable()) {
|
||||
return that._resolve(resolver, logger);
|
||||
}
|
||||
|
||||
// If force flag is used, bypass cache, but write to cache anyway
|
||||
if (that._config.force) {
|
||||
logger.action('resolve', resolver.getSource() + '#' + resolver.getTarget());
|
||||
return that._resolve(resolver, logger);
|
||||
@@ -87,7 +90,7 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
|
||||
logger.action('validate', (pkgMeta._release ? pkgMeta._release + ' against ': '') +
|
||||
resolver.getSource() + (resolver.getTarget() ? '#' + resolver.getTarget() : ''));
|
||||
|
||||
return resolver.hasNew(canonicalDir, pkgMeta)
|
||||
return resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
// If there are no new contents, resolve to
|
||||
// the cached one
|
||||
@@ -113,15 +116,15 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
|
||||
PackageRepository.prototype.versions = function (source) {
|
||||
// Resolve the source using the factory because the
|
||||
// source can actually be a registry name
|
||||
return resolverFactory.getConstructor(source, this._config, this._registryClient)
|
||||
.spread(function (ConcreteResolver, source) {
|
||||
return this._getResolver({ source: source })
|
||||
.then(function (resolver) {
|
||||
// If offline, resolve using the cached versions
|
||||
if (this._config.offline) {
|
||||
return this._resolveCache.versions(source);
|
||||
return this._resolveCache.versions(resolver.getSource());
|
||||
}
|
||||
|
||||
// Otherwise, fetch remotely
|
||||
return ConcreteResolver.versions(source);
|
||||
return resolver.constructor.versions(resolver.getSource());
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
@@ -164,20 +167,33 @@ PackageRepository.clearRuntimeCache = function () {
|
||||
|
||||
// ---------------------
|
||||
|
||||
PackageRepository.prototype._getResolver = function (decEndpoint, logger) {
|
||||
logger = logger || this._logger;
|
||||
|
||||
// Get the appropriate resolver
|
||||
return resolverFactory(decEndpoint, { config: this._config, logger: logger }, this._registryClient);
|
||||
};
|
||||
|
||||
PackageRepository.prototype._resolve = function (resolver, logger) {
|
||||
var that = this;
|
||||
|
||||
// Resolve the resolver
|
||||
return resolver.resolve()
|
||||
// Store in the cache
|
||||
.then(function (canonicalDir) {
|
||||
return this._resolveCache.store(canonicalDir, resolver.getPkgMeta());
|
||||
}.bind(this))
|
||||
if (!resolver.isCacheable()) {
|
||||
return canonicalDir;
|
||||
}
|
||||
|
||||
return that._resolveCache.store(canonicalDir, resolver.getPkgMeta());
|
||||
})
|
||||
// Resolve promise with canonical dir and package meta
|
||||
.then(function (dir) {
|
||||
var pkgMeta = resolver.getPkgMeta();
|
||||
|
||||
logger.info('resolved', resolver.getSource() + (pkgMeta._release ? '#' + pkgMeta._release : ''));
|
||||
return [dir, pkgMeta, resolver.constructor.isTargetable()];
|
||||
}.bind(this));
|
||||
});
|
||||
};
|
||||
|
||||
PackageRepository.prototype._extendLog = function (log, info) {
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
var glob = require('glob');
|
||||
var path = require('path');
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('../util/fs');
|
||||
var Q = require('q');
|
||||
var mout = require('mout');
|
||||
var rimraf = require('rimraf');
|
||||
var rimraf = require('../util/rimraf');
|
||||
var endpointParser = require('bower-endpoint-parser');
|
||||
var Logger = require('bower-logger');
|
||||
var md5 = require('md5-hex');
|
||||
var Manager = require('./Manager');
|
||||
var defaultConfig = require('../config');
|
||||
var semver = require('../util/semver');
|
||||
var md5 = require('../util/md5');
|
||||
var createError = require('../util/createError');
|
||||
var readJson = require('../util/readJson');
|
||||
var validLink = require('../util/validLink');
|
||||
var scripts = require('./scripts');
|
||||
|
||||
function Project(config, logger) {
|
||||
// This is the only architecture component that ensures defaults
|
||||
// on config and logger
|
||||
// The reason behind it is that users can likely use this component
|
||||
// directly if commands do not fulfil their needs
|
||||
this._config = config || defaultConfig;
|
||||
this._config = config;
|
||||
this._logger = logger || new Logger();
|
||||
this._manager = new Manager(this._config, this._logger);
|
||||
|
||||
@@ -28,7 +24,7 @@ function Project(config, logger) {
|
||||
|
||||
// -----------------
|
||||
|
||||
Project.prototype.install = function (decEndpoints, options) {
|
||||
Project.prototype.install = function (decEndpoints, options, config) {
|
||||
var that = this;
|
||||
var targets = [];
|
||||
var resolved = {};
|
||||
@@ -40,27 +36,28 @@ Project.prototype.install = function (decEndpoints, options) {
|
||||
}
|
||||
|
||||
this._options = options || {};
|
||||
this._config = config || {};
|
||||
this._working = true;
|
||||
|
||||
// Analyse the project
|
||||
return this._analyse()
|
||||
.spread(function (json, tree) {
|
||||
// It shows an error when issuing `bower install`
|
||||
// and no bower.json is present in current directory
|
||||
if(!that._jsonFile && decEndpoints.length === 0 ) {
|
||||
throw createError('No bower.json present', 'ENOENT');
|
||||
}
|
||||
|
||||
// Recover tree
|
||||
that.walkTree(tree, function (node, name) {
|
||||
if (node.missing || node.different) {
|
||||
targets.push(node);
|
||||
} else if (node.incompatible) {
|
||||
if (node.incompatible) {
|
||||
incompatibles.push(node);
|
||||
} else if (node.missing || node.different || that._config.force) {
|
||||
targets.push(node);
|
||||
} else {
|
||||
resolved[name] = node;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Add decomposed endpoints as targets
|
||||
decEndpoints = decEndpoints || [];
|
||||
@@ -76,21 +73,33 @@ Project.prototype.install = function (decEndpoints, options) {
|
||||
// Bootstrap the process
|
||||
return that._bootstrap(targets, resolved, incompatibles);
|
||||
})
|
||||
.then(function () {
|
||||
return that._manager.preinstall(that._json);
|
||||
})
|
||||
.then(function () {
|
||||
return that._manager.install(that._json);
|
||||
})
|
||||
.then(function (installed) {
|
||||
// Handle save and saveDev options
|
||||
if (that._options.save || that._options.saveDev) {
|
||||
if (that._options.save || that._options.saveDev || that._options.saveExact) {
|
||||
// Cycle through the specified endpoints
|
||||
decEndpoints.forEach(function (decEndpoint) {
|
||||
var jsonEndpoint;
|
||||
|
||||
jsonEndpoint = endpointParser.decomposed2json(decEndpoint);
|
||||
|
||||
if (that._options.save) {
|
||||
that._json.dependencies = mout.object.mixIn(that._json.dependencies || {}, jsonEndpoint);
|
||||
if (that._options.saveExact) {
|
||||
if (decEndpoint.name !== decEndpoint.source) {
|
||||
jsonEndpoint[decEndpoint.name] = decEndpoint.source + '#' + decEndpoint.pkgMeta.version;
|
||||
} else {
|
||||
jsonEndpoint[decEndpoint.name] = decEndpoint.pkgMeta.version;
|
||||
}
|
||||
}
|
||||
|
||||
if (that._options.saveDev) {
|
||||
that._json.devDependencies = mout.object.mixIn(that._json.devDependencies || {}, jsonEndpoint);
|
||||
} else {
|
||||
that._json.dependencies = mout.object.mixIn(that._json.dependencies || {}, jsonEndpoint);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -98,7 +107,9 @@ Project.prototype.install = function (decEndpoints, options) {
|
||||
// Save JSON, might contain changes to dependencies and resolutions
|
||||
return that.saveJson()
|
||||
.then(function () {
|
||||
return installed;
|
||||
return that._manager.postinstall(that._json).then(function () {
|
||||
return installed;
|
||||
});
|
||||
});
|
||||
})
|
||||
.fin(function () {
|
||||
@@ -128,13 +139,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
|
||||
@@ -151,14 +163,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);
|
||||
@@ -172,22 +185,45 @@ 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) {
|
||||
if (that._options.save || that._options.saveDev) {
|
||||
// Cycle through the specified endpoints
|
||||
targets.forEach(function (target) {
|
||||
// Abort if current and new version are identical
|
||||
if (target.target === target.pkgMeta.version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var jsonEndpoint = endpointParser.decomposed2json(target);
|
||||
// Bower uses the ~ range specifier for new installs
|
||||
jsonEndpoint[target.name] = '~' + target.pkgMeta.version;
|
||||
|
||||
if (that._options.saveDev && mout.object.has(that._json, 'devDependencies.' + target.name)) {
|
||||
that._json.devDependencies = mout.object.mixIn(that._json.devDependencies || {}, jsonEndpoint);
|
||||
}
|
||||
|
||||
if (that._options.save && mout.object.has(that._json, 'dependencies.' + target.name)) {
|
||||
that._json.dependencies = mout.object.mixIn(that._json.dependencies || {}, jsonEndpoint);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Save JSON, might contain changes to resolutions
|
||||
return that.saveJson()
|
||||
.then(function () {
|
||||
return installed;
|
||||
return that._manager.postinstall(that._json).then(function () {
|
||||
return installed;
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -306,7 +342,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 = [];
|
||||
@@ -361,7 +399,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) {
|
||||
@@ -448,7 +486,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,
|
||||
@@ -459,41 +496,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));
|
||||
};
|
||||
|
||||
@@ -520,9 +567,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 () {
|
||||
@@ -532,9 +577,29 @@ Project.prototype._readJson = function () {
|
||||
return Q.resolve(this._json);
|
||||
}
|
||||
|
||||
// Read local json
|
||||
return this._json = readJson(this._config.cwd, {
|
||||
assume: { name: path.basename(this._config.cwd) || 'root' }
|
||||
return Q.fcall(function() {
|
||||
// This will throw if package.json does not exist
|
||||
return fs.readFileSync(path.join(that._config.cwd, 'package.json'));
|
||||
})
|
||||
.then(function(buffer) {
|
||||
// If package.json exists, use it's values as defaults
|
||||
var defaults = {}, npm = JSON.parse(buffer.toString());
|
||||
|
||||
defaults.name = npm.name || path.basename(that._config.cwd) || 'root';
|
||||
defaults.description = npm.description;
|
||||
defaults.main = npm.main;
|
||||
defaults.authors = npm.contributors || npm.author;
|
||||
defaults.license = npm.license;
|
||||
defaults.keywords = npm.keywords;
|
||||
|
||||
return defaults;
|
||||
})
|
||||
.catch(function(err) {
|
||||
// Most likely no package.json so just set default name
|
||||
return { name: path.basename(that._config.cwd) || 'root' };
|
||||
})
|
||||
.then(function(defaults) {
|
||||
return that._json = readJson(that._config.cwd, { assume: defaults });
|
||||
})
|
||||
.spread(function (json, deprecated, assumed) {
|
||||
var jsonStr;
|
||||
@@ -675,43 +740,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();
|
||||
})
|
||||
@@ -723,7 +792,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
|
||||
@@ -733,10 +802,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) {
|
||||
@@ -772,21 +842,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);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('../util/fs');
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
var mkdirp = require('mkdirp');
|
||||
var rimraf = require('rimraf');
|
||||
var rimraf = require('../util/rimraf');
|
||||
var LRU = require('lru-cache');
|
||||
var lockFile = require('lockfile');
|
||||
var md5 = require('md5-hex');
|
||||
var semver = require('../util/semver');
|
||||
var readJson = require('../util/readJson');
|
||||
var copy = require('../util/copy');
|
||||
var md5 = require('../util/md5');
|
||||
|
||||
function ResolveCache(config) {
|
||||
// TODO: Make some config entries, such as:
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,146 +1,217 @@
|
||||
/*jshint laxbreak:true*/
|
||||
var Q = require('q');
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('../util/fs');
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var resolvers = require('./resolvers');
|
||||
var createError = require('../util/createError');
|
||||
|
||||
function createInstance(decEndpoint, config, logger, registryClient) {
|
||||
return getConstructor(decEndpoint.source, config, registryClient)
|
||||
.spread(function (ConcreteResolver, source, fromRegistry) {
|
||||
var decEndpointCopy = mout.object.pick(decEndpoint, ['name', 'target']);
|
||||
var pluginResolverFactory = require('./resolvers/pluginResolverFactory');
|
||||
|
||||
decEndpointCopy.source = source;
|
||||
function createInstance(decEndpoint, options, registryClient) {
|
||||
decEndpoint = mout.object.pick(decEndpoint, ['name', 'target', 'source']);
|
||||
|
||||
// Signal if it was fetched from the registry
|
||||
if (fromRegistry) {
|
||||
decEndpoint.registry = true;
|
||||
// If no name was specified, assume the name from the registry
|
||||
if (!decEndpointCopy.name) {
|
||||
decEndpointCopy.name = decEndpoint.name = decEndpoint.source;
|
||||
}
|
||||
}
|
||||
options.version = require('../../package.json').version;
|
||||
|
||||
return new ConcreteResolver(decEndpointCopy, config, logger);
|
||||
return getConstructor(decEndpoint, options, registryClient)
|
||||
.spread(function (ConcreteResolver, decEndpoint) {
|
||||
return new ConcreteResolver(decEndpoint, options.config, options.logger);
|
||||
});
|
||||
}
|
||||
|
||||
function getConstructor(source, config, registryClient) {
|
||||
var absolutePath,
|
||||
promise;
|
||||
function getConstructor(decEndpoint, options, registryClient) {
|
||||
var source = decEndpoint.source;
|
||||
var config = options.config;
|
||||
|
||||
// Below we try a series of async tests to guess the type of resolver to use
|
||||
// If a step was unable to guess the resolver, it returns undefined
|
||||
// If a step can guess the resolver, it returns with construcotor of resolver
|
||||
|
||||
var promise = Q.resolve();
|
||||
|
||||
var addResolver = function (resolverFactory) {
|
||||
promise = promise.then(function (result) {
|
||||
if (result === undefined) {
|
||||
return resolverFactory(decEndpoint, options);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Plugin resolvers.
|
||||
//
|
||||
// It requires each resolver defined in config.resolvers and calls
|
||||
// its "match" to check if given resolves supports given decEndpoint
|
||||
addResolver(function () {
|
||||
var selectedResolver;
|
||||
var resolverNames;
|
||||
|
||||
if (Array.isArray(config.resolvers)) {
|
||||
resolverNames = config.resolvers;
|
||||
} else if (!!config.resolvers) {
|
||||
resolverNames = config.resolvers.split(',');
|
||||
} else {
|
||||
resolverNames = [];
|
||||
}
|
||||
|
||||
var resolverPromises = resolverNames.map(function (resolverName) {
|
||||
var resolver = resolvers[resolverName]
|
||||
|| pluginResolverFactory(require(resolverName), options);
|
||||
|
||||
return function () {
|
||||
if (selectedResolver === undefined) {
|
||||
var match = resolver.match.bind(resolver);
|
||||
|
||||
return Q.fcall(match, source).then(function (result) {
|
||||
if (result) {
|
||||
return selectedResolver = resolver;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return selectedResolver;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return resolverPromises.reduce(Q.when, new Q(undefined)).then(function (resolver) {
|
||||
if (resolver) {
|
||||
return Q.fcall(resolver.locate.bind(resolver), decEndpoint.source).then(function (result) {
|
||||
if (result && result !== decEndpoint.source) {
|
||||
decEndpoint.source = result;
|
||||
decEndpoint.registry = true;
|
||||
return getConstructor(decEndpoint, options, registryClient);
|
||||
} else {
|
||||
return [resolver, decEndpoint];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Git case: git git+ssh, git+http, git+https
|
||||
// .git at the end (probably ssh shorthand)
|
||||
// git@ at the start
|
||||
if (/^git(\+(ssh|https?))?:\/\//i.test(source) || /\.git\/?$/i.test(source) || /^git@/i.test(source)) {
|
||||
source = source.replace(/^git\+/, '');
|
||||
return Q.fcall(function () {
|
||||
addResolver(function() {
|
||||
if (/^git(\+(ssh|https?))?:\/\//i.test(source) || /\.git\/?$/i.test(source) || /^git@/i.test(source)) {
|
||||
decEndpoint.source = source.replace(/^git\+/, '');
|
||||
|
||||
// If it's a GitHub repository, return the specialized resolver
|
||||
if (resolvers.GitHub.getOrgRepoPair(source)) {
|
||||
return [resolvers.GitHub, source];
|
||||
return [resolvers.GitHub, decEndpoint];
|
||||
}
|
||||
|
||||
return [resolvers.GitRemote, source];
|
||||
});
|
||||
}
|
||||
return [resolvers.GitRemote, decEndpoint];
|
||||
}
|
||||
});
|
||||
|
||||
// SVN case: svn, svn+ssh, svn+http, svn+https, svn+file
|
||||
addResolver(function () {
|
||||
if (/^svn(\+(ssh|https?|file))?:\/\//i.test(source)) {
|
||||
return [resolvers.Svn, decEndpoint];
|
||||
}
|
||||
});
|
||||
|
||||
// URL case
|
||||
if (/^https?:\/\//i.exec(source)) {
|
||||
return Q.fcall(function () {
|
||||
return [resolvers.Url, source];
|
||||
});
|
||||
}
|
||||
addResolver(function () {
|
||||
if (/^https?:\/\//i.exec(source)) {
|
||||
return [resolvers.Url, decEndpoint];
|
||||
}
|
||||
});
|
||||
|
||||
// Below we try a series of async tests to guess the type of resolver to use
|
||||
// If a step was unable to guess the resolver, it throws an error
|
||||
// If a step was able to guess the resolver, it resolves with a function
|
||||
// That function returns a promise that will resolve with the concrete type
|
||||
|
||||
// If source is ./ or ../ or an absolute path
|
||||
absolutePath = path.resolve(config.cwd, source);
|
||||
|
||||
if (/^\.\.?[\/\\]/.test(source) || /^~\//.test(source) || path.normalize(source) === absolutePath) {
|
||||
promise = Q.nfcall(fs.stat, path.join(absolutePath, '.git'))
|
||||
.then(function (stats) {
|
||||
if (stats.isDirectory()) {
|
||||
return function () {
|
||||
return Q.resolve([resolvers.GitFs, absolutePath]);
|
||||
};
|
||||
}
|
||||
addResolver(function () {
|
||||
var absolutePath = path.resolve(config.cwd, source);
|
||||
|
||||
throw new Error('Not a Git repository');
|
||||
})
|
||||
// If not, check if source is a valid file/folder
|
||||
.fail(function () {
|
||||
return Q.nfcall(fs.stat, absolutePath)
|
||||
.then(function () {
|
||||
return function () {
|
||||
return Q.resolve([resolvers.Fs, absolutePath]);
|
||||
};
|
||||
if (/^\.\.?[\/\\]/.test(source) || /^~\//.test(source) ||
|
||||
path.normalize(source).replace(/[\/\\]+$/, '') === absolutePath
|
||||
) {
|
||||
return Q.nfcall(fs.stat, path.join(absolutePath, '.git'))
|
||||
.then(function (stats) {
|
||||
decEndpoint.source = absolutePath;
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return Q.resolve([resolvers.GitFs, decEndpoint]);
|
||||
}
|
||||
|
||||
throw new Error('Not a Git repository');
|
||||
})
|
||||
// If not, check if source is a valid Subversion repository
|
||||
.fail(function () {
|
||||
return Q.nfcall(fs.stat, path.join(absolutePath, '.svn'))
|
||||
.then(function (stats) {
|
||||
decEndpoint.source = absolutePath;
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return Q.resolve([resolvers.Svn, decEndpoint]);
|
||||
}
|
||||
|
||||
throw new Error('Not a Subversion repository');
|
||||
});
|
||||
})
|
||||
// If not, check if source is a valid file/folder
|
||||
.fail(function () {
|
||||
return Q.nfcall(fs.stat, absolutePath)
|
||||
.then(function () {
|
||||
decEndpoint.source = absolutePath;
|
||||
|
||||
return Q.resolve([resolvers.Fs, decEndpoint]);
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
promise = Q.reject(new Error('Not an absolute or relative file'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return promise
|
||||
// Check if is a shorthand and expand it
|
||||
.fail(function (err) {
|
||||
var parts;
|
||||
|
||||
addResolver(function () {
|
||||
// Skip ssh and/or URL with auth
|
||||
if (/[:@]/.test(source)) {
|
||||
throw err;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure exactly only one "/"
|
||||
parts = source.split('/');
|
||||
var parts = source.split('/');
|
||||
if (parts.length === 2) {
|
||||
source = mout.string.interpolate(config.shorthandResolver, {
|
||||
decEndpoint.source = mout.string.interpolate(config.shorthandResolver, {
|
||||
shorthand: source,
|
||||
owner: parts[0],
|
||||
package: parts[1]
|
||||
});
|
||||
|
||||
return function () {
|
||||
return getConstructor(source, config, registryClient);
|
||||
};
|
||||
return getConstructor(decEndpoint, options, registryClient);
|
||||
}
|
||||
});
|
||||
|
||||
throw err;
|
||||
})
|
||||
// As last resort, we try the registry
|
||||
.fail(function (err) {
|
||||
addResolver(function () {
|
||||
if (!registryClient) {
|
||||
throw err;
|
||||
return;
|
||||
}
|
||||
|
||||
return function () {
|
||||
return Q.nfcall(registryClient.lookup.bind(registryClient), source)
|
||||
.then(function (entry) {
|
||||
if (!entry) {
|
||||
throw createError('Package ' + source + ' not found', 'ENOTFOUND');
|
||||
}
|
||||
return Q.nfcall(registryClient.lookup.bind(registryClient), source)
|
||||
.then(function (entry) {
|
||||
if (!entry) {
|
||||
throw createError('Package ' + source + ' not found', 'ENOTFOUND');
|
||||
}
|
||||
|
||||
// TODO: Handle entry.type.. for now it's only 'alias'
|
||||
// When we got published packages, this needs to be adjusted
|
||||
source = entry.url;
|
||||
decEndpoint.registry = true;
|
||||
|
||||
return getConstructor(source, config, registryClient)
|
||||
.spread(function (ConcreteResolver, source) {
|
||||
return [ConcreteResolver, source, true];
|
||||
});
|
||||
});
|
||||
};
|
||||
})
|
||||
// If we got the function, simply call and return
|
||||
.then(function (func) {
|
||||
return func();
|
||||
// Finally throw a meaningful error
|
||||
}, function () {
|
||||
if (!decEndpoint.name) {
|
||||
decEndpoint.name = decEndpoint.source;
|
||||
}
|
||||
|
||||
decEndpoint.source = entry.url;
|
||||
|
||||
return getConstructor(decEndpoint, options);
|
||||
});
|
||||
});
|
||||
|
||||
addResolver(function () {
|
||||
throw createError('Could not find appropriate resolver for ' + source, 'ENORESOLVER');
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function clearRuntimeCache() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var util = require('util');
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('../../util/fs');
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
var GitRemoteResolver = require('./GitRemoteResolver');
|
||||
var download = require('../../util/download');
|
||||
var extract = require('../../util/extract');
|
||||
@@ -35,6 +36,11 @@ function GitHubResolver(decEndpoint, config, logger) {
|
||||
if (this._config.proxy || this._config.httpsProxy) {
|
||||
this._source = this._source.replace('git://', 'https://');
|
||||
}
|
||||
|
||||
// Enable shallow clones for GitHub repos
|
||||
this._shallowClone = function() {
|
||||
return Q.resolve(true);
|
||||
};
|
||||
}
|
||||
|
||||
util.inherits(GitHubResolver, GitRemoteResolver);
|
||||
@@ -66,7 +72,7 @@ GitHubResolver.prototype._checkout = function () {
|
||||
|
||||
// Download tarball
|
||||
return download(tarballUrl, file, {
|
||||
proxy: this._config.httpsProxy,
|
||||
ca: this._config.ca.default,
|
||||
strictSSL: this._config.strictSsl,
|
||||
timeout: this._config.timeout,
|
||||
headers: reqHeaders
|
||||
@@ -81,9 +87,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 () {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/*jshint laxbreak:true*/
|
||||
var util = require('util');
|
||||
var url = require('url');
|
||||
var Q = require('q');
|
||||
@@ -25,6 +26,11 @@ function GitRemoteResolver(decEndpoint, config, logger) {
|
||||
} else {
|
||||
this._host = url.parse(this._source).host;
|
||||
}
|
||||
|
||||
this._remote = url.parse(this._source);
|
||||
|
||||
// Verify whether the server supports shallow cloning
|
||||
this._shallowClone = this._supportsShallowCloning;
|
||||
}
|
||||
|
||||
util.inherits(GitRemoteResolver, GitResolver);
|
||||
@@ -54,9 +60,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 +87,7 @@ GitRemoteResolver.prototype._checkout = function () {
|
||||
// Clear timer at the end
|
||||
.fin(function () {
|
||||
clearTimeout(timer);
|
||||
reporter.cancel();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -110,34 +118,36 @@ GitRemoteResolver.prototype._fastClone = function (resolution) {
|
||||
branch = resolution.tag || resolution.branch;
|
||||
args = ['clone', this._source, '-b', branch, '--progress', '.'];
|
||||
|
||||
// If the host does not support shallow clones, we don't use --depth=1
|
||||
if (!GitRemoteResolver._noShallow.get(this._host)) {
|
||||
args.push('--depth', 1);
|
||||
}
|
||||
|
||||
return cmd('git', args, { cwd: this._tempDir })
|
||||
.spread(function (stdout, stderr) {
|
||||
// Only after 1.7.10 --branch accepts tags
|
||||
// Detect those cases and inform the user to update git otherwise it's
|
||||
// a lot slower than newer versions
|
||||
if (!/branch .+? not found/i.test(stderr)) {
|
||||
return;
|
||||
return this._shallowClone().then(function (shallowCloningSupported) {
|
||||
// If the host does not support shallow clones, we don't use --depth=1
|
||||
if (shallowCloningSupported && !GitRemoteResolver._noShallow.get(that._host)) {
|
||||
args.push('--depth', 1);
|
||||
}
|
||||
|
||||
that._logger.warn('old-git', 'It seems you are using an old version of git, it will be slower and propitious to errors!');
|
||||
return cmd('git', ['checkout', resolution.commit], { cwd: that._tempDir });
|
||||
}, function (err) {
|
||||
// Some git servers do not support shallow clones
|
||||
// When that happens, we mark this host and try again
|
||||
if (!GitRemoteResolver._noShallow.has(that._source) &&
|
||||
err.details &&
|
||||
/(rpc failed|shallow)/i.test(err.details)
|
||||
) {
|
||||
GitRemoteResolver._noShallow.set(that._host, true);
|
||||
return that._fastClone(resolution);
|
||||
}
|
||||
return cmd('git', args, { cwd: that._tempDir })
|
||||
.spread(function (stdout, stderr) {
|
||||
// Only after 1.7.10 --branch accepts tags
|
||||
// Detect those cases and inform the user to update git otherwise it's
|
||||
// a lot slower than newer versions
|
||||
if (!/branch .+? not found/i.test(stderr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw err;
|
||||
that._logger.warn('old-git', 'It seems you are using an old version of git, it will be slower and propitious to errors!');
|
||||
return cmd('git', ['checkout', resolution.commit], { cwd: that._tempDir });
|
||||
}, function (err) {
|
||||
// Some git servers do not support shallow clones
|
||||
// When that happens, we mark this host and try again
|
||||
if (!GitRemoteResolver._noShallow.has(that._source) &&
|
||||
err.details &&
|
||||
/(rpc failed|shallow|--depth)/i.test(err.details)
|
||||
) {
|
||||
GitRemoteResolver._noShallow.set(that._host, true);
|
||||
return that._fastClone(resolution);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -155,6 +165,79 @@ GitRemoteResolver.prototype._suggestProxyWorkaround = function (err) {
|
||||
}
|
||||
};
|
||||
|
||||
// Verifies whether the server supports shallow cloning.
|
||||
// This is done according to the rules found in the following links:
|
||||
// * https://github.com/dimitri/el-get/pull/1921/files
|
||||
// * http://stackoverflow.com/questions/9270488/is-it-possible-to-detect-whether-a-http-git-remote-is-smart-or-dumb
|
||||
//
|
||||
// Summary of the rules:
|
||||
// * Protocols like ssh or git always support shallow cloning
|
||||
// * HTTP-based protocols can be verified by sending a HEAD or GET request to the URI (appended to the URL of the Git repo):
|
||||
// /info/refs?service=git-upload-pack
|
||||
// * If the server responds with a 'Content-Type' header of 'application/x-git-upload-pack-advertisement',
|
||||
// the server supports shallow cloning ("smart server")
|
||||
// * If the server responds with a different content type, the server does not support shallow cloning ("dumb server")
|
||||
// * Instead of doing the HEAD or GET request using an HTTP client, we're letting Git and Curl do the heavy lifting.
|
||||
// Calling Git with the GIT_CURL_VERBOSE=2 env variable will provide the Git and Curl output, which includes
|
||||
// the content type. This has the advantage that Git will take care of using stored credentials and any additional
|
||||
// negotiation that needs to take place.
|
||||
//
|
||||
// The above should cover most cases, including BitBucket.
|
||||
GitRemoteResolver.prototype._supportsShallowCloning = function () {
|
||||
var value = true;
|
||||
|
||||
// Verify that the remote could be parsed and that a protocol is set
|
||||
// This case is unlikely, but let's still cover it.
|
||||
if (this._remote == null || this._remote.protocol == null) {
|
||||
return Q.resolve(false);
|
||||
}
|
||||
|
||||
|
||||
if (!this._host || !this._config.shallowCloneHosts || this._config.shallowCloneHosts.indexOf(this._host) === -1) {
|
||||
return Q.resolve(false);
|
||||
}
|
||||
|
||||
// Check for protocol - the remote check for hosts supporting shallow cloning is only required for
|
||||
// HTTP or HTTPS, not for Git or SSH.
|
||||
// Also check for hosts that have been checked in a previous request and have been found to support
|
||||
// shallow cloning.
|
||||
if (mout.string.startsWith(this._remote.protocol, 'http')
|
||||
&& !GitRemoteResolver._canShallow.get(this._host)) {
|
||||
// Provide GIT_CURL_VERBOSE=2 environment variable to capture curl output.
|
||||
// Calling ls-remote includes a call to the git-upload-pack service, which returns the content type in the response.
|
||||
var processEnv = mout.object.merge(process.env, { 'GIT_CURL_VERBOSE': '2' });
|
||||
|
||||
value = cmd('git', ['ls-remote', '--heads', this._source], {
|
||||
env: processEnv
|
||||
})
|
||||
.spread(function (stdout, stderr) {
|
||||
// Check stderr for content-type, ignore stdout
|
||||
var isSmartServer;
|
||||
|
||||
// If the content type is 'x-git', then the server supports shallow cloning
|
||||
isSmartServer = mout.string.contains(stderr,
|
||||
'Content-Type: application/x-git-upload-pack-advertisement');
|
||||
|
||||
this._logger.debug('detect-smart-git', 'Smart Git host detected: ' + isSmartServer);
|
||||
|
||||
if (isSmartServer) {
|
||||
// Cache this host
|
||||
GitRemoteResolver._canShallow.set(this._host, true);
|
||||
}
|
||||
|
||||
return isSmartServer;
|
||||
}.bind(this));
|
||||
}
|
||||
else {
|
||||
// One of the following cases:
|
||||
// * A non-HTTP/HTTPS protocol
|
||||
// * A host that has been checked before and that supports shallow cloning
|
||||
return Q.resolve(true);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
// ------------------------------
|
||||
|
||||
// Grab refs remotely
|
||||
@@ -185,7 +268,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;
|
||||
};
|
||||
@@ -193,4 +276,7 @@ GitRemoteResolver.refs = function (source) {
|
||||
// Store hosts that do not support shallow clones here
|
||||
GitRemoteResolver._noShallow = new LRU({ max: 50, maxAge: 5 * 60 * 1000 });
|
||||
|
||||
// Store hosts that support shallow clones here
|
||||
GitRemoteResolver._canShallow = new LRU({ max: 50, maxAge: 5 * 60 * 1000 });
|
||||
|
||||
module.exports = GitRemoteResolver;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
var Q = require('q');
|
||||
var chmodr = require('chmodr');
|
||||
var rimraf = require('rimraf');
|
||||
var rimraf = require('../../util/rimraf');
|
||||
var mkdirp = require('mkdirp');
|
||||
var which = require('which');
|
||||
var LRU = require('lru-cache');
|
||||
@@ -10,7 +9,6 @@ var mout = require('mout');
|
||||
var Resolver = require('./Resolver');
|
||||
var semver = require('../../util/semver');
|
||||
var createError = require('../../util/createError');
|
||||
var defaultConfig = require('../../config');
|
||||
|
||||
var hasGit;
|
||||
|
||||
@@ -22,13 +20,13 @@ try {
|
||||
hasGit = false;
|
||||
}
|
||||
|
||||
// Set template dir to the empty directory so that user templates are not run
|
||||
// This environment variable is not multiple config aware but it's not documented
|
||||
// anyway
|
||||
mkdirp.sync(defaultConfig.storage.empty);
|
||||
process.env.GIT_TEMPLATE_DIR = defaultConfig.storage.empty;
|
||||
|
||||
function GitResolver(decEndpoint, config, logger) {
|
||||
// Set template dir to the empty directory so that user templates are not run
|
||||
// This environment variable is not multiple config aware but it's not documented
|
||||
// anyway
|
||||
mkdirp.sync(config.storage.empty);
|
||||
process.env.GIT_TEMPLATE_DIR = config.storage.empty;
|
||||
|
||||
Resolver.call(this, decEndpoint, config, logger);
|
||||
|
||||
if (!hasGit) {
|
||||
@@ -41,7 +39,7 @@ mout.object.mixIn(GitResolver, Resolver);
|
||||
|
||||
// -----------------
|
||||
|
||||
GitResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
|
||||
GitResolver.prototype._hasNew = function (pkgMeta) {
|
||||
var oldResolution = pkgMeta._resolution || {};
|
||||
|
||||
return this._findResolution()
|
||||
@@ -128,10 +126,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] };
|
||||
}
|
||||
@@ -139,65 +143,58 @@ GitResolver.prototype._findResolution = function (target) {
|
||||
throw createError('No tag found that was able to satisfy ' + target, 'ENORESTARGET', {
|
||||
details: !versions.length ?
|
||||
'No versions found in ' + that._source :
|
||||
'Available versions: ' + versions.map(function (version) { return version.version; }).join(', ')
|
||||
'Available versions in ' + that._source + ': ' + versions.map(function (version) { return version.version; }).join(', ')
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
||||
};
|
||||
|
||||
GitResolver.prototype._cleanup = function () {
|
||||
var gitFolder = path.join(this._tempDir, '.git');
|
||||
|
||||
// Remove the .git folder
|
||||
// Note that on windows, we need to chmod to 0777 before due to a bug in git
|
||||
// See: https://github.com/isaacs/rimraf/issues/19
|
||||
if (process.platform === 'win32') {
|
||||
return Q.nfcall(chmodr, gitFolder, 0777)
|
||||
.then(function () {
|
||||
return Q.nfcall(rimraf, gitFolder);
|
||||
}, function (err) {
|
||||
// If .git does not exist, chmodr returns ENOENT
|
||||
// so, we ignore that error code
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Q.nfcall(rimraf, gitFolder);
|
||||
}
|
||||
return Q.nfcall(rimraf, gitFolder);
|
||||
};
|
||||
|
||||
GitResolver.prototype._savePkgMeta = function (meta) {
|
||||
@@ -222,8 +219,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 +296,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 && !mout.string.endsWith(match[2], '^{}')) {
|
||||
tag = match[2];
|
||||
tags[match[2]] = match[1];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('../../util/fs');
|
||||
var path = require('path');
|
||||
var Q = require('q');
|
||||
var tmp = require('tmp');
|
||||
var mkdirp = require('mkdirp');
|
||||
var rimraf = require('rimraf');
|
||||
var rimraf = require('../../util/rimraf');
|
||||
var readJson = require('../../util/readJson');
|
||||
var createError = require('../../util/createError');
|
||||
var removeIgnores = require('../../util/removeIgnores');
|
||||
var md5 = require('md5-hex');
|
||||
|
||||
tmp.setGracefulCleanup();
|
||||
|
||||
@@ -43,9 +44,8 @@ Resolver.prototype.getPkgMeta = function () {
|
||||
return this._pkgMeta;
|
||||
};
|
||||
|
||||
Resolver.prototype.hasNew = function (canonicalDir, pkgMeta) {
|
||||
Resolver.prototype.hasNew = function (pkgMeta) {
|
||||
var promise;
|
||||
var metaFile;
|
||||
var that = this;
|
||||
|
||||
// If already working, error out
|
||||
@@ -56,24 +56,7 @@ Resolver.prototype.hasNew = function (canonicalDir, pkgMeta) {
|
||||
this._working = true;
|
||||
|
||||
// Avoid reading the package meta if already given
|
||||
if (pkgMeta) {
|
||||
promise = this._hasNew(canonicalDir, pkgMeta);
|
||||
// Otherwise call _hasNew with both the package meta and the canonical dir
|
||||
} else {
|
||||
metaFile = path.join(canonicalDir, '.bower.json');
|
||||
|
||||
promise = readJson(metaFile)
|
||||
.spread(function (pkgMeta) {
|
||||
return that._hasNew(canonicalDir, pkgMeta);
|
||||
}, function (err) {
|
||||
that._logger.debug('read-json', 'Failed to read ' + metaFile, {
|
||||
filename: metaFile,
|
||||
error: err
|
||||
});
|
||||
|
||||
return true; // Simply resolve to true if there was an error reading the file
|
||||
});
|
||||
}
|
||||
promise = this._hasNew(pkgMeta);
|
||||
|
||||
return promise.fin(function () {
|
||||
that._working = false;
|
||||
@@ -114,6 +97,25 @@ Resolver.prototype.resolve = function () {
|
||||
});
|
||||
};
|
||||
|
||||
Resolver.prototype.isCacheable = function () {
|
||||
// Bypass cache for local dependencies
|
||||
if (this._source &&
|
||||
/^(?:file:[\/\\]{2}|[A-Z]:)?\.?\.?[\/\\]/.test(this._source)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't want to cache moving targets like branches
|
||||
if (this._pkgMeta &&
|
||||
this._pkgMeta._resolution &&
|
||||
this._pkgMeta._resolution.type === 'branch') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
// -----------------
|
||||
|
||||
// Abstract functions that must be implemented by concrete resolvers
|
||||
@@ -123,7 +125,7 @@ Resolver.prototype._resolve = function () {
|
||||
|
||||
// Abstract functions that can be re-implemented by concrete resolvers
|
||||
// as necessary
|
||||
Resolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
|
||||
Resolver.prototype._hasNew = function (pkgMeta) {
|
||||
return Q.resolve(true);
|
||||
};
|
||||
|
||||
@@ -143,14 +145,14 @@ Resolver.prototype._createTempDir = function () {
|
||||
return Q.nfcall(mkdirp, this._config.tmp)
|
||||
.then(function () {
|
||||
return Q.nfcall(tmp.dir, {
|
||||
template: path.join(this._config.tmp, this._name + '-' + process.pid + '-XXXXXX'),
|
||||
template: path.join(this._config.tmp, md5(this._name) + '-' + process.pid + '-XXXXXX'),
|
||||
mode: 0777 & ~process.umask(),
|
||||
unsafeCleanup: true
|
||||
});
|
||||
}.bind(this))
|
||||
.then(function (dir) {
|
||||
this._tempDir = dir;
|
||||
return dir;
|
||||
// nfcall may return multiple callback arguments as an array
|
||||
return this._tempDir = Array.isArray(dir) ? dir[0] : dir;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
@@ -201,26 +203,36 @@ Resolver.prototype._applyPkgMeta = function (meta) {
|
||||
}
|
||||
|
||||
// Otherwise remove them from the temp dir
|
||||
return removeIgnores(this._tempDir, meta.ignore)
|
||||
return removeIgnores(this._tempDir, meta)
|
||||
.then(function () {
|
||||
return meta;
|
||||
});
|
||||
};
|
||||
|
||||
Resolver.prototype._savePkgMeta = function (meta) {
|
||||
var that = this;
|
||||
var contents;
|
||||
|
||||
// Store original source & target
|
||||
meta._source = this._source;
|
||||
meta._target = this._target;
|
||||
|
||||
['main', 'ignore'].forEach(function (attr) {
|
||||
if (meta[attr]) return;
|
||||
|
||||
that._logger.log(
|
||||
'warn', 'invalid-meta',
|
||||
(meta.name || 'component') + ' is missing "' + attr + '" entry in bower.json'
|
||||
);
|
||||
});
|
||||
|
||||
// Stringify contents
|
||||
contents = JSON.stringify(meta, null, 2);
|
||||
|
||||
return Q.nfcall(fs.writeFile, path.join(this._tempDir, '.bower.json'), contents)
|
||||
.then(function () {
|
||||
return this._pkgMeta = meta;
|
||||
}.bind(this));
|
||||
return that._pkgMeta = meta;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Resolver;
|
||||
|
||||
403
lib/core/resolvers/SvnResolver.js
Normal file
403
lib/core/resolvers/SvnResolver.js
Normal file
@@ -0,0 +1,403 @@
|
||||
var util = require('util');
|
||||
var Q = require('q');
|
||||
var which = require('which');
|
||||
var LRU = require('lru-cache');
|
||||
var mout = require('mout');
|
||||
var Resolver = require('./Resolver');
|
||||
var semver = require('../../util/semver');
|
||||
var createError = require('../../util/createError');
|
||||
var 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 (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', '--non-interactive', this._source + '/trunk', '-r' + resolution.commit, this._tempDir]);
|
||||
} else if (resolution.type === 'branch' && resolution.branch === 'trunk') {
|
||||
promise = cmd('svn', ['export', '--force', '--non-interactive', this._source + '/trunk', this._tempDir]);
|
||||
} else if (resolution.type === 'branch') {
|
||||
promise = cmd('svn', ['export', '--force', '--non-interactive', this._source + '/branches/' + resolution.branch, this._tempDir]);
|
||||
} else {
|
||||
promise = cmd('svn', ['export', '--force', '--non-interactive', this._source + '/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 in ' + that._source + ': ' + 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 + '/tags', '--verbose', '--non-interactive'])
|
||||
.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', '--non-interactive'])
|
||||
.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;
|
||||
@@ -1,6 +1,6 @@
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('../../util/fs');
|
||||
var url = require('url');
|
||||
var request = require('request');
|
||||
var Q = require('q');
|
||||
@@ -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);
|
||||
@@ -41,7 +39,7 @@ UrlResolver.isTargetable = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
UrlResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
|
||||
UrlResolver.prototype._hasNew = function (pkgMeta) {
|
||||
var oldCacheHeaders = pkgMeta._cacheHeaders || {};
|
||||
var reqHeaders = {};
|
||||
|
||||
@@ -57,7 +55,7 @@ UrlResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
|
||||
|
||||
// Make an HEAD request to the source
|
||||
return Q.nfcall(request.head, this._source, {
|
||||
proxy: this._remote.protocol === 'https:' ? this._config.httpsProxy : this._config.proxy,
|
||||
ca: this._config.ca.default,
|
||||
strictSSL: this._config.strictSsl,
|
||||
timeout: this._config.timeout,
|
||||
headers: reqHeaders
|
||||
@@ -106,8 +104,19 @@ UrlResolver.prototype._resolve = function () {
|
||||
|
||||
// -----------------
|
||||
|
||||
UrlResolver.prototype._parseSourceURL = function (_url) {
|
||||
return url.parse(path.basename(_url)).pathname;
|
||||
};
|
||||
|
||||
UrlResolver.prototype._download = function () {
|
||||
var file = path.join(this._tempDir, path.basename(this._source));
|
||||
var fileName = this._parseSourceURL(this._source);
|
||||
|
||||
if (!fileName) {
|
||||
this._source = this._source.replace(/\/(?=\?|#)/, '');
|
||||
fileName = this._parseSourceURL(this._source);
|
||||
}
|
||||
|
||||
var file = path.join(this._tempDir, fileName);
|
||||
var reqHeaders = {};
|
||||
var that = this;
|
||||
|
||||
@@ -122,7 +131,7 @@ UrlResolver.prototype._download = function () {
|
||||
|
||||
// Download the file
|
||||
return download(this._source, file, {
|
||||
proxy: this._remote.protocol === 'https:' ? this._config.httpsProxy : this._config.proxy,
|
||||
ca: this._config.ca.default,
|
||||
strictSSL: this._config.strictSsl,
|
||||
timeout: this._config.timeout,
|
||||
headers: reqHeaders
|
||||
@@ -139,9 +148,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 +208,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)) {
|
||||
|
||||
@@ -2,6 +2,7 @@ module.exports = {
|
||||
GitFs: require('./GitFsResolver'),
|
||||
GitRemote: require('./GitRemoteResolver'),
|
||||
GitHub: require('./GitHubResolver'),
|
||||
Svn: require('./SvnResolver'),
|
||||
Fs: require('./FsResolver'),
|
||||
Url: require('./UrlResolver')
|
||||
};
|
||||
|
||||
322
lib/core/resolvers/pluginResolverFactory.js
Normal file
322
lib/core/resolvers/pluginResolverFactory.js
Normal file
@@ -0,0 +1,322 @@
|
||||
var Q = require('q');
|
||||
var path = require('path');
|
||||
var fs = require('../../util/fs');
|
||||
var object = require('mout/object');
|
||||
|
||||
var semver = require('../../util/semver');
|
||||
var createError = require('../../util/createError');
|
||||
var readJson = require('../../util/readJson');
|
||||
var removeIgnores = require('../../util/removeIgnores');
|
||||
|
||||
function pluginResolverFactory(resolverFactory, bower) {
|
||||
bower = bower || {};
|
||||
|
||||
if (typeof resolverFactory !== 'function') {
|
||||
throw createError('Resolver has "' + typeof resolverFactory + '" type instead of "function" type.', 'ERESOLERAPI');
|
||||
}
|
||||
|
||||
var resolver = resolverFactory(bower);
|
||||
|
||||
function maxSatisfyingVersion(versions, target) {
|
||||
var versionsArr, index;
|
||||
|
||||
versionsArr = versions.map(function (obj) { return obj.version; });
|
||||
|
||||
// Find a satisfying version, enabling strict match so that pre-releases
|
||||
// have lower priority over normal ones when target is *
|
||||
index = semver.maxSatisfyingIndex(versionsArr, target, true);
|
||||
|
||||
if (index !== -1) {
|
||||
return versions[index];
|
||||
}
|
||||
}
|
||||
|
||||
function PluginResolver(decEndpoint) {
|
||||
this._decEndpoint = decEndpoint;
|
||||
}
|
||||
|
||||
// @private
|
||||
PluginResolver.prototype.getEndpoint = function () {
|
||||
return object.merge(this._decEndpoint, {
|
||||
name: this.getName(),
|
||||
source: this.getSource(),
|
||||
target: this.getTarget()
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.prototype.getSource = function () {
|
||||
return this._decEndpoint.source;
|
||||
};
|
||||
|
||||
PluginResolver.prototype.getTarget = function () {
|
||||
return this._decEndpoint.target || '*';
|
||||
};
|
||||
|
||||
PluginResolver.prototype.getName = function() {
|
||||
if (!this._decEndpoint.name && typeof resolver.getName === 'function') {
|
||||
return resolver.getName.call(resolver, this.getSource());
|
||||
} else if (!this._decEndpoint.name) {
|
||||
return path.basename(this.getSource());
|
||||
} else {
|
||||
return this._decEndpoint.name;
|
||||
}
|
||||
};
|
||||
|
||||
PluginResolver.prototype.getPkgMeta = function () {
|
||||
return this._pkgMeta;
|
||||
};
|
||||
|
||||
// -----------------
|
||||
|
||||
// Plugin Resolver is always considered potentially cacheable
|
||||
// The "resolve" method decides whether to use cached or fetch new version.
|
||||
PluginResolver.prototype.isCacheable = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
// Not only it's always potentially cacheable, but also always potenially new.
|
||||
// The "resolve" handles logic of re-downloading target if needed.
|
||||
PluginResolver.prototype.hasNew = function (pkgMeta) {
|
||||
if (this.hasNewPromise) {
|
||||
return this.hasNewPromise;
|
||||
}
|
||||
|
||||
this._pkgMeta = pkgMeta;
|
||||
|
||||
return this.hasNewPromise = this.resolve().then(function (result) {
|
||||
return result !== undefined;
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.prototype.resolve = function () {
|
||||
if (this.resolvePromise) {
|
||||
return this.resolvePromise;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
|
||||
return this.resolvePromise = Q.fcall(function() {
|
||||
var target = that.getTarget();
|
||||
|
||||
// It means that we can accept ranges as targets
|
||||
if(that.constructor.isTargetable()) {
|
||||
that._release = target;
|
||||
|
||||
if (semver.validRange(target)) {
|
||||
return Q.fcall(resolver.releases.bind(resolver), that.getSource())
|
||||
.then(function (result) {
|
||||
if (!result) {
|
||||
throw createError('Resolver did not provide releases of package.');
|
||||
}
|
||||
|
||||
var releases = this._releases = result;
|
||||
|
||||
var versions = releases.filter(function (target) {
|
||||
return semver.clean(target.version);
|
||||
});
|
||||
|
||||
var maxRelease = maxSatisfyingVersion(versions, target);
|
||||
|
||||
if (maxRelease) {
|
||||
that._version = maxRelease.version;
|
||||
that._release = that._decEndpoint.target = maxRelease.target;
|
||||
} else {
|
||||
throw createError('No version found that was able to satisfy ' + target, 'ENORESTARGET', {
|
||||
details: !versions.length ?
|
||||
'No versions found in ' + that.getSource() :
|
||||
'Available versions: ' + versions.map(function (version) { return version.version; }).join(', ')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (semver.validRange(target) && target !== '*') {
|
||||
return Q.reject(createError('Resolver does not accept version ranges (' + target + ')'));
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
|
||||
// We pass old _resolution (if hasNew has been called before contents).
|
||||
// So resolver can decide wheter use cached version of contents new one.
|
||||
if (typeof resolver.fetch !== 'function') {
|
||||
throw createError('Resolver does not implement the "fetch" method.');
|
||||
}
|
||||
|
||||
var cached = {};
|
||||
|
||||
if (that._releases) {
|
||||
cached.releases = that._releases;
|
||||
}
|
||||
|
||||
if (that._pkgMeta) {
|
||||
cached.endpoint = {
|
||||
name: that._pkgMeta.name,
|
||||
source: that._pkgMeta._source,
|
||||
target: that._pkgMeta._target
|
||||
};
|
||||
|
||||
cached.release = that._pkgMeta._release;
|
||||
|
||||
cached.version = that._pkgMeta.version;
|
||||
|
||||
cached.resolution = that._pkgMeta._resolution || {};
|
||||
}
|
||||
|
||||
return Q.fcall(resolver.fetch.bind(resolver), that.getEndpoint(), cached);
|
||||
})
|
||||
.then(function (result) {
|
||||
// Empty result means to re-use existing resolution
|
||||
if (!result) {
|
||||
return;
|
||||
} else {
|
||||
if (!result.tempPath) {
|
||||
throw createError('Resolver did not provide path to extracted contents of package.');
|
||||
}
|
||||
|
||||
that._tempDir = result.tempPath;
|
||||
|
||||
return that._readJson(that._tempDir).then(function (meta) {
|
||||
return that._applyPkgMeta(meta, result)
|
||||
.then(that._savePkgMeta.bind(that, meta, result))
|
||||
.then(function () {
|
||||
return that._tempDir;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.prototype._readJson = function (dir) {
|
||||
var that = this;
|
||||
|
||||
return readJson(dir, {
|
||||
assume: { name: that.getName() }
|
||||
})
|
||||
.spread(function (json, deprecated) {
|
||||
if (deprecated) {
|
||||
bower.logger.warn('deprecated', 'Package ' + that.getName() + ' is using the deprecated ' + deprecated);
|
||||
}
|
||||
|
||||
return json;
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.prototype._applyPkgMeta = function (meta, result) {
|
||||
// Check if name defined in the json is different
|
||||
// If so and if the name was "guessed", assume the json name
|
||||
if (meta.name !== this._name) {
|
||||
this._name = meta.name;
|
||||
}
|
||||
|
||||
// Handle ignore property, deleting all files from the temporary directory
|
||||
// If no ignores were specified, simply resolve
|
||||
if (result.removeIgnores === false || !meta.ignore || !meta.ignore.length) {
|
||||
return Q.resolve(meta);
|
||||
}
|
||||
|
||||
// Otherwise remove them from the temp dir
|
||||
return removeIgnores(this._tempDir, meta).then(function () {
|
||||
return meta;
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.prototype._savePkgMeta = function (meta, result) {
|
||||
var that = this;
|
||||
|
||||
meta._source = that.getSource();
|
||||
meta._target = that.getTarget();
|
||||
|
||||
if (result.resolution) {
|
||||
meta._resolution = result.resolution;
|
||||
}
|
||||
|
||||
if (that._release) {
|
||||
meta._release = that._release;
|
||||
}
|
||||
|
||||
if (that._version) {
|
||||
meta.version = that._version;
|
||||
} else {
|
||||
delete meta.version;
|
||||
}
|
||||
|
||||
// Stringify contents
|
||||
var contents = JSON.stringify(meta, null, 2);
|
||||
|
||||
return Q.nfcall(fs.writeFile, path.join(this._tempDir, '.bower.json'), contents)
|
||||
.then(function () {
|
||||
return that._pkgMeta = meta;
|
||||
});
|
||||
};
|
||||
|
||||
// It is used only by "bower info". It returns all semver versions.
|
||||
PluginResolver.versions = function (source) {
|
||||
return Q.fcall(resolver.releases.bind(resolver), source).then(function (result) {
|
||||
if (!result) {
|
||||
throw createError('Resolver did not provide releases of package.');
|
||||
}
|
||||
|
||||
var releases = this._releases = result;
|
||||
|
||||
var versions = releases.map(function (version) {
|
||||
return semver.clean(version.version);
|
||||
});
|
||||
|
||||
versions = versions.filter(function (version) {
|
||||
return version;
|
||||
});
|
||||
|
||||
versions.sort(function (a, b) {
|
||||
return semver.rcompare(a, b);
|
||||
});
|
||||
|
||||
return versions;
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.isTargetable = function() {
|
||||
// If resolver doesn't define versions function, it's not targetable..
|
||||
return typeof resolver.releases === 'function';
|
||||
};
|
||||
|
||||
PluginResolver.clearRuntimeCache = function () {
|
||||
resolver = resolverFactory(bower);
|
||||
};
|
||||
|
||||
PluginResolver.match = function (source) {
|
||||
if (typeof resolver.match !== 'function') {
|
||||
throw createError('Resolver is missing "match" method.', 'ERESOLVERAPI');
|
||||
}
|
||||
|
||||
var match = resolver.match.bind(resolver);
|
||||
|
||||
return Q.fcall(match, source).then(function (result) {
|
||||
if (typeof result !== 'boolean') {
|
||||
throw createError('Resolver\'s "match" method should return a boolean', 'ERESOLVERAPI');
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
PluginResolver.locate = function (source) {
|
||||
if (typeof resolver.locate !== 'function') {
|
||||
return source;
|
||||
}
|
||||
|
||||
return Q.fcall(resolver.locate.bind(resolver), source).then(function (result) {
|
||||
if (typeof result !== 'string') {
|
||||
throw createError('Resolver\'s "locate" method should return a string', 'ERESOLVERAPI');
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
return PluginResolver;
|
||||
}
|
||||
|
||||
|
||||
module.exports = pluginResolverFactory;
|
||||
|
||||
96
lib/core/scripts.js
Normal file
96
lib/core/scripts.js
Normal 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
|
||||
};
|
||||
31
lib/index.js
31
lib/index.js
@@ -1,40 +1,19 @@
|
||||
var abbrev = require('abbrev');
|
||||
var mout = require('mout');
|
||||
var commands = require('./commands');
|
||||
var PackageRepository = require('./core/PackageRepository');
|
||||
|
||||
var abbreviations = abbrev(expandNames(commands));
|
||||
abbreviations.i = 'install';
|
||||
abbreviations.rm = 'uninstall';
|
||||
abbreviations.ls = 'list';
|
||||
|
||||
function expandNames(obj, prefix, stack) {
|
||||
prefix = prefix || '';
|
||||
stack = stack || [];
|
||||
|
||||
mout.object.forOwn(obj, function (value, name) {
|
||||
name = prefix + name;
|
||||
|
||||
stack.push(name);
|
||||
|
||||
if (typeof value === 'object' && !value.line) {
|
||||
expandNames(value, name + ' ', stack);
|
||||
}
|
||||
});
|
||||
|
||||
return stack;
|
||||
}
|
||||
var pkg = require('../package.json');
|
||||
var abbreviations = require('./abbreviations')(commands);
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
@@ -31,10 +31,10 @@ JsonRenderer.prototype.error = function (err) {
|
||||
err.message = message;
|
||||
|
||||
// Stack
|
||||
/*jshint camelcase:false*/
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
stack = err.fstream_stack || err.stack || 'N/A';
|
||||
err.stacktrace = (Array.isArray(stack) ? stack.join('\n') : stack);
|
||||
/*jshint camelcase:true*/
|
||||
// jscs:enable requireCamelCaseOrUpperCaseIdentifiers
|
||||
|
||||
this.log(err);
|
||||
this.end();
|
||||
@@ -88,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');
|
||||
@@ -105,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 () {
|
||||
@@ -112,8 +121,6 @@ JsonRenderer.prototype.prompt = function (prompts) {
|
||||
});
|
||||
};
|
||||
|
||||
JsonRenderer.prototype.updateAvailable = function () {};
|
||||
|
||||
// -------------------------
|
||||
|
||||
JsonRenderer.prototype._stringify = function (log) {
|
||||
|
||||
@@ -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) {
|
||||
@@ -63,15 +71,15 @@ StandardRenderer.prototype.error = function (err) {
|
||||
// Print trace if verbose, the error has no code
|
||||
// or if the error is a node error
|
||||
if (this._config.verbose || !err.code || err.errno) {
|
||||
/*jshint camelcase:false*/
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
stack = err.fstream_stack || err.stack || 'N/A';
|
||||
str = chalk.yellow('\nStack trace:\n');
|
||||
str += (Array.isArray(stack) ? stack.join('\n') : stack) + '\n';
|
||||
str += chalk.yellow('\nConsole trace:\n');
|
||||
/*jshint camelcase:true*/
|
||||
// jscs:enable requireCamelCaseOrUpperCaseIdentifiers
|
||||
|
||||
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'
|
||||
|
||||
127
lib/util/analytics.js
Normal file
127
lib/util/analytics.js
Normal file
@@ -0,0 +1,127 @@
|
||||
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');
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
insight = new Insight({
|
||||
trackingCode: 'UA-00000000-0',
|
||||
pkg: {
|
||||
name: 'bower-test',
|
||||
version: '1.0.0'
|
||||
}
|
||||
});
|
||||
|
||||
insight.config.clear();
|
||||
} else {
|
||||
insight = new Insight({
|
||||
trackingCode: 'UA-43531210-1',
|
||||
pkg: require('../../package.json')
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes the application-wide insight singleton and asks for the
|
||||
// permission on the CLI during the first run.
|
||||
//
|
||||
// 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));
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var fstream = require('fstream');
|
||||
var fstreamIgnore = require('fstream-ignore');
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('./fs');
|
||||
var Q = require('q');
|
||||
|
||||
function copy(reader, writer) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('./fs');
|
||||
var path = require('path');
|
||||
var Q = require('q');
|
||||
var mkdirp = require('mkdirp');
|
||||
@@ -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'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,19 +3,20 @@ var request = require('request');
|
||||
var Q = require('q');
|
||||
var mout = require('mout');
|
||||
var retry = require('retry');
|
||||
var fs = require('graceful-fs');
|
||||
var createError = require('./createError');
|
||||
var createWriteStream = require('fs-write-stream-atomic');
|
||||
var destroy = require('destroy');
|
||||
|
||||
var errorCodes = [
|
||||
'EADDRINFO',
|
||||
'ETIMEDOUT',
|
||||
'ECONNRESET',
|
||||
'ESOCKETTIMEDOUT'
|
||||
'ESOCKETTIMEDOUT',
|
||||
'ENOTFOUND'
|
||||
];
|
||||
|
||||
function download(url, file, options) {
|
||||
var operation;
|
||||
var response;
|
||||
var deferred = Q.defer();
|
||||
var progressDelay = 8000;
|
||||
|
||||
@@ -24,31 +25,23 @@ function download(url, file, options) {
|
||||
factor: 2,
|
||||
minTimeout: 1000,
|
||||
maxTimeout: 35000,
|
||||
randomize: true
|
||||
randomize: true,
|
||||
progressDelay: progressDelay
|
||||
}, options || {});
|
||||
|
||||
// Retry on network errors
|
||||
operation = retry.operation(options);
|
||||
|
||||
operation.attempt(function () {
|
||||
var req;
|
||||
var writeStream;
|
||||
|
||||
req = progress(request(url, options), {
|
||||
delay: progressDelay
|
||||
Q.fcall(fetch, url, file, options)
|
||||
.then(function(response) {
|
||||
deferred.resolve(response);
|
||||
})
|
||||
.on('response', function (res) {
|
||||
var status = res.statusCode;
|
||||
|
||||
if (status < 200 || status >= 300) {
|
||||
return deferred.reject(createError('Status code of ' + status, 'EHTTP'));
|
||||
}
|
||||
|
||||
response = res;
|
||||
.progress(function (status) {
|
||||
deferred.notify(status);
|
||||
})
|
||||
.on('progress', function (state) {
|
||||
deferred.notify(state);
|
||||
})
|
||||
.on('error', function (error) {
|
||||
.fail(function (error) {
|
||||
// Save timeout before retrying to report
|
||||
var timeout = operation._timeouts[0];
|
||||
|
||||
// Reject if error is not a network error
|
||||
@@ -59,36 +52,91 @@ function download(url, file, options) {
|
||||
// Next attempt will start reporting download progress immediately
|
||||
progressDelay = 0;
|
||||
|
||||
// Check if there are more retries
|
||||
// This will schedule next retry or return false
|
||||
if (operation.retry(error)) {
|
||||
// Ensure that there are no more events from this request
|
||||
req.removeAllListeners();
|
||||
req.on('error', function () {});
|
||||
// Ensure that there are no more events from the write stream
|
||||
writeStream.removeAllListeners();
|
||||
writeStream.on('error', function () {});
|
||||
|
||||
return deferred.notify({
|
||||
deferred.notify({
|
||||
retry: true,
|
||||
delay: timeout,
|
||||
error: error
|
||||
});
|
||||
} else {
|
||||
deferred.reject(error);
|
||||
}
|
||||
|
||||
// No more retries, reject!
|
||||
deferred.reject(error);
|
||||
});
|
||||
|
||||
// Pipe read stream to write stream
|
||||
writeStream = req
|
||||
.pipe(fs.createWriteStream(file))
|
||||
.on('error', deferred.reject)
|
||||
.on('close', function () {
|
||||
deferred.resolve(response);
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function fetch(url, file, options) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
var contentLength;
|
||||
var bytesDownloaded = 0;
|
||||
|
||||
var reject = function (error) {
|
||||
deferred.reject(error);
|
||||
};
|
||||
|
||||
var req = progress(request(url, options), {
|
||||
delay: options.progressDelay
|
||||
})
|
||||
.on('response', function (response) {
|
||||
contentLength = Number(response.headers['content-length']);
|
||||
|
||||
var status = response.statusCode;
|
||||
|
||||
if (status < 200 || status >= 300) {
|
||||
return deferred.reject(createError('Status code of ' + status, 'EHTTP'));
|
||||
}
|
||||
|
||||
var writeStream = createWriteStream(file);
|
||||
var errored = false;
|
||||
|
||||
// Change error listener so it cleans up writeStream before exiting
|
||||
req.removeListener('error', reject);
|
||||
req.on('error', function (error) {
|
||||
errored = true;
|
||||
destroy(req);
|
||||
destroy(writeStream);
|
||||
|
||||
// Wait for writeStream to cleanup after itself...
|
||||
// TODO: Maybe there's a better way?
|
||||
setTimeout(function () {
|
||||
deferred.reject(error);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
writeStream.on('finish', function () {
|
||||
if (!errored) {
|
||||
destroy(req);
|
||||
deferred.resolve(response);
|
||||
}
|
||||
});
|
||||
|
||||
req.pipe(writeStream);
|
||||
})
|
||||
.on('data', function (data) {
|
||||
bytesDownloaded += data.length;
|
||||
})
|
||||
.on('progress', function (state) {
|
||||
deferred.notify(state);
|
||||
})
|
||||
.on('error', reject)
|
||||
.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'
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
module.exports = download;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
var path = require('path');
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('./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');
|
||||
var createError = require('./createError');
|
||||
var createWriteStream = require('fs-write-stream-atomic');
|
||||
var destroy = require('destroy');
|
||||
|
||||
// This forces the default chunk size to something small in an attempt
|
||||
// to avoid issue #314
|
||||
@@ -22,6 +24,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 +36,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;
|
||||
}
|
||||
@@ -48,15 +51,24 @@ function extractZip(archive, dst) {
|
||||
function extractTar(archive, dst) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
fs.createReadStream(archive)
|
||||
.on('error', deferred.reject)
|
||||
.pipe(tar.Extract({
|
||||
path: dst,
|
||||
follow: false, // Do not follow symlinks (#699)
|
||||
filter: filterSymlinks // Filter symlink files
|
||||
var stream = fs.createReadStream(archive);
|
||||
|
||||
var reject = function (error) {
|
||||
destroy(stream);
|
||||
deferred.reject(error);
|
||||
};
|
||||
|
||||
stream.on('error', reject)
|
||||
.pipe(tar.extract(dst, {
|
||||
ignore: isSymlink, // Filter symlink files
|
||||
dmode: 0555, // Ensure dirs are readable
|
||||
fmode: 0444 // Ensure files are readable
|
||||
}))
|
||||
.on('error', deferred.reject)
|
||||
.on('close', deferred.resolve.bind(deferred, dst));
|
||||
.on('error', reject)
|
||||
.on('finish', function (result) {
|
||||
destroy(stream);
|
||||
deferred.resolve(dst);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
@@ -64,17 +76,26 @@ function extractTar(archive, dst) {
|
||||
function extractTarGz(archive, dst) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
fs.createReadStream(archive)
|
||||
.on('error', deferred.reject)
|
||||
var stream = fs.createReadStream(archive);
|
||||
|
||||
var reject = function (error) {
|
||||
destroy(stream);
|
||||
deferred.reject(error);
|
||||
};
|
||||
|
||||
stream.on('error', reject)
|
||||
.pipe(zlib.createGunzip())
|
||||
.on('error', deferred.reject)
|
||||
.pipe(tar.Extract({
|
||||
path: dst,
|
||||
follow: false, // Do not follow symlinks (#699)
|
||||
filter: filterSymlinks // Filter symlink files
|
||||
.on('error', reject)
|
||||
.pipe(tar.extract(dst, {
|
||||
ignore: isSymlink, // Filter symlink files
|
||||
dmode: 0555, // Ensure dirs are readable
|
||||
fmode: 0444 // Ensure files are readable
|
||||
}))
|
||||
.on('error', deferred.reject)
|
||||
.on('close', deferred.resolve.bind(deferred, dst));
|
||||
.on('error', reject)
|
||||
.on('finish', function (result) {
|
||||
destroy(stream);
|
||||
deferred.resolve(dst);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
@@ -82,17 +103,29 @@ function extractTarGz(archive, dst) {
|
||||
function extractGz(archive, dst) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
fs.createReadStream(archive)
|
||||
.on('error', deferred.reject)
|
||||
var stream = fs.createReadStream(archive);
|
||||
|
||||
var reject = function (error) {
|
||||
destroy(stream);
|
||||
deferred.reject(error);
|
||||
};
|
||||
stream.on('error', reject)
|
||||
.pipe(zlib.createGunzip())
|
||||
.on('error', deferred.reject)
|
||||
.pipe(fs.createWriteStream(dst))
|
||||
.on('error', deferred.reject)
|
||||
.on('close', deferred.resolve.bind(deferred, dst));
|
||||
.on('error', reject)
|
||||
.pipe(createWriteStream(dst))
|
||||
.on('error', reject)
|
||||
.on('finish', function (result) {
|
||||
destroy(stream);
|
||||
deferred.resolve(dst);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function isSymlink(entry) {
|
||||
return entry.type === 'SymbolicLink';
|
||||
}
|
||||
|
||||
function filterSymlinks(entry) {
|
||||
return entry.type !== 'SymbolicLink';
|
||||
}
|
||||
@@ -155,15 +188,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:
|
||||
|
||||
34
lib/util/fs.js
Normal file
34
lib/util/fs.js
Normal file
@@ -0,0 +1,34 @@
|
||||
var fs = require('graceful-fs');
|
||||
|
||||
var readdir = fs.readdir.bind(fs);
|
||||
var readdirSync = fs.readdirSync.bind(fs);
|
||||
|
||||
module.exports = fs;
|
||||
|
||||
module.exports.readdir = function (dir, callback) {
|
||||
fs.stat(dir, function (err, stats) {
|
||||
if (err) return callback(err);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return readdir(dir, callback);
|
||||
} else {
|
||||
var error = new Error('ENOTDIR, not a directory \'' + dir + '\'');
|
||||
error.code = 'ENOTDIR';
|
||||
error.path = dir;
|
||||
error.errono = -20;
|
||||
return callback(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.readdirSync = function (dir) {
|
||||
var stats = fs.statSync(dir);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return readdirSync(dir);
|
||||
} else {
|
||||
var error = new Error();
|
||||
error.code = 'ENOTDIR';
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
var crypto = require('crypto');
|
||||
|
||||
function md5(contents) {
|
||||
return crypto.createHash('md5').update(contents).digest('hex');
|
||||
}
|
||||
|
||||
module.exports = md5;
|
||||
@@ -1,43 +1,58 @@
|
||||
var util = require('util');
|
||||
var rimraf = require('rimraf');
|
||||
var IgnoreReader = require('fstream-ignore');
|
||||
var path = require('path');
|
||||
var rimraf = require('../util/rimraf');
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
46
lib/util/rimraf.js
Normal file
46
lib/util/rimraf.js
Normal file
@@ -0,0 +1,46 @@
|
||||
var rimraf = require('rimraf');
|
||||
var chmodr = require('chmodr');
|
||||
var fs = require('./fs');
|
||||
|
||||
module.exports = function (dir, callback) {
|
||||
var checkAndRetry = function (e) {
|
||||
fs.lstat(dir, function (err, stats) {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') return callback();
|
||||
return callback(e);
|
||||
}
|
||||
|
||||
chmodr(dir, 0777, function (err) {
|
||||
if (err) return callback(e);
|
||||
rimraf(dir, callback);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
checkAndRetry();
|
||||
} else {
|
||||
rimraf(dir, checkAndRetry);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.sync = function (dir) {
|
||||
var checkAndRetry = function () {
|
||||
try {
|
||||
fs.lstatSync(dir);
|
||||
chmodr.sync(dir, 0777);
|
||||
return rimraf.sync(dir);
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') return;
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
return rimraf.sync(dir);
|
||||
} catch (e) {
|
||||
return checkAndRetry();
|
||||
} finally {
|
||||
return checkAndRetry();
|
||||
}
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
/*jshint multistr:true*/
|
||||
|
||||
var sudoBlock = require('sudo-block');
|
||||
// jscs:disable disallowMultipleLineStrings
|
||||
'use strict';
|
||||
var isRoot = require('is-root');
|
||||
var createError = require('./createError');
|
||||
var cli = require('./cli');
|
||||
|
||||
var renderer;
|
||||
|
||||
@@ -10,7 +10,7 @@ function rootCheck(options, config) {
|
||||
var errorMsg;
|
||||
|
||||
// Allow running the command as root
|
||||
if (options.allowRoot) {
|
||||
if (options.allowRoot || config.allowRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,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);
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var path = require('path');
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('./fs');
|
||||
var Handlebars = require('handlebars');
|
||||
var mout = require('mout');
|
||||
var helpers = require('../../templates/helpers');
|
||||
|
||||
3
lib/util/userAgent.js
Normal file
3
lib/util/userAgent.js
Normal file
@@ -0,0 +1,3 @@
|
||||
var pkg = require('../../package.json');
|
||||
|
||||
module.exports = 'node/' + process.version + ' ' + process.platform + ' ' + process.arch + ' ' + ';Bower ' + pkg.version;
|
||||
@@ -1,17 +1,17 @@
|
||||
var Q = require('q');
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('./fs');
|
||||
|
||||
function validLink(file) {
|
||||
// Ensures that a file is a symlink that points
|
||||
// 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];
|
||||
});
|
||||
})
|
||||
|
||||
189
package.json
189
package.json
@@ -1,78 +1,141 @@
|
||||
{
|
||||
"name": "bower",
|
||||
"version": "1.2.2",
|
||||
"description": "The browser package manager.",
|
||||
"version": "1.6.8",
|
||||
"description": "The browser package manager",
|
||||
"author": "Twitter",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/bower/bower/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/bower/bower.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": "bower/bower",
|
||||
"main": "lib",
|
||||
"homepage": "http://bower.io",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"keywords": [
|
||||
"bower"
|
||||
],
|
||||
"dependencies": {
|
||||
"abbrev": "~1.0.4",
|
||||
"archy": "0.0.2",
|
||||
"bower-config": "~0.4.3",
|
||||
"bower-endpoint-parser": "~0.2.0",
|
||||
"bower-json": "~0.4.0",
|
||||
"bower-logger": "~0.2.1",
|
||||
"bower-registry-client": "~0.1.4",
|
||||
"cardinal": "~0.4.0",
|
||||
"chalk": "~0.2.0",
|
||||
"chmodr": "~0.1.0",
|
||||
"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.27.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": "^1.2.3",
|
||||
"bower-endpoint-parser": "^0.2.2",
|
||||
"bower-json": "^0.4.0",
|
||||
"bower-logger": "^0.2.2",
|
||||
"bower-registry-client": "^1.0.0",
|
||||
"cardinal": "0.4.4",
|
||||
"chalk": "^1.0.0",
|
||||
"chmodr": "^1.0.2",
|
||||
"configstore": "^0.3.2",
|
||||
"decompress-zip": "^0.1.0",
|
||||
"destroy": "^1.0.3",
|
||||
"fs-write-stream-atomic": "^1.0.4",
|
||||
"fstream": "^1.0.3",
|
||||
"fstream-ignore": "^1.0.2",
|
||||
"github": "^0.2.3",
|
||||
"glob": "^4.3.2",
|
||||
"graceful-fs": "^3.0.5",
|
||||
"handlebars": "^2.0.0",
|
||||
"inquirer": "0.10.0",
|
||||
"insight": "^0.7.0",
|
||||
"is-root": "^1.0.0",
|
||||
"junk": "^1.0.0",
|
||||
"lockfile": "^1.0.0",
|
||||
"lru-cache": "^2.5.0",
|
||||
"md5-hex": "^1.0.2",
|
||||
"mkdirp": "0.5.0",
|
||||
"mout": "^0.11.0",
|
||||
"nopt": "^3.0.1",
|
||||
"opn": "^1.0.1",
|
||||
"p-throttler": "0.1.1",
|
||||
"promptly": "0.2.0",
|
||||
"q": "^1.1.2",
|
||||
"request": "2.53.0",
|
||||
"request-progress": "0.3.1",
|
||||
"retry": "0.6.1",
|
||||
"rimraf": "^2.2.8",
|
||||
"semver": "^2.3.0",
|
||||
"shell-quote": "^1.4.2",
|
||||
"stringify-object": "^1.0.0",
|
||||
"tar-fs": "^1.4.1",
|
||||
"tmp": "0.0.24",
|
||||
"update-notifier": "^0.3.0",
|
||||
"user-home": "^1.1.0",
|
||||
"which": "^1.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"expect.js": "~0.2.0",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-simple-mocha": "~0.4.0",
|
||||
"grunt-contrib-watch": "~0.5.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.5.0"
|
||||
"chai": "^1.10.0",
|
||||
"coveralls": "^2.11.2",
|
||||
"expect.js": "^0.3.1",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-cli": "^0.1.13",
|
||||
"grunt-contrib-jshint": "^0.10.0",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-exec": "^0.4.6",
|
||||
"grunt-jscs": "^2.3.0",
|
||||
"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": "^3.1.0",
|
||||
"node-uuid": "^1.4.2",
|
||||
"proxyquire": "^1.3.0",
|
||||
"spawn-sync": "1.0.13"
|
||||
},
|
||||
"bundledDependencies": [
|
||||
"abbrev",
|
||||
"archy",
|
||||
"bower-config",
|
||||
"bower-endpoint-parser",
|
||||
"bower-json",
|
||||
"bower-logger",
|
||||
"bower-registry-client",
|
||||
"cardinal",
|
||||
"chalk",
|
||||
"chmodr",
|
||||
"configstore",
|
||||
"decompress-zip",
|
||||
"destroy",
|
||||
"fs-write-stream-atomic",
|
||||
"fstream",
|
||||
"fstream-ignore",
|
||||
"github",
|
||||
"glob",
|
||||
"graceful-fs",
|
||||
"handlebars",
|
||||
"inquirer",
|
||||
"insight",
|
||||
"is-root",
|
||||
"junk",
|
||||
"lockfile",
|
||||
"lru-cache",
|
||||
"md5-hex",
|
||||
"mkdirp",
|
||||
"mout",
|
||||
"nopt",
|
||||
"opn",
|
||||
"p-throttler",
|
||||
"promptly",
|
||||
"q",
|
||||
"request",
|
||||
"request-progress",
|
||||
"retry",
|
||||
"rimraf",
|
||||
"semver",
|
||||
"shell-quote",
|
||||
"stringify-object",
|
||||
"tar-fs",
|
||||
"tmp",
|
||||
"update-notifier",
|
||||
"user-home",
|
||||
"which"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
},
|
||||
"bin": {
|
||||
"bower": "bin/bower"
|
||||
},
|
||||
"preferGlobal": true
|
||||
"bin": "bin/bower",
|
||||
"files": [
|
||||
"bin",
|
||||
"lib",
|
||||
"templates"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"command": "cache list",
|
||||
"command": "cache clean",
|
||||
"description": "Cleans cached packages.",
|
||||
"usage": [
|
||||
"cache clean [<options>]",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"command": "cache clean",
|
||||
"command": "cache list",
|
||||
"description": "Lists cached packages.",
|
||||
"usage": [
|
||||
"cache list [<options>]",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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>]"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"command": "list",
|
||||
"description": "List local packages.",
|
||||
"description": "List local packages - and possible updates.",
|
||||
"usage": [
|
||||
"list [<options>]"
|
||||
],
|
||||
|
||||
19
templates/json/help-login.json
Normal file
19
templates/json/help-login.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"command": "login",
|
||||
"description": "Authenticate with GitHub and store credentials to be used later.",
|
||||
"usage": [
|
||||
"login [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
},
|
||||
{
|
||||
"shorthand": "-t",
|
||||
"flag": "--token",
|
||||
"description": "Pass an existing GitHub auth token rather than prompting for username and password."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
14
templates/json/help-unregister.json
Normal file
14
templates/json/help-unregister.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"command": "unregister",
|
||||
"description": "Unregisters a package.",
|
||||
"usage": [
|
||||
"unregister <name> [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
14
templates/json/help-version.json
Normal file
14
templates/json/help-version.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -10,13 +10,16 @@
|
||||
"init": "Interactively create a bower.json file",
|
||||
"install": "Install a package locally",
|
||||
"link": "Symlink a package folder",
|
||||
"list": "List local packages",
|
||||
"list": "List local packages - and possible updates",
|
||||
"login": "Authenticate with GitHub and store credentials",
|
||||
"lookup": "Look up a package URL by name",
|
||||
"prune": "Removes local extraneous packages",
|
||||
"register": "Register a package",
|
||||
"search": "Search for a package by name",
|
||||
"update": "Update a local package",
|
||||
"uninstall": "Remove a local package"
|
||||
"uninstall": "Remove a local package",
|
||||
"unregister": "Remove a package from the registry",
|
||||
"version": "Bump a package version"
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
@@ -57,6 +60,15 @@
|
||||
{
|
||||
"flag": "--allow-root",
|
||||
"description": "Allows running commands as root"
|
||||
},
|
||||
{
|
||||
"shorthand": "-v",
|
||||
"flag": "--version",
|
||||
"description": "Output Bower version"
|
||||
},
|
||||
{
|
||||
"flag": "--no-color",
|
||||
"description": "Disable colors"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -14,3 +14,4 @@ Commands:
|
||||
{{#rpad length="23"}}{{@key}}{{/rpad}} {{.}}
|
||||
{{/each}}
|
||||
{{/condense}}
|
||||
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
Package not found.
|
||||
{{/if}}
|
||||
{{/condense}}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
{{#cyan}}{{{name}}}{{/cyan}} {{{url}}}
|
||||
{{/.}}
|
||||
{{/condense}}
|
||||
|
||||
{{else}}No results.
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('../../../lib/util/fs');
|
||||
var path = require('path');
|
||||
var Logger = require('bower-logger');
|
||||
var Resolver = require('../../../lib/core/resolvers/Resolver');
|
||||
var defaultConfig = require('../../../lib/config');
|
||||
|
||||
var resolver = new Resolver({ source: 'foo' }, defaultConfig, new Logger());
|
||||
var resolver = new Resolver({ source: 'foo' }, defaultConfig(), new Logger());
|
||||
resolver._createTempDir()
|
||||
.then(function (dir) {
|
||||
// Need to write something to prevent tmp to automatically
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
var fs = require('graceful-fs');
|
||||
var fs = require('../../../lib/util/fs');
|
||||
var path = require('path');
|
||||
var Logger = require('bower-logger');
|
||||
var Resolver = require('../../../lib/core/resolvers/Resolver');
|
||||
var defaultConfig = require('../../../lib/config');
|
||||
|
||||
var resolver = new Resolver({ source: 'foo' }, defaultConfig, new Logger());
|
||||
var resolver = new Resolver({ source: 'foo' }, defaultConfig(), new Logger());
|
||||
resolver._createTempDir()
|
||||
.then(function (dir) {
|
||||
// Need to write something to prevent tmp to automatically
|
||||
|
||||
23
test/commands/bower.js
Normal file
23
test/commands/bower.js
Normal file
@@ -0,0 +1,23 @@
|
||||
var expect = require('expect.js');
|
||||
var runBin = require('../helpers').runBin;
|
||||
|
||||
describe('bower', function () {
|
||||
process.env.CI = '1';
|
||||
|
||||
it('runs bower installation', function () {
|
||||
var result = runBin();
|
||||
var text = result.stdout.toString();
|
||||
|
||||
expect(text).to.contain('Usage:');
|
||||
expect(text).to.contain('Commands:');
|
||||
});
|
||||
});
|
||||
|
||||
describe('abbreviations', function () {
|
||||
it('Returns same value than the full command', function() {
|
||||
var abbr = runBin(['install']);
|
||||
var full = runBin(['i']);
|
||||
|
||||
expect(abbr.stdout.toString()).to.be.equal(full.stdout.toString());
|
||||
});
|
||||
});
|
||||
89
test/commands/cache/clean.js
vendored
Normal file
89
test/commands/cache/clean.js
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
var expect = require('expect.js');
|
||||
var md5 = require('md5-hex');
|
||||
var helpers = require('../../helpers');
|
||||
|
||||
var cacheClean = helpers.command('cache/clean');
|
||||
var object = require('mout/object');
|
||||
|
||||
describe('bower cache clean', function () {
|
||||
|
||||
// Because directory names are required to be md5 of _source
|
||||
var cacheFilesFactory = function (spec) {
|
||||
var files = {};
|
||||
|
||||
object.map(spec, function(bowerJson) {
|
||||
bowerJson._source = bowerJson.name + '/' + bowerJson.version;
|
||||
var path = md5(bowerJson._source) + '/' + bowerJson.version + '/.bower.json';
|
||||
files[path] = bowerJson;
|
||||
});
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
|
||||
var cacheFiles = cacheFilesFactory([
|
||||
{
|
||||
name: 'angular',
|
||||
version: '1.3.8'
|
||||
},
|
||||
{
|
||||
name: 'angular',
|
||||
version: '1.3.9'
|
||||
},
|
||||
{
|
||||
name: 'jquery',
|
||||
version: '1.0.0'
|
||||
}
|
||||
]);
|
||||
|
||||
var cacheDir = new helpers.TempDir(cacheFiles);
|
||||
|
||||
it('correctly reads arguments', function() {
|
||||
expect(cacheClean.readOptions(['jquery', 'angular']))
|
||||
.to.eql([['jquery', 'angular'], {}]);
|
||||
});
|
||||
|
||||
it('removes all cache', function () {
|
||||
cacheDir.prepare();
|
||||
|
||||
return helpers.run(cacheClean, [undefined, {}, {
|
||||
storage: {
|
||||
packages: cacheDir.path
|
||||
}
|
||||
}]).spread(function(result) {
|
||||
object.map(cacheFiles, function (_, cacheFile) {
|
||||
expect(cacheDir.exists(cacheFile)).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('removes single package', function () {
|
||||
cacheDir.prepare();
|
||||
|
||||
return helpers.run(cacheClean, [['angular'], {}, {
|
||||
storage: {
|
||||
packages: cacheDir.path
|
||||
}
|
||||
}]).spread(function(result) {
|
||||
var paths = Object.keys(cacheFiles);
|
||||
expect(cacheDir.exists(paths[0])).to.be(false);
|
||||
expect(cacheDir.exists(paths[1])).to.be(false);
|
||||
expect(cacheDir.exists(paths[2])).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('removes single package package version', function () {
|
||||
cacheDir.prepare();
|
||||
|
||||
return helpers.run(cacheClean, [['angular#1.3.8'], {}, {
|
||||
storage: {
|
||||
packages: cacheDir.path
|
||||
}
|
||||
}]).spread(function(result) {
|
||||
var paths = Object.keys(cacheFiles);
|
||||
expect(cacheDir.exists(paths[0])).to.be(false);
|
||||
expect(cacheDir.exists(paths[1])).to.be(true);
|
||||
expect(cacheDir.exists(paths[2])).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
60
test/commands/cache/list.js
vendored
Normal file
60
test/commands/cache/list.js
vendored
Normal 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
43
test/commands/help.js
Normal 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
59
test/commands/home.js
Normal 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 mainPackage = 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 () {
|
||||
mainPackage.prepare();
|
||||
|
||||
return Q.Promise(function(resolve) {
|
||||
var home = helpers.command('home', { opn: resolve });
|
||||
helpers.run(home, [mainPackage.path]);
|
||||
}).then(function(url) {
|
||||
expect(url).to.be('http://bower.io');
|
||||
});
|
||||
});
|
||||
|
||||
it('opens home page of current repository', function () {
|
||||
mainPackage.prepare();
|
||||
|
||||
return Q.Promise(function(resolve) {
|
||||
var home = helpers.command('home', { opn: resolve });
|
||||
helpers.run(home, [undefined, { cwd: mainPackage.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');
|
||||
});
|
||||
});
|
||||
});
|
||||
19
test/commands/index.js
Normal file
19
test/commands/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
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');
|
||||
require('./bower');
|
||||
});
|
||||
52
test/commands/info.js
Normal file
52
test/commands/info.js
Normal 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 mainPackage = 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 () {
|
||||
mainPackage.prepareGit({});
|
||||
|
||||
return helpers.run(info, [mainPackage.path]).spread(function(results) {
|
||||
expect(results).to.eql({
|
||||
'latest': meta2,
|
||||
'name': mainPackage.path,
|
||||
'versions': [
|
||||
'0.1.3',
|
||||
'0.1.2'
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
149
test/commands/init.js
Normal file
149
test/commands/init.js
Normal file
@@ -0,0 +1,149 @@
|
||||
var expect = require('expect.js');
|
||||
var helpers = require('../helpers');
|
||||
|
||||
var init = helpers.command('init');
|
||||
|
||||
describe('bower init', function () {
|
||||
|
||||
var mainPackage = new helpers.TempDir();
|
||||
|
||||
it('correctly reads arguments', function() {
|
||||
expect(init.readOptions([]))
|
||||
.to.eql([]);
|
||||
});
|
||||
|
||||
it('generates bower.json file', function () {
|
||||
mainPackage.prepare();
|
||||
|
||||
var logger = init({
|
||||
cwd: mainPackage.path,
|
||||
interactive: true
|
||||
});
|
||||
|
||||
return helpers.expectEvent(logger, 'prompt')
|
||||
.spread(function (prompt, answer) {
|
||||
answer({
|
||||
name: 'test-name',
|
||||
description: 'test-description',
|
||||
moduleType: 'test-moduleType',
|
||||
keywords: 'test-keyword',
|
||||
authors: 'test-author',
|
||||
license: 'test-license',
|
||||
homepage: 'test-homepage',
|
||||
private: true
|
||||
});
|
||||
|
||||
return helpers.expectEvent(logger, 'prompt');
|
||||
})
|
||||
.spread(function (prompt, answer) {
|
||||
answer({ prompt: true });
|
||||
return helpers.expectEvent(logger, 'end');
|
||||
})
|
||||
.then(function () {
|
||||
expect(mainPackage.readJson('bower.json')).to.eql({
|
||||
name: 'test-name',
|
||||
homepage: 'test-homepage',
|
||||
authors: [ 'test-author' ],
|
||||
description: 'test-description',
|
||||
moduleType: 'test-moduleType',
|
||||
keywords: [ 'test-keyword' ],
|
||||
license: 'test-license',
|
||||
private: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('errors on non-interactive mode', function () {
|
||||
mainPackage.prepare();
|
||||
|
||||
return helpers.run(init, { cwd: mainPackage.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 () {
|
||||
mainPackage.prepare({
|
||||
'bower.json': {
|
||||
name: 'foobar'
|
||||
}
|
||||
});
|
||||
|
||||
var logger = init({ cwd: mainPackage.path, interactive: true });
|
||||
|
||||
return helpers.expectEvent(logger, 'log').spread(function(event) {
|
||||
expect(event.level).to.be('warn');
|
||||
expect(event.message).to.be(
|
||||
'The existing bower.json file will be used and filled in'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('gets defaults from package.json', function () {
|
||||
mainPackage.prepare({
|
||||
'package.json': {
|
||||
'name': 'name from npm',
|
||||
'description': 'description from npm',
|
||||
'main': 'index.js',
|
||||
'keywords': [
|
||||
'foo',
|
||||
'bar'
|
||||
],
|
||||
'author': 'JD Isaacks',
|
||||
'license': 'ISC'
|
||||
}
|
||||
});
|
||||
|
||||
var logger = init({
|
||||
cwd: mainPackage.path,
|
||||
interactive: true
|
||||
});
|
||||
|
||||
return helpers.expectEvent(logger, 'prompt')
|
||||
.spread(function (prompt, answer) {
|
||||
|
||||
// Get defaults from prompt
|
||||
var defaults = prompt.reduce(function(memo, obj) {
|
||||
memo[obj.name] = obj['default'];
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
// Answer with defaults
|
||||
answer({
|
||||
name: defaults.name,
|
||||
description: defaults.description,
|
||||
main: defaults.main,
|
||||
moduleType: defaults.moduleType,
|
||||
keywords: defaults.keywords,
|
||||
authors: defaults.authors,
|
||||
license: defaults.license,
|
||||
homepage: 'test-homepage',
|
||||
private: true
|
||||
});
|
||||
|
||||
return helpers.expectEvent(logger, 'prompt');
|
||||
})
|
||||
.spread(function (prompt, answer) {
|
||||
answer({ prompt: true });
|
||||
return helpers.expectEvent(logger, 'end');
|
||||
})
|
||||
.then(function () {
|
||||
expect(mainPackage.readJson('bower.json')).to.eql({
|
||||
'name': 'name from npm',
|
||||
'description': 'description from npm',
|
||||
'main': 'index.js',
|
||||
'keywords': [
|
||||
'foo',
|
||||
'bar'
|
||||
],
|
||||
'authors': ['JD Isaacks'],
|
||||
'license': 'ISC',
|
||||
'private': true,
|
||||
'homepage': 'test-homepage'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
400
test/commands/install.js
Normal file
400
test/commands/install.js
Normal file
@@ -0,0 +1,400 @@
|
||||
var expect = require('expect.js');
|
||||
var helpers = require('../helpers');
|
||||
var nock = require('nock');
|
||||
var fs = require('../../lib/util/fs');
|
||||
|
||||
describe('bower install', function () {
|
||||
|
||||
var tempDir = new helpers.TempDir();
|
||||
|
||||
var install = helpers.command('install', { cwd: tempDir.path });
|
||||
|
||||
it('correctly reads arguments', function() {
|
||||
expect(install.readOptions(['jquery', 'angular', '-F', '-p', '-S', '-D', '-E']))
|
||||
.to.eql([['jquery', 'angular'], {
|
||||
forceLatest: true,
|
||||
production: true,
|
||||
save: true,
|
||||
saveDev: true,
|
||||
saveExact: true
|
||||
}]);
|
||||
});
|
||||
|
||||
it('correctly reads long arguments', function() {
|
||||
expect(install.readOptions([
|
||||
'jquery', 'angular',
|
||||
'--force-latest', '--production', '--save', '--save-dev', '--save-exact'
|
||||
])).to.eql([['jquery', 'angular'], {
|
||||
forceLatest: true,
|
||||
production: true,
|
||||
save: true,
|
||||
saveDev: true,
|
||||
saveExact: true
|
||||
}]);
|
||||
});
|
||||
|
||||
var mainPackage = new helpers.TempDir({
|
||||
'bower.json': {
|
||||
name: 'package'
|
||||
}
|
||||
}).prepare();
|
||||
|
||||
var gitPackage = new helpers.TempDir();
|
||||
|
||||
it('writes to bower.json if --save flag is used', function () {
|
||||
mainPackage.prepare();
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test'
|
||||
}
|
||||
});
|
||||
|
||||
return helpers.run(install, [[mainPackage.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 () {
|
||||
mainPackage.prepare({
|
||||
'bower.json': {
|
||||
name: 'package',
|
||||
version: '1.2.3'
|
||||
}
|
||||
});
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test'
|
||||
}
|
||||
});
|
||||
|
||||
return helpers.run(install, [
|
||||
[mainPackage.path],
|
||||
{ saveExact: true, save: true }
|
||||
]).then(function() {
|
||||
expect(tempDir.readJson('bower.json').dependencies.package).to.equal(mainPackage.path + '#1.2.3');
|
||||
});
|
||||
});
|
||||
|
||||
it('writes an exact version number to devDependencies in bower.json if --save-dev --save-exact flags are used', function () {
|
||||
mainPackage.prepare({
|
||||
'bower.json': {
|
||||
name: 'package',
|
||||
version: '0.1.0'
|
||||
}
|
||||
});
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test'
|
||||
}
|
||||
});
|
||||
|
||||
return helpers.run(install, [
|
||||
[mainPackage.path],
|
||||
{ saveExact: true, saveDev: true }
|
||||
]).then(function() {
|
||||
expect(tempDir.readJson('bower.json').devDependencies.package).to.equal(mainPackage.path + '#0.1.0');
|
||||
});
|
||||
});
|
||||
|
||||
it('reads .bowerrc from cwd', function () {
|
||||
mainPackage.prepare({ foo: 'bar' });
|
||||
|
||||
tempDir.prepare({
|
||||
'.bowerrc': { directory: 'assets' },
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: mainPackage.path
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return helpers.run(install).then(function() {
|
||||
expect(tempDir.read('assets/package/foo')).to.be('bar');
|
||||
});
|
||||
});
|
||||
|
||||
it('runs preinstall hook', function () {
|
||||
mainPackage.prepare();
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: mainPackage.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 () {
|
||||
mainPackage.prepare();
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: mainPackage.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 () {
|
||||
mainPackage.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, [[mainPackage.path], { save: true }]).then(function() {
|
||||
expect(tempDir.read('hook.txt')).to.contain('dependencies');
|
||||
});
|
||||
});
|
||||
|
||||
it('display the output of hook scripts', function (next) {
|
||||
mainPackage.prepare();
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: mainPackage.path
|
||||
}
|
||||
},
|
||||
'.bowerrc': {
|
||||
scripts: {
|
||||
postinstall: 'node -e \'process.stdout.write("foobar")\''
|
||||
}
|
||||
}
|
||||
});
|
||||
var lastAction = null;
|
||||
|
||||
helpers.run(install).logger.intercept(function (log) {
|
||||
if (log.level === 'action') {
|
||||
lastAction = log;
|
||||
}
|
||||
}).on('end', function () {
|
||||
expect(lastAction.message).to.be('foobar');
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
it('skips components not installed by bower', function () {
|
||||
mainPackage.prepare({
|
||||
'.git': {} //Make a dummy file instead of using slower gitPrepare()
|
||||
});
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: mainPackage.path
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return helpers.run(install).then(function() {
|
||||
var packageFiles = fs.readdirSync(mainPackage.path);
|
||||
//presence of .git file implies folder was not overwritten
|
||||
expect(packageFiles).to.contain('.git');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('works for git repositories', function () {
|
||||
gitPackage.prepareGit({
|
||||
'1.0.0': {
|
||||
'bower.json': {
|
||||
name: 'package'
|
||||
},
|
||||
'version.txt': '1.0.0'
|
||||
},
|
||||
'1.0.1': {
|
||||
'bower.json': {
|
||||
name: 'package'
|
||||
},
|
||||
'version.txt': '1.0.1'
|
||||
}
|
||||
});
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test',
|
||||
dependencies: {
|
||||
package: gitPackage.path + '#1.0.0'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return helpers.run(install).then(function() {
|
||||
expect(tempDir.read('bower_components/package/version.txt')).to.contain('1.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
it('does not install ignored dependencies', function() {
|
||||
mainPackage.prepare();
|
||||
var package2 = new helpers.TempDir({
|
||||
'bower.json': {
|
||||
name: 'package2',
|
||||
}
|
||||
}).prepare();
|
||||
|
||||
var package3 = new helpers.TempDir({
|
||||
'bower.json': {
|
||||
name: 'package3',
|
||||
dependencies: {
|
||||
package2: package2.path,
|
||||
package: mainPackage.path,
|
||||
}
|
||||
}
|
||||
}).prepare();
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test_tw',
|
||||
dependencies: {
|
||||
package3: package3.path
|
||||
}
|
||||
},
|
||||
'.bowerrc': {
|
||||
ignoredDependencies: ['package']
|
||||
}
|
||||
});
|
||||
|
||||
return helpers.run(install).then(function() {
|
||||
expect(tempDir.exists('bower_components/package')).to.be(false);
|
||||
expect(tempDir.exists('bower_components/package2')).to.be(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('does not install ignored dependencies if run multiple times', function() {
|
||||
mainPackage.prepare();
|
||||
var package2 = new helpers.TempDir({
|
||||
'bower.json': {
|
||||
name: 'package2',
|
||||
}
|
||||
}).prepare();
|
||||
|
||||
var package3 = new helpers.TempDir({
|
||||
'bower.json': {
|
||||
name: 'package3',
|
||||
dependencies: {
|
||||
package2: package2.path,
|
||||
package: mainPackage.path,
|
||||
}
|
||||
}
|
||||
}).prepare();
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test_tw',
|
||||
dependencies: {
|
||||
package3: package3.path
|
||||
}
|
||||
},
|
||||
'.bowerrc': {
|
||||
ignoredDependencies: ['package']
|
||||
}
|
||||
});
|
||||
return helpers.run(install).then(function() {
|
||||
return helpers.run(install).then(function() {
|
||||
expect(tempDir.exists('bower_components/package')).to.be(false);
|
||||
expect(tempDir.exists('bower_components/package2')).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('recognizes proxy option in config', function (done) {
|
||||
this.timeout(10000);
|
||||
|
||||
tempDir.prepare({
|
||||
'bower.json': {
|
||||
name: 'test_tw',
|
||||
dependencies: {
|
||||
pure: 'http://github.com/yahoo/pure/archive/v0.6.0.tar.gz'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var install = helpers.command('install', {
|
||||
cwd: tempDir.path
|
||||
});
|
||||
|
||||
nock('http://dummy.local')
|
||||
.get('http://github.com/yahoo/pure/archive/v0.6.0.tar.gz')
|
||||
.reply(500);
|
||||
|
||||
return helpers.run(install, [
|
||||
undefined,
|
||||
undefined,
|
||||
{ proxy: 'http://dummy.local/' }
|
||||
])
|
||||
.fail(function(error) {
|
||||
expect(error.message).to.equal('Status code of 500');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('errors if the components directory is not a directory', function () {
|
||||
tempDir.prepare({
|
||||
'.bowerrc': {
|
||||
directory: '.bowerrc'
|
||||
}
|
||||
});
|
||||
|
||||
return helpers.run(install).fail(function(error) {
|
||||
expect(error.code).to.equal('ENOTDIR');
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user