mirror of
https://github.com/bower/bower.git
synced 2026-04-24 03:00:19 -04:00
Compare commits
2273 Commits
v0.8.2
...
update-req
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adafa3dc03 | ||
|
|
bb17839bc2 | ||
|
|
24a6dc2f70 | ||
|
|
5a6ae540f9 | ||
|
|
4443698fdb | ||
|
|
1e2c27f338 | ||
|
|
1935716660 | ||
|
|
add601795f | ||
|
|
8e34328466 | ||
|
|
c3e9c94833 | ||
|
|
dd19bafa37 | ||
|
|
74af42c176 | ||
|
|
a6308bf8f8 | ||
|
|
e1dc0105d2 | ||
|
|
ce210e4f16 | ||
|
|
e483e9bc2c | ||
|
|
b0c3859699 | ||
|
|
e6d1b2d82e | ||
|
|
d4345bb254 | ||
|
|
975f9bdcdb | ||
|
|
a969a9c557 | ||
|
|
6500b421ce | ||
|
|
0641167b96 | ||
|
|
0d03374dab | ||
|
|
765d8e739d | ||
|
|
0bd318de53 | ||
|
|
aa6b51edc0 | ||
|
|
2c2e5309fd | ||
|
|
b716bc4e3a | ||
|
|
bda400634c | ||
|
|
b01243ac3c | ||
|
|
89902a6919 | ||
|
|
80308a41a6 | ||
|
|
47cc2262e1 | ||
|
|
f7c5154490 | ||
|
|
cba4b2a4cd | ||
|
|
bdabf6a4e6 | ||
|
|
7896224384 | ||
|
|
3209cda975 | ||
|
|
38501a0b93 | ||
|
|
e60d236b25 | ||
|
|
044896e708 | ||
|
|
fc4c260de4 | ||
|
|
d405917b4a | ||
|
|
22bbb3fcaf | ||
|
|
8b6c8eeaa9 | ||
|
|
58ddb59104 | ||
|
|
f26fe38c30 | ||
|
|
9317bb6e33 | ||
|
|
2693bae8ed | ||
|
|
8368539a93 | ||
|
|
8947400487 | ||
|
|
44eeb60529 | ||
|
|
2309cd80c6 | ||
|
|
684ab0c0a6 | ||
|
|
1f6b32a48e | ||
|
|
e4f0295ef1 | ||
|
|
85e39cf190 | ||
|
|
16c134f0f9 | ||
|
|
bcbff32716 | ||
|
|
809117ea73 | ||
|
|
af448ba484 | ||
|
|
89069784bb | ||
|
|
57cc6f7a40 | ||
|
|
cea728c7bc | ||
|
|
f6be8e5e28 | ||
|
|
c6f7ec36de | ||
|
|
3235b3c0d3 | ||
|
|
6ca5d434a4 | ||
|
|
eb27ae8fdc | ||
|
|
353a399f75 | ||
|
|
c718541a4e | ||
|
|
1f4372299a | ||
|
|
e640d0ec5c | ||
|
|
2e5acfe076 | ||
|
|
19f3c53c70 | ||
|
|
26902aec27 | ||
|
|
08feaf2b0a | ||
|
|
db1ed1c08f | ||
|
|
3c2562ca0e | ||
|
|
b1f1b8fae3 | ||
|
|
f1c874b202 | ||
|
|
4757e7353f | ||
|
|
249ac47b9c | ||
|
|
dc59913098 | ||
|
|
343e6ac8bc | ||
|
|
e729829174 | ||
|
|
40e3ee091b | ||
|
|
8ee2d78779 | ||
|
|
7c54812ecf | ||
|
|
3251051d20 | ||
|
|
81052830f2 | ||
|
|
7b2fd5dcd1 | ||
|
|
bbc9b35cb1 | ||
|
|
3aebb34f1d | ||
|
|
8065e5c64a | ||
|
|
b8e6f36a91 | ||
|
|
12d41aeb8c | ||
|
|
8c624bbda6 | ||
|
|
7ee1686cf4 | ||
|
|
8e181c1792 | ||
|
|
9569a8074d | ||
|
|
e8e4c8fdbc | ||
|
|
9e5cd572f8 | ||
|
|
69cd360551 | ||
|
|
4793fc0d1c | ||
|
|
aaaa9cd530 | ||
|
|
c2e0dc9d23 | ||
|
|
fc4446247c | ||
|
|
f4e0b3dfba | ||
|
|
4d7cdb0556 | ||
|
|
037bbff17e | ||
|
|
f05cd5fb94 | ||
|
|
358a73b98e | ||
|
|
32cbb5a0e8 | ||
|
|
8679ad77ae | ||
|
|
1b1d8bdad6 | ||
|
|
aecdf3f365 | ||
|
|
1cf87041cc | ||
|
|
5116fec1ab | ||
|
|
8f2668a24d | ||
|
|
e7516e4bcb | ||
|
|
3853d1297e | ||
|
|
1e398f999b | ||
|
|
3a0ea968f4 | ||
|
|
7de2e5d601 | ||
|
|
b1c45bb586 | ||
|
|
f4cb047e9d | ||
|
|
f494ae7ddd | ||
|
|
44e71267c1 | ||
|
|
52aa684949 | ||
|
|
46655b7c4e | ||
|
|
16cde3118a | ||
|
|
7c2dfc1146 | ||
|
|
53eeca97d3 | ||
|
|
9201a379d6 | ||
|
|
2052ba3eed | ||
|
|
b32bd8b877 | ||
|
|
233f685c61 | ||
|
|
5fe8df2e0a | ||
|
|
a3ae3b66b7 | ||
|
|
2000d2f5db | ||
|
|
84d8d8c57f | ||
|
|
f9ea3846e2 | ||
|
|
8b6c20239e | ||
|
|
73171766e7 | ||
|
|
88b3655829 | ||
|
|
3241e8ed62 | ||
|
|
0a81308e98 | ||
|
|
8d76b87d65 | ||
|
|
f2884656c0 | ||
|
|
6fff6fa707 | ||
|
|
1357f63a1b | ||
|
|
529d702959 | ||
|
|
0155a70457 | ||
|
|
a6ca2ae9bb | ||
|
|
78e443db0a | ||
|
|
5f24eab32d | ||
|
|
f4620b28ab | ||
|
|
9f4c2384ea | ||
|
|
e85a5f778f | ||
|
|
5283a132bc | ||
|
|
db1453f7c0 | ||
|
|
878a228a7d | ||
|
|
b33041c3ec | ||
|
|
c8a6ff38a0 | ||
|
|
573b84f7f4 | ||
|
|
ef67955c21 | ||
|
|
36a14b9b37 | ||
|
|
96d986f436 | ||
|
|
394dd7c8d2 | ||
|
|
6c67d07cc8 | ||
|
|
cd7bbab310 | ||
|
|
4b4a854ed8 | ||
|
|
8194bcb4c6 | ||
|
|
e5d478a1cc | ||
|
|
bbaaee67a1 | ||
|
|
ad27112b58 | ||
|
|
38c3cee1a7 | ||
|
|
b485c5d3cb | ||
|
|
d63047b4ee | ||
|
|
f0a54d0018 | ||
|
|
1d73764788 | ||
|
|
9d2681b0c4 | ||
|
|
f3330e8612 | ||
|
|
11996c04b7 | ||
|
|
35e73a619a | ||
|
|
fe615fd517 | ||
|
|
3e3b64218d | ||
|
|
afe76e57f8 | ||
|
|
6ee3ef7aa8 | ||
|
|
64db869bd4 | ||
|
|
a4ea05800d | ||
|
|
8cf897cd19 | ||
|
|
d06af7a3d7 | ||
|
|
d4fd71986e | ||
|
|
3154444556 | ||
|
|
24f8b913b9 | ||
|
|
fe6b6863ea | ||
|
|
671c23ad50 | ||
|
|
5384fa54b1 | ||
|
|
4bfa8227d9 | ||
|
|
55d78f7928 | ||
|
|
2110148830 | ||
|
|
afc4bfbd42 | ||
|
|
9c42a008aa | ||
|
|
67884744c3 | ||
|
|
ed881e3f29 | ||
|
|
e6e60d5d5e | ||
|
|
d8f166a933 | ||
|
|
bb626d1605 | ||
|
|
daa5b8ddf9 | ||
|
|
db087dfe13 | ||
|
|
848e401efd | ||
|
|
c17c725057 | ||
|
|
2b31f6c07a | ||
|
|
3cf597fccf | ||
|
|
e2adbc37f1 | ||
|
|
6c3b7dbf58 | ||
|
|
d3ab3c1fa7 | ||
|
|
b1ba9be7f6 | ||
|
|
1e5122c023 | ||
|
|
4255d7d4a8 | ||
|
|
cdf45239f4 | ||
|
|
8b2fad32f6 | ||
|
|
d1ae0b1982 | ||
|
|
87cf578ba8 | ||
|
|
3ead440c7c | ||
|
|
e168c894a2 | ||
|
|
75e3661371 | ||
|
|
baf8f7bf6b | ||
|
|
88758cd98c | ||
|
|
3bd2d62e67 | ||
|
|
d3eef5772a | ||
|
|
7c714901d4 | ||
|
|
7792b6d35d | ||
|
|
2db983dba3 | ||
|
|
b9718bb309 | ||
|
|
26f609e614 | ||
|
|
4c2b56096b | ||
|
|
5af929f0be | ||
|
|
6a18cde782 | ||
|
|
32c5538fc5 | ||
|
|
57478d86c7 | ||
|
|
42107e6fea | ||
|
|
686e883d87 | ||
|
|
f2767648e7 | ||
|
|
9e4bdd270d | ||
|
|
7cb88ab49f | ||
|
|
a532c55dca | ||
|
|
c559432c19 | ||
|
|
9aae3b8d7e | ||
|
|
322c49edf4 | ||
|
|
34ec39507c | ||
|
|
fe9b27a647 | ||
|
|
304bb36bbc | ||
|
|
376a2de600 | ||
|
|
1605374a25 | ||
|
|
ac244a1400 | ||
|
|
9e58bbca31 | ||
|
|
027a0694f7 | ||
|
|
d50e50f3b5 | ||
|
|
3030469c59 | ||
|
|
977e0ddc52 | ||
|
|
de3e1089da | ||
|
|
7897ad7dba | ||
|
|
accdab3ece | ||
|
|
612aaa88eb | ||
|
|
338ac99080 | ||
|
|
9605bbea5f | ||
|
|
7bc97a1241 | ||
|
|
bd2c253f99 | ||
|
|
f1efce7d1f | ||
|
|
844cc5a527 | ||
|
|
0e27b6f813 | ||
|
|
3791aee9d6 | ||
|
|
20a223a959 | ||
|
|
9219f54718 | ||
|
|
ea5bd51327 | ||
|
|
5a2272cab1 | ||
|
|
b94c20b8da | ||
|
|
b81ba140e3 | ||
|
|
4ffdb500b9 | ||
|
|
609607b096 | ||
|
|
e9657668a9 | ||
|
|
1696cde273 | ||
|
|
94ffc35b25 | ||
|
|
5a1e5eb9c7 | ||
|
|
8c1f30b1c8 | ||
|
|
e3f402fc66 | ||
|
|
6616d09f47 | ||
|
|
25ad2ef946 | ||
|
|
ba4a1a9d45 | ||
|
|
3a37202dc5 | ||
|
|
12258324d3 | ||
|
|
2adb0b0807 | ||
|
|
19fc84007d | ||
|
|
8fcbd3671d | ||
|
|
0eadbef02d | ||
|
|
107ad1d3fc | ||
|
|
d2ba80e6e9 | ||
|
|
f18b38cde5 | ||
|
|
e6f1805df0 | ||
|
|
4da1b62542 | ||
|
|
852a586d5c | ||
|
|
e8a2d92785 | ||
|
|
50ed13e4ee | ||
|
|
cb9b737b9d | ||
|
|
700b46162c | ||
|
|
0e1153f610 | ||
|
|
8669ed2aac | ||
|
|
6d12ef291b | ||
|
|
ca0a36abcf | ||
|
|
4c6fdc905f | ||
|
|
cdbc4a123c | ||
|
|
bb7c02b07b | ||
|
|
4f42aeabd7 | ||
|
|
b77517ef64 | ||
|
|
b9c3f750eb | ||
|
|
aaecbfab17 | ||
|
|
c4539aa603 | ||
|
|
7f801319bf | ||
|
|
1a990f4563 | ||
|
|
944a328f30 | ||
|
|
e11b60d812 | ||
|
|
c91e99b782 | ||
|
|
da8ec1e4ab | ||
|
|
c8d5199815 | ||
|
|
e7868f0fb1 | ||
|
|
eca46dbd85 | ||
|
|
67bd5d026f | ||
|
|
51de67cc73 | ||
|
|
3d4f9cd919 | ||
|
|
cd8d397e63 | ||
|
|
5a9c099188 | ||
|
|
2137089a70 | ||
|
|
fbd02852a3 | ||
|
|
f2584ade24 | ||
|
|
2f72cd4b7d | ||
|
|
50bfd14968 | ||
|
|
2ff53fc448 | ||
|
|
42cd2e584f | ||
|
|
49de3cca62 | ||
|
|
718db0309f | ||
|
|
d867095f50 | ||
|
|
e51bf20e72 | ||
|
|
89286e628b | ||
|
|
c8042b4781 | ||
|
|
15f8a30cd4 | ||
|
|
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 | ||
|
|
b7c19695e7 | ||
|
|
b85cf2683c | ||
|
|
ff0f2a8f83 | ||
|
|
7e5184d342 | ||
|
|
8fa1fd55e9 | ||
|
|
52463dea09 | ||
|
|
8cf09f5444 | ||
|
|
20a6190ed8 | ||
|
|
402a9f3017 | ||
|
|
af09872fba | ||
|
|
2311d7dc44 | ||
|
|
b261bf8a76 | ||
|
|
140c6d963f | ||
|
|
0c5e457359 | ||
|
|
bfa4295606 | ||
|
|
8ac68ede5d | ||
|
|
a019f887e9 | ||
|
|
eba2c69308 | ||
|
|
8df5970300 | ||
|
|
db265d471f | ||
|
|
0bb1536c99 | ||
|
|
64eb7d598a | ||
|
|
df8e5a16be | ||
|
|
3ce2dd3989 | ||
|
|
99105fbb57 | ||
|
|
96f1e98859 | ||
|
|
d614b057c9 | ||
|
|
aafe02f3e9 | ||
|
|
0441e16bdb | ||
|
|
30a489535e | ||
|
|
ac88ece259 | ||
|
|
9b45c76744 | ||
|
|
059a5f83b7 | ||
|
|
cf5cd61995 | ||
|
|
a499cc5103 | ||
|
|
123779bbd1 | ||
|
|
5365c7b428 | ||
|
|
da961a9c42 | ||
|
|
8ff0e0e2d1 | ||
|
|
ffde6bd228 | ||
|
|
bebb4fb33b | ||
|
|
2c243ea5b7 | ||
|
|
498fe84b99 | ||
|
|
9b8b66ed83 | ||
|
|
9cc3dd4c92 | ||
|
|
bf23d81c9e | ||
|
|
f53100d8d3 | ||
|
|
acbe60cdf1 | ||
|
|
4c7f37e0f8 | ||
|
|
3ba696937c | ||
|
|
1647994471 | ||
|
|
674e09dc56 | ||
|
|
4c129d470f | ||
|
|
c630f01baa | ||
|
|
6018fc13b2 | ||
|
|
c4fc6cd0e2 | ||
|
|
eed8735238 | ||
|
|
8c0155e8bd | ||
|
|
b2d4412e59 | ||
|
|
4e3e45a88b | ||
|
|
af9b386d8a | ||
|
|
201b8a3bc6 | ||
|
|
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 | ||
|
|
a23f66d889 | ||
|
|
a1596bb63c | ||
|
|
ada6fc18d9 | ||
|
|
19af145166 | ||
|
|
4ed81be0c6 | ||
|
|
1c62adcdb6 | ||
|
|
95f46930a5 | ||
|
|
50b0186c06 | ||
|
|
2c9d847a1d | ||
|
|
816cdda6bb | ||
|
|
76dd504589 | ||
|
|
938c69c816 | ||
|
|
d5fc402a89 | ||
|
|
065a7a1f1b | ||
|
|
833f97198e | ||
|
|
293d5cc02b | ||
|
|
dbb302f22a | ||
|
|
ccadffea73 | ||
|
|
b3390ce201 | ||
|
|
6039f6c691 | ||
|
|
c65cdc8699 | ||
|
|
7603886e04 | ||
|
|
406a96e4d1 | ||
|
|
87d21e7968 | ||
|
|
9dd79a8061 | ||
|
|
a1287416d4 | ||
|
|
00dc877f0a | ||
|
|
335081053d | ||
|
|
7d74d7d8f6 | ||
|
|
e98d8139bc | ||
|
|
4af22cfbc0 | ||
|
|
8a47aab01d | ||
|
|
395b208a0c | ||
|
|
ab4f8a0e39 | ||
|
|
7e110603b5 | ||
|
|
94455192ad | ||
|
|
c4ca24a537 | ||
|
|
ea1f5d1ff0 | ||
|
|
77b7355433 | ||
|
|
f6178c2f75 | ||
|
|
f1e04e5629 | ||
|
|
cc913a728a | ||
|
|
e727566741 | ||
|
|
0f68da4eb8 | ||
|
|
1a7abfd3b7 | ||
|
|
905c2775d2 | ||
|
|
126da9ee12 | ||
|
|
1080cb71c4 | ||
|
|
39a295901a | ||
|
|
7e55b0b099 | ||
|
|
f8c179b153 | ||
|
|
b4aa90b402 | ||
|
|
bea46fb879 | ||
|
|
379de05a61 | ||
|
|
a1ecf8a413 | ||
|
|
4d59d266c1 | ||
|
|
c2f222760a | ||
|
|
7e0a2ea4cb | ||
|
|
912808b672 | ||
|
|
a352d51711 | ||
|
|
4ad5ed64d7 | ||
|
|
3838a3b4fb | ||
|
|
7d748ae15e | ||
|
|
9aef3b7f1d | ||
|
|
9dab389b50 | ||
|
|
260b4adb8c | ||
|
|
c93bbbd302 | ||
|
|
009e5ce0c8 | ||
|
|
61bb4f53c0 | ||
|
|
182d92f9bd | ||
|
|
61a68a9e38 | ||
|
|
b6e33d70c8 | ||
|
|
7a26bf1a10 | ||
|
|
b6dd5e445e | ||
|
|
6f4b77a440 | ||
|
|
463584ea10 | ||
|
|
fd8d603831 | ||
|
|
a1ec83b002 | ||
|
|
041d3f2843 | ||
|
|
0b22127906 | ||
|
|
0b0b507827 | ||
|
|
df1a87eb4e | ||
|
|
16de289942 | ||
|
|
e203b1aa9a | ||
|
|
02dc97e413 | ||
|
|
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 | ||
|
|
703eae72eb | ||
|
|
6a629e963c | ||
|
|
06f4d0c117 | ||
|
|
b5e557ffb0 | ||
|
|
8bd6c4a335 | ||
|
|
29eaff9edc | ||
|
|
08afaf7fa5 | ||
|
|
45bab9fe71 | ||
|
|
514eb8f0e3 | ||
|
|
a7baa58c22 | ||
|
|
e548d8b1a5 | ||
|
|
92ff0fe624 | ||
|
|
a464f5a88e | ||
|
|
962a565d30 | ||
|
|
f242e60c1a | ||
|
|
7db50391f2 | ||
|
|
8b0d55a729 | ||
|
|
de6f341f41 | ||
|
|
c9fb530dbf | ||
|
|
836bcd09ec | ||
|
|
52a6836872 | ||
|
|
c00cadb37a | ||
|
|
b26c072f0d | ||
|
|
c99482f59d | ||
|
|
4aa0f567c3 | ||
|
|
4656021902 | ||
|
|
3df6144b77 | ||
|
|
6d335abe21 | ||
|
|
179b8a28b4 | ||
|
|
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 | ||
|
|
fc13328e2a | ||
|
|
d1427e7d2e | ||
|
|
5584d1062e | ||
|
|
b3f28fac64 | ||
|
|
f65af7a308 | ||
|
|
559f50a3e3 | ||
|
|
18b809314c | ||
|
|
aa1f819c0d | ||
|
|
5a8cecf499 | ||
|
|
79f362abee | ||
|
|
42db74b522 | ||
|
|
8cb41fe5fb | ||
|
|
cd893fec15 | ||
|
|
3309e9f53f | ||
|
|
ae3a017143 | ||
|
|
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 | ||
|
|
43d7a11ba8 | ||
|
|
02e12e17d6 | ||
|
|
e42d3d5620 | ||
|
|
5c83972401 | ||
|
|
b8df18481f | ||
|
|
1690dd4728 | ||
|
|
a86087ed7c | ||
|
|
745060f0b5 | ||
|
|
0a0a490a38 | ||
|
|
4f838685d6 | ||
|
|
6339ba09f0 | ||
|
|
f816a5b0da | ||
|
|
2f02e49716 | ||
|
|
a98a0b1ac2 | ||
|
|
034326c984 | ||
|
|
a965b05400 | ||
|
|
23c2e82c97 | ||
|
|
6c2de4a359 | ||
|
|
e9588279c8 | ||
|
|
7138d3518e | ||
|
|
f852325906 | ||
|
|
3c9082ece3 | ||
|
|
efe3a78499 | ||
|
|
98c77ffe18 | ||
|
|
31969a4939 | ||
|
|
b95d5f9f23 | ||
|
|
adf59d78d7 | ||
|
|
904ae3eab7 | ||
|
|
44d2309700 | ||
|
|
5dd6a3883a | ||
|
|
7a00c5ac71 | ||
|
|
23fbbb5191 | ||
|
|
a58b1ccfa5 | ||
|
|
5f0a3fe1c1 | ||
|
|
37b0a5c04c | ||
|
|
3fb4f60192 | ||
|
|
635fa84731 | ||
|
|
410bf4f0cd | ||
|
|
eebe115b78 | ||
|
|
70eb9f0678 | ||
|
|
b9de179fe8 | ||
|
|
ac29e24c1b | ||
|
|
d83572803d | ||
|
|
87faa4f108 | ||
|
|
ea3a0d64c1 | ||
|
|
8288d2f38b | ||
|
|
ff817dad0d | ||
|
|
ba554d5f45 | ||
|
|
90922a0ce0 | ||
|
|
a91cb546f9 | ||
|
|
3cb21d128f | ||
|
|
1b8d5d0648 | ||
|
|
03f035cdf0 | ||
|
|
ac95654409 | ||
|
|
ba33bb6aa4 | ||
|
|
2898c0507e | ||
|
|
d59edd6cca | ||
|
|
bc3079332c | ||
|
|
3c0395b19f | ||
|
|
9e7b591c78 | ||
|
|
b6107a1198 | ||
|
|
2d94018f12 | ||
|
|
42f0268829 | ||
|
|
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 | ||
|
|
4812c380c4 | ||
|
|
98ea43eca9 | ||
|
|
cd541c7a20 | ||
|
|
5a5cf31aba | ||
|
|
3074711828 | ||
|
|
aa97c928e9 | ||
|
|
5b4d6f2fee | ||
|
|
0e833c155a | ||
|
|
9ebb1b17d1 | ||
|
|
3397cf053c | ||
|
|
6e73b5934e | ||
|
|
7120ad5014 | ||
|
|
6286cfac28 | ||
|
|
1af09da2aa | ||
|
|
bea533acf8 | ||
|
|
5316c2479e | ||
|
|
9895f8c71e | ||
|
|
9749a398c0 | ||
|
|
2883a278ee | ||
|
|
6bbb9ec956 | ||
|
|
630556f7fe | ||
|
|
32a79c4fa1 | ||
|
|
eabfee6890 | ||
|
|
6b9b283149 | ||
|
|
a19ad6663c | ||
|
|
dfae97a8d7 | ||
|
|
93d03434eb | ||
|
|
76fcd341f6 | ||
|
|
cea53ddd1f | ||
|
|
cfc061c960 | ||
|
|
09bc1784e4 | ||
|
|
b723cf92e7 | ||
|
|
3d7b9f60da | ||
|
|
b049bdb33f | ||
|
|
a5c49d89d5 | ||
|
|
1f9a92e6ad | ||
|
|
6280611aea | ||
|
|
20c0e6e12b | ||
|
|
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 | ||
|
|
d3ccc73796 | ||
|
|
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 | ||
|
|
53d3ac570e | ||
|
|
237022baae | ||
|
|
b11ef97b7d | ||
|
|
bccfd7bbbb | ||
|
|
634ed4a341 | ||
|
|
52aa87c145 | ||
|
|
597853cd6c | ||
|
|
1bd6568f94 | ||
|
|
4b2235aef2 | ||
|
|
faf1c26669 | ||
|
|
3aea5fb704 | ||
|
|
ea559592b5 | ||
|
|
c385c08e2f | ||
|
|
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 | ||
|
|
ab7e7ac12a | ||
|
|
99cb553cbd | ||
|
|
dcc396234c | ||
|
|
3788e8d7b3 | ||
|
|
715de1b18c | ||
|
|
7c2384cb94 | ||
|
|
5b1dd15749 | ||
|
|
041290e1c7 | ||
|
|
1f8417041d | ||
|
|
7ecd70a90b | ||
|
|
6d85ae8e8e | ||
|
|
f405877a16 | ||
|
|
d22c6f9d66 | ||
|
|
c87f0495c0 | ||
|
|
15fbf44948 | ||
|
|
10963bd808 | ||
|
|
0866c93573 | ||
|
|
48e475c9f6 | ||
|
|
2c5ab19374 | ||
|
|
9d50c31d2c | ||
|
|
b8b03c88e4 | ||
|
|
a65f57e0f5 | ||
|
|
491027b048 | ||
|
|
5e1971a951 | ||
|
|
1099e786df | ||
|
|
be95169c1b | ||
|
|
f64f672b20 | ||
|
|
7c0a5d60ef | ||
|
|
1f7bd9fd47 | ||
|
|
2a7c3eb2e6 | ||
|
|
32aeba2bb8 | ||
|
|
20e376c214 | ||
|
|
a0c1552726 | ||
|
|
46783e32d5 | ||
|
|
2eafac3ff6 | ||
|
|
9bd85b7023 | ||
|
|
eb9dcce30b | ||
|
|
a899cb48b3 | ||
|
|
ed8ac01f07 | ||
|
|
742ef58d89 | ||
|
|
c98bef5fc7 | ||
|
|
23044b3758 | ||
|
|
107ce78b12 | ||
|
|
5f81b8f0c1 | ||
|
|
83d75e704b | ||
|
|
a860e3eaf7 | ||
|
|
3d84bcc7ad | ||
|
|
c31504220d | ||
|
|
f8501cf7f4 | ||
|
|
81ef5a1dd2 | ||
|
|
29ec793386 | ||
|
|
187457df13 | ||
|
|
2eea214a42 | ||
|
|
639c2290b7 | ||
|
|
3044dcd3af | ||
|
|
3d64b16227 | ||
|
|
7bd22a5103 | ||
|
|
12cde3ddce | ||
|
|
860d70551f | ||
|
|
6db6fcc414 | ||
|
|
25c229de73 | ||
|
|
d954a54017 | ||
|
|
dcdd8cb3db | ||
|
|
e93c5f8265 | ||
|
|
77be4520dc | ||
|
|
cf76bcb2aa | ||
|
|
b272a61eac | ||
|
|
4ed5f278f8 | ||
|
|
6a16850960 | ||
|
|
78bbf1f04f | ||
|
|
52938202bd | ||
|
|
0eac8bb96e | ||
|
|
46ae1e759c | ||
|
|
e0c1308c2a | ||
|
|
0f29818030 | ||
|
|
dd39a25dd0 | ||
|
|
430dc04b09 | ||
|
|
39209f3bda | ||
|
|
0eaa43c05d | ||
|
|
6ed4be9135 | ||
|
|
02b6a21276 | ||
|
|
794ca573b8 | ||
|
|
5eadd36107 | ||
|
|
c6a370309d | ||
|
|
13e2514830 | ||
|
|
0b8be97fb5 | ||
|
|
c327dcb255 | ||
|
|
1b505cc0e6 | ||
|
|
032f771996 | ||
|
|
35d83d2306 | ||
|
|
dd23feb5ea | ||
|
|
e21d7bd000 | ||
|
|
d18cfa7b59 | ||
|
|
eca956cf80 | ||
|
|
587cc5acdd | ||
|
|
b5e9a4c191 | ||
|
|
3ced03ca7f | ||
|
|
9fa08fee99 | ||
|
|
3dfd7a9ab1 | ||
|
|
1713e5e2eb | ||
|
|
1d7342573b | ||
|
|
64fc295ecc | ||
|
|
12baabcc89 | ||
|
|
13839e9384 | ||
|
|
6b6dc8311a | ||
|
|
b1d8c3c1e3 | ||
|
|
d42a564de8 | ||
|
|
71d083a552 | ||
|
|
cf802c368d | ||
|
|
71037cb482 | ||
|
|
1f4e5cadd2 | ||
|
|
72e6e61970 | ||
|
|
4402b80ec9 | ||
|
|
3f0dbef7ea | ||
|
|
1ee8abf098 | ||
|
|
55eb1e2290 | ||
|
|
906990d9b5 | ||
|
|
518f3d2a8f | ||
|
|
6ba6ea0084 | ||
|
|
01c499d73f | ||
|
|
0de9cc82f6 | ||
|
|
7dba46df9b | ||
|
|
7621a71359 | ||
|
|
17f72f0ae2 | ||
|
|
47094ef046 | ||
|
|
6e28e79b29 | ||
|
|
463b258409 | ||
|
|
49d4c96ddf | ||
|
|
197d3e9d36 | ||
|
|
adcc368238 | ||
|
|
eff97c4d28 | ||
|
|
d2aeed7a0c | ||
|
|
9cb09feb65 | ||
|
|
fb084fa4cd | ||
|
|
846b8fb57e | ||
|
|
cb649830a0 | ||
|
|
bf4266c2e3 | ||
|
|
814a0f9016 | ||
|
|
098753520f | ||
|
|
43ae12f63b | ||
|
|
46937bbb87 | ||
|
|
5bf0c768f7 | ||
|
|
f124fd4c23 | ||
|
|
ff49d24743 | ||
|
|
99ca255aad | ||
|
|
d1a63b6fe0 | ||
|
|
3530a33fe7 | ||
|
|
d08efcfe3c | ||
|
|
337c0f2d0a | ||
|
|
1acf4a53d3 | ||
|
|
a2b0fb7d82 | ||
|
|
088c488e2d | ||
|
|
64ef934a3c | ||
|
|
39b690e96a | ||
|
|
08fc86698d | ||
|
|
89510f40d3 | ||
|
|
9f2207eb1f | ||
|
|
7bacf8c425 | ||
|
|
896672c4f4 | ||
|
|
a6552d7d54 | ||
|
|
cacef7b316 | ||
|
|
7a8642db94 | ||
|
|
2b8e0fdf06 | ||
|
|
dfb18b305d | ||
|
|
1606395546 | ||
|
|
abe4264a13 | ||
|
|
60d9bfb2b8 | ||
|
|
9141b3020f | ||
|
|
367c3c3e95 | ||
|
|
41a3903ac9 | ||
|
|
146d6ac538 | ||
|
|
fce93e16ae | ||
|
|
fb12fc03bf | ||
|
|
9c49b097c8 | ||
|
|
524e83fca4 | ||
|
|
f5999e84a2 | ||
|
|
ee2640bc43 | ||
|
|
2b93aa000c | ||
|
|
9c4b30b65c | ||
|
|
160dc5c470 | ||
|
|
bbe2896de2 | ||
|
|
673f641283 | ||
|
|
36d8d308f2 | ||
|
|
3482dd2a6b | ||
|
|
9cee8b7b78 | ||
|
|
4ae1b5e04d | ||
|
|
a34c8dc502 | ||
|
|
b42c1ddbbc | ||
|
|
9cc4860050 | ||
|
|
58921f7422 | ||
|
|
8dbd79d49b | ||
|
|
e2c67fa25a | ||
|
|
f332de8ba9 | ||
|
|
8dce9a6f66 | ||
|
|
1c616115b9 | ||
|
|
a97375552f | ||
|
|
1cf99d757a | ||
|
|
e84e94a8d1 | ||
|
|
cf85177c7f | ||
|
|
2b334774ce | ||
|
|
bc4a0f448b | ||
|
|
14ef86456f | ||
|
|
b083422321 | ||
|
|
12efc85baf | ||
|
|
68124dfdbe | ||
|
|
ee4158e90b | ||
|
|
0b3b7efccf | ||
|
|
b282d02a77 | ||
|
|
e07c813320 | ||
|
|
b76a00b6fe | ||
|
|
77e077d658 | ||
|
|
1cec48c065 | ||
|
|
30fa7f5a7f | ||
|
|
83edff6b26 | ||
|
|
fd58a5fe06 | ||
|
|
6064269936 | ||
|
|
d0929896cf | ||
|
|
990e87de1f | ||
|
|
8da47dcd00 | ||
|
|
15957bc9ac | ||
|
|
59f87bdffe | ||
|
|
ee31e93f98 | ||
|
|
d3f3d17536 | ||
|
|
88c1045180 | ||
|
|
2c06feb6a1 | ||
|
|
5b4fb961af | ||
|
|
3995ec0ff7 | ||
|
|
d2144905db | ||
|
|
b29c9c28fa | ||
|
|
aa2e68d1ad | ||
|
|
85b2ec93c8 | ||
|
|
c63d88d987 | ||
|
|
c21bde192b | ||
|
|
39f1f8aff5 | ||
|
|
3f8fcbf4e3 | ||
|
|
24cabf19af | ||
|
|
ae04f71e55 | ||
|
|
c56f0db891 | ||
|
|
ebf2765f2f | ||
|
|
8dac95d86d | ||
|
|
6125c0423f | ||
|
|
f3d1638131 | ||
|
|
8b699c58ae | ||
|
|
743a97c784 | ||
|
|
ea7ae5698a | ||
|
|
3ed9b3ec39 | ||
|
|
e8f3e3b88a | ||
|
|
82d16fbfde | ||
|
|
73c2e885e9 | ||
|
|
2e615e7570 | ||
|
|
d0f90546f3 | ||
|
|
22091cf272 | ||
|
|
f2f3fa4c9c | ||
|
|
0ab2ce9cf6 | ||
|
|
1b1424fe2f | ||
|
|
ee31c44ff1 | ||
|
|
e64af6b11b | ||
|
|
d6045931a5 | ||
|
|
9f99d5d42a | ||
|
|
327703871b | ||
|
|
b456f71b64 | ||
|
|
9576b8a8e9 | ||
|
|
e0037c7eaf | ||
|
|
59c5caa395 | ||
|
|
e4a35594ba | ||
|
|
5f44d1f2cf | ||
|
|
489c6f6730 | ||
|
|
c66afdf434 | ||
|
|
d62f5142cc | ||
|
|
5d4007918c | ||
|
|
5b49b5c535 | ||
|
|
7bd19e39a7 | ||
|
|
5037aed3ae | ||
|
|
c00bacb790 | ||
|
|
5672019203 | ||
|
|
7cb5da9971 | ||
|
|
62672bc4c8 | ||
|
|
705d62be18 | ||
|
|
d237522e8c | ||
|
|
44308fa026 | ||
|
|
766dcd0dd5 | ||
|
|
87302d6d86 | ||
|
|
aae19206a0 | ||
|
|
212c4c3a71 | ||
|
|
e1a443177e | ||
|
|
1210ea821a | ||
|
|
9721381383 | ||
|
|
5ea9a1d715 | ||
|
|
83f00d720e | ||
|
|
ee76fe584e | ||
|
|
063453b744 | ||
|
|
05b94d1d15 | ||
|
|
ed0c93aec3 | ||
|
|
3a0cb8b35c | ||
|
|
f0ed7f22db | ||
|
|
ac91f07d22 | ||
|
|
d5a7a691a2 | ||
|
|
229b23b350 | ||
|
|
18ee9518ec | ||
|
|
43e0328722 | ||
|
|
12cf93ddd8 | ||
|
|
03b4467173 | ||
|
|
45a5ac28f2 | ||
|
|
635eec013c | ||
|
|
679b9217e0 | ||
|
|
39f3571383 | ||
|
|
f027cc6a3e | ||
|
|
634ce6829a | ||
|
|
64b5f9af78 | ||
|
|
9269fcb8a7 | ||
|
|
571b34c990 | ||
|
|
f0d29cb755 | ||
|
|
897e0f1ba6 | ||
|
|
4cfa94d304 | ||
|
|
5e2abb8a33 | ||
|
|
0dc5052e36 | ||
|
|
774ad1e2ff | ||
|
|
52e91cc309 | ||
|
|
38fb72ece9 | ||
|
|
fcbefa218b | ||
|
|
c06b9d696e | ||
|
|
d3e6274939 | ||
|
|
ec904eb8a4 | ||
|
|
68c5e996d6 | ||
|
|
95a09c5463 | ||
|
|
125fb598d5 | ||
|
|
f8c13f939c | ||
|
|
e55e08339a | ||
|
|
ec86260eb5 | ||
|
|
adee5cec2b | ||
|
|
c846b24ebb | ||
|
|
85324d9109 | ||
|
|
10b410d46f | ||
|
|
c87fe7c265 | ||
|
|
ce0984573b | ||
|
|
b38c3a5035 | ||
|
|
15dca65bd1 | ||
|
|
e1aa43147d | ||
|
|
057b18e4be | ||
|
|
d4f2ced6a3 | ||
|
|
0b6c92fcd2 | ||
|
|
4491e71ee2 | ||
|
|
d09f78801c | ||
|
|
f26ea32897 | ||
|
|
6b7f6aee22 | ||
|
|
514fd0730c | ||
|
|
854596fb04 | ||
|
|
cf6b8f752b | ||
|
|
702f36be0d | ||
|
|
c738bc1e96 | ||
|
|
6c1c13ed11 | ||
|
|
d1b39cce2b | ||
|
|
5e67622374 | ||
|
|
90e95bbd76 | ||
|
|
6deaacadea | ||
|
|
2ae4003f7d | ||
|
|
dd951d5350 | ||
|
|
c86d0230ba | ||
|
|
79345691b8 | ||
|
|
2763cf37f7 | ||
|
|
66cfe050e0 | ||
|
|
e6b1595cde | ||
|
|
3b376a3d3d | ||
|
|
5ed04d97dd | ||
|
|
1e724af838 | ||
|
|
aec00821e5 | ||
|
|
bf1542c35b | ||
|
|
5a6d27cc19 | ||
|
|
2d83abbb85 | ||
|
|
a9afa42f52 | ||
|
|
a37f1c5dcb | ||
|
|
ecac2bd084 | ||
|
|
01aec3760c | ||
|
|
33cac4832f | ||
|
|
41b246903f | ||
|
|
4148e0c7a9 | ||
|
|
729bdd9c68 | ||
|
|
1f2d8d6340 | ||
|
|
de3cf327d4 | ||
|
|
fa3da880ea | ||
|
|
5dea57ddd5 | ||
|
|
caa59cbb62 | ||
|
|
51e32f72de | ||
|
|
32089aefce | ||
|
|
048bc53bd0 | ||
|
|
69e1d04638 | ||
|
|
de4c7a04c3 | ||
|
|
2c79181c05 | ||
|
|
5b37ec0de5 | ||
|
|
86dbea7ddb | ||
|
|
c49e7e181b | ||
|
|
b0dbbc51df | ||
|
|
91779a67fb | ||
|
|
1fe6cde934 | ||
|
|
3b8d00095e | ||
|
|
4221ddbb35 | ||
|
|
eeb0c22d90 | ||
|
|
eb801e66ae | ||
|
|
3fdf639bb4 | ||
|
|
4061ef65e4 | ||
|
|
0cf8af00b3 | ||
|
|
4d0d4ca6ea | ||
|
|
56cdae67c3 | ||
|
|
b50017cad4 | ||
|
|
62c616f197 | ||
|
|
fcfeda9f39 | ||
|
|
c85c38cde3 | ||
|
|
f606eda18d | ||
|
|
08c9e2dde3 | ||
|
|
dd965e03c5 | ||
|
|
3e3f5faacc | ||
|
|
7e41cd0d32 | ||
|
|
882bf7b020 | ||
|
|
da4e70bc60 | ||
|
|
f75f720c8a | ||
|
|
8d3aff5ff1 | ||
|
|
4e8c9078f7 | ||
|
|
41e4efcf1f | ||
|
|
f55e6138a5 | ||
|
|
147e24d835 | ||
|
|
4a94858ed1 | ||
|
|
e1d77e12b9 | ||
|
|
8e51d1eaf3 | ||
|
|
738835088b | ||
|
|
823b287eda | ||
|
|
a2c195e844 | ||
|
|
5a5503ab34 | ||
|
|
6e037a4d46 | ||
|
|
14597f74f4 | ||
|
|
fabf2479ec | ||
|
|
12c90bae04 | ||
|
|
37904db9cd | ||
|
|
499d9da75f | ||
|
|
198d612b87 | ||
|
|
e7667ba34b | ||
|
|
861ef7313b | ||
|
|
0291ffb951 | ||
|
|
93a7f9f3b8 | ||
|
|
62337e34a3 | ||
|
|
a24b70de75 | ||
|
|
6f7f10b2f7 | ||
|
|
bd7579fa65 | ||
|
|
430a2ea2f6 | ||
|
|
87569617ae | ||
|
|
b17beaccf1 | ||
|
|
cddba64151 | ||
|
|
ed3a18529a | ||
|
|
06bffb9cee | ||
|
|
a6e4ff83f0 | ||
|
|
a73025d20b | ||
|
|
0085ebb756 | ||
|
|
5d5f23cddc | ||
|
|
761c947e25 | ||
|
|
ec33766803 | ||
|
|
6daf891c5c | ||
|
|
9b04553fc8 | ||
|
|
a006bfeb24 | ||
|
|
71e1a8666d | ||
|
|
820c7e660a | ||
|
|
e94cb6a4b6 | ||
|
|
16e7872a82 | ||
|
|
aa4ebb07f8 | ||
|
|
4366d6a8c7 | ||
|
|
aa76d49234 | ||
|
|
d9df06644e | ||
|
|
554ee01263 | ||
|
|
b3055067d8 | ||
|
|
7f997d4b59 | ||
|
|
07717b2d08 | ||
|
|
7d2162b2d2 | ||
|
|
d15fa0cfb3 | ||
|
|
d2d959f455 | ||
|
|
a9e497f878 | ||
|
|
e423e9ffba | ||
|
|
3b8b52d222 | ||
|
|
b9514692fe | ||
|
|
73592f76ba | ||
|
|
47f0f1fab8 | ||
|
|
bb3a5620d3 | ||
|
|
268a1475f0 | ||
|
|
a46dad9fde | ||
|
|
3f7bf15cdb | ||
|
|
ed9f883301 | ||
|
|
2386d46609 | ||
|
|
0da1dbdc14 | ||
|
|
dba635aac5 | ||
|
|
e1ca49c082 | ||
|
|
a47d5f4f12 | ||
|
|
e809133c25 | ||
|
|
4e39979852 | ||
|
|
ff99fae928 | ||
|
|
2e0353c725 | ||
|
|
3c9983ca12 | ||
|
|
d3412e7de6 | ||
|
|
908fa55674 | ||
|
|
4a26332133 | ||
|
|
6c17c5a3a2 | ||
|
|
1e6f54f055 | ||
|
|
59da873adf | ||
|
|
fb4b1827db | ||
|
|
2e2263ed58 | ||
|
|
ac18199295 | ||
|
|
4a954853f2 | ||
|
|
1973f1a1de | ||
|
|
d36a448aff | ||
|
|
29aa481d2d | ||
|
|
5c276c34ea | ||
|
|
1d2b654021 | ||
|
|
bdfb0bb14f | ||
|
|
6eeb03f159 | ||
|
|
f806416b84 | ||
|
|
7e64e60625 | ||
|
|
a44d4bccc2 | ||
|
|
b8c3e95068 | ||
|
|
85e55f3be2 | ||
|
|
eaee9e0e49 | ||
|
|
e232c42fc5 | ||
|
|
c7c043c898 | ||
|
|
57b718ff32 | ||
|
|
f2775c18a1 | ||
|
|
d1001aea1a | ||
|
|
39e33f3538 | ||
|
|
a786fa534a | ||
|
|
2a0831ab5a | ||
|
|
c6b61b0838 | ||
|
|
e73e480939 | ||
|
|
e5d53b4964 | ||
|
|
1521fde0bd | ||
|
|
eee0e81e6b | ||
|
|
c7be3d73b9 | ||
|
|
8574d50eaa | ||
|
|
2841942899 | ||
|
|
eb33c9ec81 | ||
|
|
56679d2a8e | ||
|
|
415e79b523 | ||
|
|
9a6fdaa42b | ||
|
|
ff54168a10 | ||
|
|
b2acd5fa75 | ||
|
|
33d0f1fd94 | ||
|
|
bb4f09316a | ||
|
|
b5976dc2ff | ||
|
|
47824ec1ac | ||
|
|
9223b089b1 | ||
|
|
6da9a1285c | ||
|
|
3b90e24ff8 | ||
|
|
93da60c03b | ||
|
|
a3e501a355 | ||
|
|
0cdbe998cb | ||
|
|
35b0c49da5 | ||
|
|
d43c9f006b | ||
|
|
01a6ae61d2 | ||
|
|
a25d9e7aa7 | ||
|
|
440020726f | ||
|
|
36ad643a5f | ||
|
|
2b6800f01b | ||
|
|
b7cb3ee89f | ||
|
|
c9490b9ef8 | ||
|
|
87210a3c15 | ||
|
|
a314d33dea | ||
|
|
4c305b7532 | ||
|
|
19cd6a1bdf | ||
|
|
dbd276dd9c | ||
|
|
28913e3a72 | ||
|
|
d0465eccf1 | ||
|
|
3aedec3e2d | ||
|
|
f931d60ac1 | ||
|
|
4b4b233377 | ||
|
|
ccc9907034 | ||
|
|
7c8146128e | ||
|
|
6c506ba556 | ||
|
|
5a7baa0a3b | ||
|
|
26520abe2b | ||
|
|
1397c3248d | ||
|
|
c40dc39b88 | ||
|
|
a2753bb27d | ||
|
|
1c5529691b | ||
|
|
8e5bdc6b2b | ||
|
|
c5342e4a15 | ||
|
|
47a1cce5fa | ||
|
|
ef60fcb9ec | ||
|
|
b45a1bd913 | ||
|
|
9a8a0932d0 | ||
|
|
ccd06ee534 | ||
|
|
5f5e33027a | ||
|
|
3fbdfa1583 | ||
|
|
624bc1de56 | ||
|
|
597100d108 | ||
|
|
418f97dbba | ||
|
|
6a38671e3c | ||
|
|
4f8542c133 | ||
|
|
0f2d49b2d6 | ||
|
|
39324d6b55 | ||
|
|
b1ad187d1b | ||
|
|
606f15fec5 | ||
|
|
34a2f1ad65 | ||
|
|
ce20fb535d | ||
|
|
bfe8323753 | ||
|
|
f2b86ad3ed | ||
|
|
42b498654b | ||
|
|
888a4e1c0e | ||
|
|
09a0eb26d1 | ||
|
|
bf9e8048ff | ||
|
|
1c6d2864b6 | ||
|
|
21ebc226e4 | ||
|
|
8e3fef9096 | ||
|
|
829dccc1b6 | ||
|
|
a65caa62b3 | ||
|
|
ddb6f9c5b4 | ||
|
|
e152ab6cf2 | ||
|
|
4085af023a | ||
|
|
686e401368 | ||
|
|
8ec079945a | ||
|
|
4d8dbefc85 | ||
|
|
746c05081d | ||
|
|
2fdfa64b13 | ||
|
|
f406cfcebb | ||
|
|
80f35725e6 | ||
|
|
6741d99681 | ||
|
|
ad4ec14778 | ||
|
|
2bf16ad88b | ||
|
|
6c56581c15 | ||
|
|
ce89d9fbe0 | ||
|
|
9517191e35 | ||
|
|
e298f74310 | ||
|
|
c550c1373e | ||
|
|
6a1bb88c3b | ||
|
|
a585e96fdf | ||
|
|
e78ef493a1 | ||
|
|
c139378694 | ||
|
|
e51827bfcd | ||
|
|
d0eb3e760f | ||
|
|
9cfa3e5002 | ||
|
|
bb76621b7f | ||
|
|
c6746f6f77 | ||
|
|
714d453aa0 | ||
|
|
915c8d7afa | ||
|
|
1bf51a047e | ||
|
|
a7b2fe14b9 | ||
|
|
82278037ec | ||
|
|
0b592f86d0 | ||
|
|
ee4e003c2d | ||
|
|
b2904bc6fb | ||
|
|
67b94cf52e | ||
|
|
c308d2c9dd | ||
|
|
e36370b080 | ||
|
|
d0f005a6f1 | ||
|
|
a461fa9137 | ||
|
|
22e969fb59 | ||
|
|
34527c8395 | ||
|
|
012f4d68bc | ||
|
|
da8d0f3265 | ||
|
|
9b57d18143 | ||
|
|
9f0ac51e06 | ||
|
|
74b9e51cf7 | ||
|
|
ec4d8d7f9c | ||
|
|
011a19772d | ||
|
|
d8cef76592 | ||
|
|
856b0f4469 | ||
|
|
ca76d41f9a | ||
|
|
f7ba6e1121 | ||
|
|
df19dbc0de | ||
|
|
3b3e64e752 | ||
|
|
724283433a | ||
|
|
aea19b93b1 | ||
|
|
73020a711d | ||
|
|
7e62e671e3 | ||
|
|
2e845ac0ab | ||
|
|
2df41e52ed | ||
|
|
bf8e93f581 | ||
|
|
f5eec3283c | ||
|
|
223161c7d6 | ||
|
|
b8ba6e4827 | ||
|
|
15c8259ac2 | ||
|
|
720492932f | ||
|
|
a08a0fb084 | ||
|
|
827fbaac1f | ||
|
|
0de3175775 | ||
|
|
008b807803 | ||
|
|
08b53410b4 | ||
|
|
ce06b2cb18 | ||
|
|
d4d5a04487 | ||
|
|
297224bd31 | ||
|
|
9dc835c60c | ||
|
|
67a96bc2f3 | ||
|
|
92d98c8d59 | ||
|
|
7becb19da4 | ||
|
|
deee654426 | ||
|
|
552fd52bd5 | ||
|
|
f58fb3e556 | ||
|
|
ff5bf16111 | ||
|
|
8d22059462 | ||
|
|
91d144f5f7 | ||
|
|
c894b1d335 | ||
|
|
d30a7bf6a1 | ||
|
|
7f18cbf7d5 | ||
|
|
e7741b3d1f | ||
|
|
9d26cd787a | ||
|
|
561bc42259 | ||
|
|
649d5f56c9 | ||
|
|
3e4ca5ceb9 | ||
|
|
81b53112c1 | ||
|
|
fe1a635c14 | ||
|
|
521e6033a2 | ||
|
|
36b033c2f7 | ||
|
|
dc9bce915b | ||
|
|
37a0e733ad | ||
|
|
b3005eeec7 | ||
|
|
b21bfda7f6 | ||
|
|
a2f9662a99 | ||
|
|
274254b12e | ||
|
|
087e17511c | ||
|
|
139e868cce | ||
|
|
b6ec84e94d | ||
|
|
2b09392261 | ||
|
|
2132384736 | ||
|
|
8798f467e1 | ||
|
|
c9a7cfafd0 | ||
|
|
9d9585ecba | ||
|
|
3204bcb7a0 | ||
|
|
645a5206ec | ||
|
|
28baf4de5e | ||
|
|
04ddcd9489 | ||
|
|
a82f392cac | ||
|
|
15d308864c | ||
|
|
9af3a0d725 | ||
|
|
d2494fb97d | ||
|
|
c32ef61d3a | ||
|
|
6020d06038 | ||
|
|
b2b1b1cb47 | ||
|
|
023a605cad | ||
|
|
a61e31f646 | ||
|
|
0361436a66 | ||
|
|
c800670f39 | ||
|
|
7fa42cbdd3 | ||
|
|
f3f39e283e | ||
|
|
c9e1159c42 | ||
|
|
f9f8f7aebd | ||
|
|
ee3941b86a | ||
|
|
f9499e4b2f | ||
|
|
0ebd7e6a58 | ||
|
|
a100abc3b6 | ||
|
|
94f6945d5a | ||
|
|
870d57bfa5 | ||
|
|
a02f26f0eb | ||
|
|
442d771a7a | ||
|
|
242e11eefc | ||
|
|
97edd21784 | ||
|
|
2309656169 | ||
|
|
22ea322fc5 | ||
|
|
17f793a4bb | ||
|
|
2f16e17ed5 | ||
|
|
27ae79f50d | ||
|
|
eb18c0a505 | ||
|
|
103d1adc46 | ||
|
|
db661f124f | ||
|
|
4c3802878a | ||
|
|
23e560b638 | ||
|
|
b82a2ee735 | ||
|
|
7fbbae8cd4 | ||
|
|
83b6820458 | ||
|
|
b80d96d9c7 | ||
|
|
d8e69360b9 | ||
|
|
83ae9b66a3 | ||
|
|
3a7b4fd404 | ||
|
|
578557f3d7 | ||
|
|
92b7668067 | ||
|
|
95f9170736 | ||
|
|
52be2230cb | ||
|
|
8d2cecfd5c | ||
|
|
4738150c55 | ||
|
|
88b4847ba9 | ||
|
|
667fae9442 | ||
|
|
f532eb708e | ||
|
|
94a24b3f57 | ||
|
|
62fadc8200 | ||
|
|
1370625568 | ||
|
|
8eed440840 | ||
|
|
1aed2b9a43 | ||
|
|
6ec31cc7f9 | ||
|
|
6d3b3e6de2 | ||
|
|
10fd2b5b5a | ||
|
|
d020acca22 | ||
|
|
e704733743 | ||
|
|
79522194f1 | ||
|
|
aadc9e64d8 | ||
|
|
2eea258b9b | ||
|
|
796e019f12 | ||
|
|
036bd999fd | ||
|
|
1fdb5373c9 | ||
|
|
0ebe5ff55a | ||
|
|
ef610ab7de | ||
|
|
68454492e9 | ||
|
|
4593b652c4 | ||
|
|
2670915583 | ||
|
|
fb41535c69 | ||
|
|
b0890844e4 | ||
|
|
8239226208 | ||
|
|
49be1638ba | ||
|
|
e188047cfb | ||
|
|
afd01cc40c | ||
|
|
ee0b620d33 | ||
|
|
2d0c0d2aad | ||
|
|
e811236973 | ||
|
|
12078f07c6 | ||
|
|
c9ef3fa1d7 | ||
|
|
0a724e5a73 | ||
|
|
2c9e4aacf2 | ||
|
|
41486d44ee | ||
|
|
7fb326245e | ||
|
|
542645d876 | ||
|
|
9a5cf69251 | ||
|
|
b322633e8b | ||
|
|
487cd384a5 | ||
|
|
0b0055e0e2 | ||
|
|
57a9b313c4 | ||
|
|
81e8b0eb18 | ||
|
|
26da7d5e81 | ||
|
|
76b85ed554 | ||
|
|
d954568f7b | ||
|
|
92a2723815 | ||
|
|
4064fedf55 | ||
|
|
a27ad2e353 | ||
|
|
13aa3914ac | ||
|
|
20ba998383 | ||
|
|
612e718b91 | ||
|
|
cfc4fc354b | ||
|
|
61208817df | ||
|
|
bb58b5cbed | ||
|
|
eed8025ae2 | ||
|
|
a57d2c0e14 | ||
|
|
90afa5b3e1 | ||
|
|
a7c0cd405d | ||
|
|
bed22468ba | ||
|
|
93d4c3c47c | ||
|
|
0c190651cb | ||
|
|
3cf56ff7d5 | ||
|
|
f3f16c96e8 | ||
|
|
70a4ab863d | ||
|
|
d62ccfd234 | ||
|
|
370793a005 | ||
|
|
292c0356f1 | ||
|
|
4d6bcf27e9 | ||
|
|
130e417451 | ||
|
|
e6f2d9de86 | ||
|
|
415a1bffac | ||
|
|
798895d78d | ||
|
|
e24529d8d1 | ||
|
|
454436905c | ||
|
|
26feee1ed5 | ||
|
|
005b3356f2 | ||
|
|
4ac052c10b | ||
|
|
f1717f8319 | ||
|
|
cb000549bd | ||
|
|
c8d4d39068 | ||
|
|
8539298008 | ||
|
|
f7395b7d7c | ||
|
|
86758dcb70 | ||
|
|
f7c3b7c664 | ||
|
|
616d1a2c88 | ||
|
|
9f2b02d98e | ||
|
|
fc193b2d24 | ||
|
|
01d8d0dbec | ||
|
|
2712aa2ae2 | ||
|
|
7dcefa6bee | ||
|
|
70e3528809 | ||
|
|
c7780a2708 | ||
|
|
784d458e62 | ||
|
|
0e1e7875a8 | ||
|
|
03c6614aed | ||
|
|
26f33b9581 | ||
|
|
0ba66d4d47 | ||
|
|
5bc0b24913 | ||
|
|
da87fd7747 | ||
|
|
49b1608af8 | ||
|
|
8bc6d7af8c | ||
|
|
479690f011 | ||
|
|
2510f21c81 | ||
|
|
d6e55dffb0 | ||
|
|
7df0e22664 | ||
|
|
8939fe0b83 | ||
|
|
c4cbfd352e | ||
|
|
aea39080d6 | ||
|
|
658a4dbb2e | ||
|
|
607e16025d | ||
|
|
ecacdfaf49 | ||
|
|
d8719ede12 | ||
|
|
11834a4186 | ||
|
|
81381fac75 | ||
|
|
9fa91aab36 | ||
|
|
2897b66a1c | ||
|
|
ef77ac11ef | ||
|
|
382c857a4b | ||
|
|
c4bfbd3f94 | ||
|
|
f4c1a125e7 | ||
|
|
010e47634b | ||
|
|
086fda5f24 | ||
|
|
27b39c29b8 | ||
|
|
680ac9825a | ||
|
|
cfb3d14028 | ||
|
|
9f6bf62efc | ||
|
|
60ecceafec | ||
|
|
7b0b53a96d | ||
|
|
87d0e1d886 | ||
|
|
e84bc3754f | ||
|
|
26bab84d04 | ||
|
|
efea6136e7 | ||
|
|
c56026c18a | ||
|
|
375b76d46a | ||
|
|
1d9504d0f0 | ||
|
|
780b1f8acc | ||
|
|
b369fa2c7b | ||
|
|
7c96eb0819 | ||
|
|
febc3b7936 | ||
|
|
1e511dd655 | ||
|
|
71eafe436a | ||
|
|
66a5312908 | ||
|
|
14f4cdbbe5 | ||
|
|
e479e84bde | ||
|
|
19e3a2ecc4 | ||
|
|
d2290d509f | ||
|
|
434d4298df | ||
|
|
9b6c5741de | ||
|
|
e09a3b8cbf | ||
|
|
c3b9d3be8c | ||
|
|
0742e18edd | ||
|
|
cb59c2489b | ||
|
|
3158b544d5 | ||
|
|
e80270d1fa | ||
|
|
99b37f24bb | ||
|
|
fdbdcc4130 | ||
|
|
08104966b2 | ||
|
|
1c99133177 | ||
|
|
28780dc67e | ||
|
|
423ce54d8a | ||
|
|
73bab73db6 | ||
|
|
3e90471fa2 | ||
|
|
7f2db8a9a7 | ||
|
|
d5fc147ffd | ||
|
|
77ffd7bbf9 | ||
|
|
42775d3477 | ||
|
|
6932b8c378 | ||
|
|
fb9ac01533 | ||
|
|
cff641ef80 | ||
|
|
373abf1b24 | ||
|
|
f668596667 | ||
|
|
adf14f79e0 | ||
|
|
cd31799c4c | ||
|
|
93e0e815d7 | ||
|
|
05275c8938 | ||
|
|
19475db7dd | ||
|
|
63c005a3de | ||
|
|
4e984f5532 | ||
|
|
5cb79a4c1a | ||
|
|
083cb9cc9a | ||
|
|
f25903905c | ||
|
|
16216147bc | ||
|
|
8069e5568b | ||
|
|
0682df3801 | ||
|
|
b0800c7e38 | ||
|
|
e5c4391900 | ||
|
|
e33e7446ee | ||
|
|
f8c3e6adf3 | ||
|
|
de0eca7890 | ||
|
|
20ca17fcac | ||
|
|
6cbd1963bf | ||
|
|
2d292000a8 | ||
|
|
6e75f94505 | ||
|
|
e748bcb291 | ||
|
|
409a8c2a77 | ||
|
|
4cc09e49ac | ||
|
|
665b5363e3 | ||
|
|
5a05f40428 | ||
|
|
007b3644be | ||
|
|
93eba6ef68 | ||
|
|
0c7e593370 | ||
|
|
6b7df8fd50 | ||
|
|
783869b45c | ||
|
|
4919b9cb48 | ||
|
|
3f81e0ab82 | ||
|
|
96f2e150a1 | ||
|
|
a38af3a31c | ||
|
|
f29023f11d | ||
|
|
3a93600195 | ||
|
|
974373dc40 | ||
|
|
62de48e4ea | ||
|
|
1031af3dbc | ||
|
|
36aacba237 | ||
|
|
2530c577dc | ||
|
|
54b7920697 | ||
|
|
9ead37845e | ||
|
|
bff9141a4f | ||
|
|
f11b2f6782 | ||
|
|
b5054a7a7f | ||
|
|
bf61ed6310 | ||
|
|
e52756bf9f | ||
|
|
b0350d9412 | ||
|
|
824fcf1736 | ||
|
|
77d4895cf0 | ||
|
|
0440e6916b | ||
|
|
4c63a4046a | ||
|
|
624886bf5a | ||
|
|
545f90ba12 | ||
|
|
f97698858a | ||
|
|
424f99cea4 | ||
|
|
b13feea790 | ||
|
|
883b79f05d | ||
|
|
6dad4de8e3 | ||
|
|
daeff92065 | ||
|
|
56b3799a7a | ||
|
|
3244c57417 | ||
|
|
f1fcaccbaf | ||
|
|
4ef26f99fa | ||
|
|
909dca8f9a | ||
|
|
82fea3a989 | ||
|
|
caeb5effdc | ||
|
|
1e73b3d926 | ||
|
|
a0ad96acd3 | ||
|
|
b5b4ec0ed7 | ||
|
|
c1d61ddef6 | ||
|
|
c904c81d1c | ||
|
|
2c12c8e2b6 | ||
|
|
de88493acb | ||
|
|
421dbc28bd | ||
|
|
019bfb8a6b | ||
|
|
09f702879e | ||
|
|
c853c64a8f | ||
|
|
aeb5a3a261 | ||
|
|
6187fd2497 | ||
|
|
d58bf2f057 | ||
|
|
c035021cd6 | ||
|
|
561a5b2b2e | ||
|
|
2bfdd033eb | ||
|
|
938ae7dc8e | ||
|
|
3f7bd98174 | ||
|
|
dc67e8bbde | ||
|
|
d0cef987fe | ||
|
|
af4131bb4d | ||
|
|
d6e0638cd9 | ||
|
|
030b6f9720 | ||
|
|
d9634030d4 | ||
|
|
79f5502b8e | ||
|
|
179781dfee | ||
|
|
413fd61900 | ||
|
|
9f605cae29 | ||
|
|
c557279607 | ||
|
|
aa315d7c97 | ||
|
|
080b25e30c | ||
|
|
75920e0a09 | ||
|
|
f46ec05a3a | ||
|
|
a87969d85f | ||
|
|
3231ac15c9 | ||
|
|
627c75b6b9 | ||
|
|
823a845752 | ||
|
|
f28ac0c037 | ||
|
|
727e02b816 | ||
|
|
a7c8c08183 | ||
|
|
b872f90365 | ||
|
|
8c3446fb1f | ||
|
|
504222b564 | ||
|
|
108fcd5691 | ||
|
|
39724517cc | ||
|
|
90353bdd0f | ||
|
|
a42a251256 | ||
|
|
0e3dab081a | ||
|
|
e78dc4892f | ||
|
|
5cce1af3d7 | ||
|
|
92156e0520 | ||
|
|
9c13e48b62 | ||
|
|
bfc840d1a7 | ||
|
|
8bc7349635 | ||
|
|
ddbfc5c57a | ||
|
|
b1e97266e3 | ||
|
|
bb8c456a04 | ||
|
|
c5c9387226 | ||
|
|
ee6ff7daaa | ||
|
|
29c93d2b0d | ||
|
|
bb98627d2b | ||
|
|
3060866586 | ||
|
|
eacf121f78 | ||
|
|
c39535fdd0 | ||
|
|
c3311df2a8 | ||
|
|
99af52ac2f | ||
|
|
5a17314b2c | ||
|
|
05d974578d | ||
|
|
0d585a4d98 | ||
|
|
b968b4a9a4 | ||
|
|
07281f050c | ||
|
|
9c3757fb0c | ||
|
|
f5d5e59040 | ||
|
|
70880c066f | ||
|
|
5508c70f3a | ||
|
|
f100982b04 | ||
|
|
c8db2a85c6 | ||
|
|
3b086d130f | ||
|
|
cb8394fb07 | ||
|
|
a401f19ca1 | ||
|
|
444fd41d58 | ||
|
|
e3ccc6d558 | ||
|
|
8ed20e35ca | ||
|
|
bd16b485be | ||
|
|
9783de7464 | ||
|
|
9139fa7ec4 | ||
|
|
ba45539375 | ||
|
|
bb99abdebd | ||
|
|
89878dddf6 | ||
|
|
7118b70c1c | ||
|
|
a48539230c | ||
|
|
075ac9e280 | ||
|
|
d43f65366b | ||
|
|
052ab2d9bc | ||
|
|
74f7c93a6d | ||
|
|
36c3cac484 | ||
|
|
de5cd8e873 | ||
|
|
c9aa3bf62c | ||
|
|
2fc014af03 | ||
|
|
8abd010d08 | ||
|
|
c117ef3000 | ||
|
|
543407920d | ||
|
|
3dbdb541ed | ||
|
|
8f24858af8 | ||
|
|
f2667bfcae | ||
|
|
4bbafe13e6 | ||
|
|
f33bac5b59 | ||
|
|
85d2a33620 | ||
|
|
643f4fddf9 | ||
|
|
8ec8118d39 | ||
|
|
dcabf2738a | ||
|
|
a4fbe65259 | ||
|
|
83d76a0781 | ||
|
|
59fbc308b0 | ||
|
|
7266410840 | ||
|
|
e4312ed5cc | ||
|
|
1642a7714f | ||
|
|
d3d960112c | ||
|
|
c62be68bda | ||
|
|
a1befc78e0 | ||
|
|
eadc4788ce | ||
|
|
16d2084af2 | ||
|
|
6232e10d20 | ||
|
|
fdb8e0e2e5 | ||
|
|
7b803d855f | ||
|
|
3116fde51c | ||
|
|
714c542da9 | ||
|
|
68755757af | ||
|
|
1c7e272ac3 | ||
|
|
ba20766ec3 | ||
|
|
1e8bc50da5 | ||
|
|
258cbe7029 | ||
|
|
c9068f5820 | ||
|
|
31f66c7142 | ||
|
|
5e1d49cc7e | ||
|
|
495a72d167 | ||
|
|
e0eabb8d71 | ||
|
|
4c2c804d0d | ||
|
|
733c375afe | ||
|
|
5dce6e416e | ||
|
|
b54fe820cb | ||
|
|
201bf29429 | ||
|
|
b37c3d25a6 | ||
|
|
408417bbda | ||
|
|
3d8c8a36dd | ||
|
|
ca1b9d35dd | ||
|
|
2841bfe819 | ||
|
|
1694b10690 | ||
|
|
ec6df96a85 | ||
|
|
3dfd95b1dd | ||
|
|
5ba11f39d1 | ||
|
|
fa72a54c1a | ||
|
|
ef2ef300f0 | ||
|
|
ac20fe25e2 | ||
|
|
5a7ae77311 | ||
|
|
757ce4385b | ||
|
|
223f501c36 | ||
|
|
fd3bf981dc | ||
|
|
fa8473a224 | ||
|
|
c703429288 | ||
|
|
d0ff8d6175 | ||
|
|
51ee822794 | ||
|
|
6f6e66dacf | ||
|
|
163775df94 | ||
|
|
b9304d219b | ||
|
|
fd472403c1 | ||
|
|
a97d941835 | ||
|
|
7c56df2600 | ||
|
|
c38a63033a | ||
|
|
3f9e19a0fe | ||
|
|
b448df3843 | ||
|
|
2c927a34dd | ||
|
|
852310596b | ||
|
|
2d61ffa1b2 | ||
|
|
dfe4b66a43 | ||
|
|
ad657f6dda | ||
|
|
ce0e0b6038 | ||
|
|
7f0e840217 | ||
|
|
c35dd00923 | ||
|
|
6c9586a68d | ||
|
|
03bdf795b9 | ||
|
|
cd45a1976b | ||
|
|
a0bb84756c | ||
|
|
1af9dae3b9 | ||
|
|
f291f36f79 | ||
|
|
3e422f2538 | ||
|
|
0169174a87 | ||
|
|
fac7e1692c | ||
|
|
e457e1b316 | ||
|
|
c6cd0e4162 | ||
|
|
35a5e12840 | ||
|
|
241cd81cec | ||
|
|
85283222ee | ||
|
|
2e50fcb8c5 | ||
|
|
9817161260 | ||
|
|
d3b806c68d | ||
|
|
9a75bff2c1 | ||
|
|
b0917fbe87 | ||
|
|
ee417c9ff8 | ||
|
|
bbb33e6aa0 | ||
|
|
5d4981e7b7 | ||
|
|
66773e129b | ||
|
|
b5c363ba56 | ||
|
|
b8854771d8 | ||
|
|
56b59da0cf | ||
|
|
a5852b101b | ||
|
|
986bea25a6 | ||
|
|
d2413f03d9 | ||
|
|
d1dc1564f3 | ||
|
|
3ac5bf57fd | ||
|
|
1c983b781d | ||
|
|
b76357eb72 | ||
|
|
d31b6c527f | ||
|
|
c212028311 | ||
|
|
6d6b6e891b | ||
|
|
c71e77d13a | ||
|
|
4d63bddaef | ||
|
|
a27cc6ffa7 | ||
|
|
7e69be8c0d | ||
|
|
582b6f41a2 | ||
|
|
deb4855598 | ||
|
|
81a80c68fe | ||
|
|
8b03585d26 | ||
|
|
482b1c8f4c | ||
|
|
f5d19d43d4 | ||
|
|
d204f7635c | ||
|
|
cf42f33c69 | ||
|
|
bcc96cfec3 | ||
|
|
339831a1ed | ||
|
|
64bfbb0133 | ||
|
|
53ad952476 | ||
|
|
39364ee9a6 | ||
|
|
969e6501cb | ||
|
|
0a78217136 | ||
|
|
ed5e903eb4 | ||
|
|
4da57cbcb6 |
46
.appveyor.yml
Normal file
46
.appveyor.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
# http://www.appveyor.com/docs/appveyor-yml
|
||||
|
||||
# Set build version format here instead of in the admin panel.
|
||||
version: "{build}"
|
||||
|
||||
# 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: "4"
|
||||
- nodejs_version: "6"
|
||||
- nodejs_version: "8"
|
||||
- nodejs_version: "9"
|
||||
|
||||
# Finish on first failed build
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
# Install node, display versions, install dependencies
|
||||
install:
|
||||
- ps: Install-Product node 8
|
||||
- node --version && npm --version
|
||||
- git --version && svn --version
|
||||
- npm install -g yarn grunt
|
||||
- yarn
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
|
||||
# Post-install test scripts.
|
||||
test_script:
|
||||
- cmd: npm run ci
|
||||
|
||||
# Make clone much faster
|
||||
shallow_clone: true
|
||||
|
||||
# Disable Visual Studio build and deploy
|
||||
build: off
|
||||
deploy: off
|
||||
|
||||
# Cache node modules, and refresh if package.json changes
|
||||
cache:
|
||||
- "%LOCALAPPDATA%\\Yarn"
|
||||
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[**.std]
|
||||
insert_final_newline = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_size = 2
|
||||
52
.eslintrc
Normal file
52
.eslintrc
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
"no-bitwise": 0,
|
||||
"curly": 0,
|
||||
"eqeqeq": 0,
|
||||
"guard-for-in": 0,
|
||||
"no-use-before-define": 0,
|
||||
"no-caller": 2,
|
||||
"no-new": 2,
|
||||
"no-plusplus": 0,
|
||||
"no-undef": 2,
|
||||
"no-unused-vars": 0,
|
||||
"strict": 0,
|
||||
"semi": 0,
|
||||
"comma-spacing": 2,
|
||||
"quote-props": [2, "consistent", { "keywords": true }],
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
"indent": [2, 4],
|
||||
"no-cond-assign": [ 2, "except-parens" ],
|
||||
"no-debugger": 2,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-unreachable": 2,
|
||||
"valid-typeof": 2,
|
||||
"no-fallthrough": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-eq-null": 0,
|
||||
"no-eval": 0,
|
||||
"no-unused-expressions": 0,
|
||||
"block-scoped-var": 0,
|
||||
"no-iterator": 0,
|
||||
"no-loop-func": 2,
|
||||
"no-script-url": 0,
|
||||
"no-shadow": 0,
|
||||
"no-new-func": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-invalid-this": 0,
|
||||
"space-before-blocks": [2, "always"],
|
||||
"space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
|
||||
"space-infix-ops": 2,
|
||||
"keyword-spacing": 2,
|
||||
"new-parens": 2,
|
||||
"no-multiple-empty-lines": [2, { max: 2}],
|
||||
"eol-last": 2,
|
||||
"no-trailing-spaces": 2
|
||||
}
|
||||
}
|
||||
44
.github/ISSUE_TEMPLATE.md
vendored
Normal file
44
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
<!--
|
||||
|
||||
If you are reporting a new issue, make sure that we do not have any duplicates.
|
||||
You can ensure this by searching the issue list for this repository.
|
||||
|
||||
You are welcome to open issues to discuss important general topics concerning Bower.
|
||||
However for support questions, please consider using http://stackoverflow.com or
|
||||
asking for help in our Discord channel: https://discordapp.com/invite/0fFM7QF0KpZaDeN9
|
||||
|
||||
# BUG REPORT
|
||||
|
||||
Use the commands below to provide key information to reproduce:
|
||||
You do NOT have to include this information if this is a FEATURE REQUEST OR DISCUSSION
|
||||
|
||||
For more information about reporting bugs, see:
|
||||
https://github.com/bower/bower/wiki/Report-a-Bug
|
||||
|
||||
-->
|
||||
|
||||
**Output of `bower -v && npm -v && node -v`:**
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
```
|
||||
|
||||
**Additional environment details (proxy, private registry, etc.):**
|
||||
|
||||
|
||||
|
||||
**Steps to reproduce the issue:**
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Describe the results you received:**
|
||||
|
||||
|
||||
|
||||
**Describe the results you expected:**
|
||||
|
||||
|
||||
|
||||
**Additional information:**
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,3 +1,14 @@
|
||||
node_modules
|
||||
components
|
||||
.DS_Store
|
||||
/node_modules
|
||||
/npm-debug.log
|
||||
|
||||
/test/assets/package-*/
|
||||
/test/assets/temp-*/
|
||||
/test/reports
|
||||
/test/tmp/
|
||||
|
||||
/bower.json
|
||||
/component.json
|
||||
/bower_components
|
||||
/test/sample
|
||||
!/test/sample/bower.json
|
||||
/npm-shrinkwrap.json
|
||||
|
||||
42
.travis.yml
42
.travis.yml
@@ -1,3 +1,43 @@
|
||||
sudo: false
|
||||
|
||||
language: node_js
|
||||
|
||||
# Use node 8 for build
|
||||
node_js:
|
||||
- 0.8
|
||||
- "8"
|
||||
|
||||
# Then test with specific node version
|
||||
env:
|
||||
- TEST_NODE_VERSION="0.10"
|
||||
- TEST_NODE_VERSION="0.12"
|
||||
- TEST_NODE_VERSION="4"
|
||||
- TEST_NODE_VERSION="6"
|
||||
- TEST_NODE_VERSION="8"
|
||||
- TEST_NODE_VERSION="9"
|
||||
|
||||
before_install:
|
||||
- node --version
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.5.1
|
||||
- export PATH=$HOME/.yarn/bin:$PATH
|
||||
|
||||
cache:
|
||||
yarn: true
|
||||
|
||||
install:
|
||||
- yarn
|
||||
- nvm install $TEST_NODE_VERSION
|
||||
- npm install -g grunt
|
||||
|
||||
os:
|
||||
- osx
|
||||
- linux
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- os: osx
|
||||
|
||||
script:
|
||||
- nvm use $TEST_NODE_VERSION
|
||||
- node --version && npm --version && git --version && svn --version | head -n 1
|
||||
- grunt travis
|
||||
|
||||
633
CHANGELOG.md
633
CHANGELOG.md
@@ -1,53 +1,620 @@
|
||||
# Changelog
|
||||
|
||||
## 0.8.2 - 2013-02-26
|
||||
## 1.8.0 - 2016-11-07
|
||||
|
||||
- Fix some errors in windows related, had to downgrade `rimraf` ([#274](https://github.com/twitter/bower/issues/274))
|
||||
- Prevent duplicate package names in error summaries ([#277](https://github.com/twitter/bower/issues/277))
|
||||
- Download tar archives from GitHub when possible (#2263)
|
||||
- Change default shorthand resolver for github from `git://` to `https://`
|
||||
- Fix ssl handling by not setting GIT_SSL_NO_VERIFY=false (#2361)
|
||||
- Allow for removing components with url instead of name (#2368)
|
||||
- Show in warning message location of malformed bower.json (#2357)
|
||||
- Improve handling of non-semver versions in git resolver (#2316)
|
||||
- Fix handling of cached releases pluginResolverFactory (#2356)
|
||||
- Allow to type the entire version when conflict occured (#2243)
|
||||
- Allow `owner/reponame` shorthand for registering components (#2248)
|
||||
- Allow single-char repo names and package names (#2249)
|
||||
- Make `bower version` no longer honor `version` in bower.json (#2232)
|
||||
- Add `postinstall` hook (#2252)
|
||||
- Allow for `@` instead of `#` for `install` and `info` commands (#2322)
|
||||
- Upgrade all bundled modules
|
||||
|
||||
## 1.7.9 - 2016-04-05
|
||||
|
||||
- Show warnings for invalid bower.json fields
|
||||
- Update bower-json
|
||||
- Less strict validation on package name (allow spaces, slashes, and "@")
|
||||
|
||||
## 1.7.8 - 2016-04-04
|
||||
|
||||
- Don't ask for git credentials in non-interactive session, fixes #956 #1009
|
||||
- Prevent swallowing exceptions with programmatic api, fixes #2187
|
||||
- Update graceful-fs to 4.x in all dependences, fixes nodejs/node#5213
|
||||
- Resolve pluggable resolvers using cwd and fallback to global modules, fixes #1919
|
||||
- Upgrade handlebars to 4.0.5, closes #2195
|
||||
- Replace all % chatacters in defined scripts, instead of only first one, fixes #2174
|
||||
- Update opn package to fix issues with "bower open" command on Windows
|
||||
- Update bower-config
|
||||
- Do not interpolate environment variables in script hooks, fixes bower/config#47
|
||||
- Update bower-json
|
||||
- Validate package name more strictly and allow only latin letters, dots, dashes and underscores
|
||||
- Add support for "save" and "save-exact" in .bowerrc, #2161
|
||||
|
||||
## 1.7.7 - 2016-01-27
|
||||
|
||||
Revert locations of all files while still packaging `node_modules`.
|
||||
|
||||
It's because people are depending on internals of bower, like
|
||||
`bower/lib/renderers/StandardRenderer`. We want to preserve this
|
||||
implicit contract, but we discourage it. The only official way
|
||||
to use bower programmatically is through `require('bower')`.
|
||||
|
||||
## 1.7.6 - 2016-01-27
|
||||
|
||||
- Revert location of "bin/bower" as developers are using it directly ([#2157](https://github.com/bower/bower/issues/2157))
|
||||
Note: Correctly, you should use an alias created in `npm bin --global`.
|
||||
|
||||
## 1.7.5 - 2016-01-26
|
||||
|
||||
- Remove analytics from Bower, fixes ([#2150](https://github.com/bower/bower/pull/2150))
|
||||
- Default to ^ operator on `bower install --save` ([#2145](https://github.com/bower/bower/pull/2145))
|
||||
- Support absolute path in .bowerrc directory option ([#2130](https://github.com/bower/bower/pull/2130))
|
||||
- Display user's name upon `bower login` command ([#2133](https://github.com/bower/bower/pull/2133))
|
||||
- Decompress gzip files ([#2092](https://github.com/bower/bower/pull/2092))
|
||||
- Prevent name clashes in package extraction ([#2102](https://github.com/bower/bower/pull/2102))
|
||||
- When strictSsl is false, set GIT_SSL_NO_VERIFY=true ([#2129](https://github.com/bower/bower/issues/2129))
|
||||
- Distribute bower with npm@3 for better Windows support ([#2146](https://github.com/bower/bower/issues/2146))
|
||||
- Update request to 2.67.0 and fs-write-stream-atomic to 1.0.8
|
||||
- Documentation improvements
|
||||
|
||||
## 1.7.4 - 2016-01-21
|
||||
|
||||
Unpublished because of issue with npm distribution:
|
||||
https://github.com/npm/npm/issues/11227
|
||||
|
||||
## 1.7.3 - 2016-01-20
|
||||
|
||||
Unpublished because of issue with npm distribution:
|
||||
https://github.com/npm/npm/issues/11227
|
||||
|
||||
## 1.7.2 - 2015-12-31
|
||||
|
||||
- Lock "fs-write-stream-atomic" to 1.0.5
|
||||
|
||||
## 1.7.1 - 2015-12-11
|
||||
|
||||
- Rollback "Add `bower update --save` functionality", it causes issues and needs more testing
|
||||
- Fix backward-compatibility of `bower search --json` ([#2066](https://github.com/bower/bower/issues/2066))
|
||||
- Ignore prerelease versions from `bower info` output
|
||||
- Update update-notifier to 0.6.0
|
||||
- Better formatting of help messages (https://github.com/bower/bower/commit/de3e1089da80f47ea3667c5ab80d301cddfd8c3e)
|
||||
- Add help menu for update `--save` and `update --save-dev` (https://github.com/bower/bower/commit/612aaa88eb4d4b268b2d8665c338ac086af3a5b0)
|
||||
|
||||
## 1.7.0 - 2015-12-07
|
||||
|
||||
- Add `bower update --save` functionality ([#2035](https://github.com/bower/bower/issues/2035))
|
||||
- `bower search` shows help message when no package name is specified ([#2066](https://github.com/bower/bower/issues/2066))
|
||||
- Update only those packages that are explicitly requested by the user. Related Issues
|
||||
- [#256](https://github.com/bower/bower/issues/256)
|
||||
- [#924](https://github.com/bower/bower/issues/924)
|
||||
- [#1770](https://github.com/bower/bower/issues/1770)
|
||||
- Allow for @ in username for SVN on windows ([#1650](https://github.com/bower/bower/issues/1650))
|
||||
- Update bower config
|
||||
- Loads the .bowerrc file from the cwd specified on the command line
|
||||
- Allow the use of environment variables in .bowerrc ([#41](https://github.com/bower/config/issues/41))
|
||||
- Allow for array notation in ENV variables ([#44](https://github.com/bower/config/issues/44))
|
||||
|
||||
## 1.6.9 - 2015-12-04
|
||||
|
||||
- Change git version of fs-write-stream-atomic back to npm version ([#2079](https://github.com/bower/bower/issues/2079))
|
||||
|
||||
## 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 in some edge cases ([#806](https://github.com/bower/bower/issues/806))
|
||||
|
||||
|
||||
## 1.2.1 - 2013-08-19
|
||||
|
||||
- Fix bower throwing on non-semver targets ([#800](https://github.com/bower/bower/issues/800))
|
||||
|
||||
|
||||
## 1.2.0 - 2013-08-19
|
||||
|
||||
- __Bower no longer installs a pre-release version by default, that is, if no version/range is specified__ ([#782](https://github.com/bower/bower/issues/782))
|
||||
- __`bower info <package>` will now show the latest `<package>` information along with the available versions__ ([#759](https://github.com/bower/bower/issues/759))
|
||||
- __`bower link` no longer requires an elevated user on Windows in most cases__ ([#472](https://github.com/bower/bower/issues/472))
|
||||
- __Init command now prompts for the whole `bower.json` spec properties, filling in default values for `author` and `homepage` based on `git` settings__ ([#693](https://github.com/bower/bower/issues/693))
|
||||
- Changes to endpoint sources in `bower.json` are now catched up by `bower install` and `bower update` ([#788](https://github.com/bower/bower/issues/788))
|
||||
- Allow semver ranges in `bower cache clean`, e.g. `bower cache clean jquery#<2.0.0` ([#688](https://github.com/bower/bower/issues/688))
|
||||
- Normalize `bower list --paths` on Windows ([#279](https://github.com/bower/bower/issues/279))
|
||||
- Multiple mains are now correctly outputted as an array in `bower list --paths` ([#784](https://github.com/bower/bower/issues/784))
|
||||
- Add `--relative` option to `bower list --json` so that Bower outputs relative paths instead of absolute ([#714](https://github.com/bower/bower/issues/714))
|
||||
- `bower list --paths` now outputs relative paths by default; can be turned off with `--no-relative` ([#785](https://github.com/bower/bower/issues/785))
|
||||
- Bower no longer fails if `symlinks` to files are present in the `bower_components` folder ([#783](https://github.com/bower/bower/issues/783) and [#791](https://github.com/bower/bower/issues/791))
|
||||
- Disable git templates/hooks when running `git` ([#761](https://github.com/bower/bower/issues/761))
|
||||
- Add instructions to setup git workaround for proxies when execution of `git` fails ([#250](https://github.com/bower/bower/issues/250))
|
||||
- Ignore `component.json` if it looks like a component(1) file ([#556](https://github.com/bower/bower/issues/556))
|
||||
- Fix multi-user usage on bower when it creates temporary directories to hold some files
|
||||
- Fix prompting causing an invalid JSON output when running commands with `--json`
|
||||
- When running Bower commands programmatically, prompting is now disabled by default (see the updated 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.
|
||||
|
||||
|
||||
## 1.1.2 - 2013-08-10
|
||||
|
||||
- Detect and fallback if the git server does not support `--depth=1` when cloning ([#747](https://github.com/bower/bower/issues/747))
|
||||
|
||||
|
||||
## 1.1.1 - 2013-08-08
|
||||
|
||||
- Fix silent fail when spawning child processes in some edge cases ([#722](https://github.com/bower/bower/issues/722))
|
||||
- Fix `home` command not guessing the correct URL for `GitHub` ssh endpoints (requires `bower cache-clean`)
|
||||
- Fix bower not correctly filtering packages with symlinks in some cases ([#730](https://github.com/bower/bower/issues/730))
|
||||
- Fix multi-user usage on bower when it falls back to create a `/tmp/bower` folder ([#743](https://github.com/bower/bower/issues/743))
|
||||
- Bower now sends a fake user agent when behind a proxy by default, so that corporate proxies do not block requests ([#698](https://github.com/bower/bower/issues/698))
|
||||
- Bower now translates GitHub public `git://` URLs to `git@` when behind a proxy ([#731](https://github.com/bower/bower/issues/731))
|
||||
- Minor improvements to the CLI output on small terminals
|
||||
- Minor programmatic usage improvements
|
||||
- Minor help usage fixes
|
||||
|
||||
|
||||
## 1.1.0 - 2013-08-03
|
||||
|
||||
- __Fix `--save` and `--save-dev` not working correctly for the uninstall command in some situations__
|
||||
- __Attempting to register a package that declares `"private": true` in `bower.json` will result in an error ([#162](https://github.com/bower/bower/issues/162))__
|
||||
- __Fix retry strategy on download error that was causing some strange I/O errors__ ([#699](https://github.com/bower/bower/issues/699) and [#704](https://github.com/bower/bower/issues/704))
|
||||
- __`bower prune` now clears pruned packages dependencies if they are also extraneous__ ([#708](https://github.com/bower/bower/issues/708))
|
||||
- __`bower uninstall` now uninstalls uninstalled packages dependencies if they are not shared ([#609](https://github.com/bower/bower/issues/609))__
|
||||
- Fix `bower list` display the `incompatible` label even if they are compatible ([#710](https://github.com/bower/bower/issues/710))
|
||||
- Fix `bower cache clean` not working correctly when `package#non-semver` is specified
|
||||
- Implement no operation `completion` command to prevent weird output when hitting tab ([#691](https://github.com/bower/bower/issues/691))
|
||||
- Fix `bower info --help` ([#703](https://github.com/bower/bower/issues/703))
|
||||
- Add colorized output for `bower info <package>#<version>` ([#571](https://github.com/bower/bower/issues/571))
|
||||
- Added `bower ls` as an alias to `bower list`
|
||||
- Fix regression: do not create a json file when saving is required, warn instead
|
||||
- Ignore linked packages when reading dependencies in `bower init` ([#709](https://github.com/bower/bower/issues/709))
|
||||
- `bower list` is now able to (partially) reconstruct the dependency tree, even for dependencies not declared in `bower.json` ([#622](https://github.com/bower/bower/issues/622))
|
||||
|
||||
|
||||
## 1.0.3 - 2013-07-30
|
||||
|
||||
- Fix some changes not being saved to bower.json ([#685](https://github.com/bower/bower/issues/685))
|
||||
- Fix `bower info <package> <property>` not showing information related to property of the latest version of that package ([#684](https://github.com/bower/bower/issues/684))
|
||||
|
||||
|
||||
## 1.0.2 - 2013-07-30
|
||||
|
||||
- Fix severe bug originated from a wrong merge that caused conflict messages to not show up correctly
|
||||
|
||||
|
||||
## 1.0.1 - 2013-07-29
|
||||
|
||||
- Fix `bower register` going ahead even if the answer was `no` ([#644](https://github.com/bower/bower/issues/644))
|
||||
- Fix local endpoints with backslashes on Windows ([#2@endpoint-parser](https://github.com/bower/endpoint-parser/pull/2))
|
||||
- Fix usage of multiple registries in the registry-client ([#3@registry-client](https://github.com/bower/registry-client/pull/3) and [#2@registry-client](https://github.com/bower/registry-client/pull/2))
|
||||
- File extensions now have more priority than mime types when deciding if extraction is necessary ([#657](https://github.com/bower/bower/pull/657))
|
||||
- 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 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))
|
||||
- Add additional system information such as node version, bower version, OS version when an error occurs ([#670](https://github.com/bower/bower/issues/670))
|
||||
- `bower install` no longer overwrites `linked` packages unless it needs to ([#593](https://github.com/bower/bower/issues/593)).
|
||||
- All endpoint parts are now trimmed so that the Manager can better detect similar endpoints ([#3@endpoint-parser](https://github.com/bower/endpoint-parser/pull/3))
|
||||
- `bower register` now shows the server that will be used ([#647](https://github.com/bower/endpoint-parser/pull/647))
|
||||
|
||||
|
||||
## 1.0.0 - 2013-07-23
|
||||
|
||||
Total rewrite of bower.
|
||||
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
|
||||
|
||||
|
||||
- Clear architecture and separation of concerns
|
||||
- Much much faster
|
||||
- `--json` output for all commands
|
||||
- `--offline` usage for all commands, except `register`
|
||||
- Proper `install` and `update` commands, similar to `npm` in behaviour
|
||||
- Named endpoints when installing, e.g. `bower install backbone-amd=backbone#~1.0.0`
|
||||
- New interactive conflict resolution strategy
|
||||
- Prevent human errors when using `register`
|
||||
- New `home` command, similar to `npm`
|
||||
- New `cache list` command
|
||||
- New `prune` command
|
||||
- Many many general bug fixes
|
||||
|
||||
Non-backwards compatible changes:
|
||||
|
||||
- The value of the `json` property from .bowerrc is no longer used
|
||||
- `--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`.
|
||||
Additionally you may remove the `~/.bower` folder manually since it's no longer used.
|
||||
On Windows the folder is located in `AppData/bower`.
|
||||
|
||||
|
||||
## 0.10.0 - 2013-07-02
|
||||
|
||||
- __Allow specific commits to be targeted__ ([#275](https://github.com/bower/bower/issues/275))
|
||||
- __Change bower default folder from `components` to `bower_components`__ ([#434](https://github.com/bower/bower/issues/434))
|
||||
- __Support semver pre-releases and builds__ ([#188](https://github.com/bower/bower/issues/188))
|
||||
- Use `Content-Type` and `Content-Disposition` to guess file types, such as zip files ([#454](https://github.com/bower/bower/pull/454))
|
||||
- Fix bower failing silently when using an invalid version value in the bower.json file ([#439](https://github.com/bower/bower/issues/439))
|
||||
- Fix bower slowness when downloading after redirects ([#437](https://github.com/bower/bower/issues/437))
|
||||
- Detect and error out with a friendly message when `git` is not installed ([#362](https://github.com/bower/bower/issues/362))
|
||||
- Add `--quiet` and `--silent` CLI options ([#343](https://github.com/bower/bower/issues/343))
|
||||
- Minor programmatic usage improvements
|
||||
|
||||
_NOTE_: The `components` folder will still be used if already created, making it easier for users to upgrade.
|
||||
|
||||
## 0.9.2 - 2013-04-28
|
||||
- Better fix for [#429](https://github.com/bower/bower/issues/429)
|
||||
|
||||
## 0.9.1 - 2013-04-27
|
||||
- Update `package.json`, docs and other stuff to point to the new `Bower` organisation on GitHub
|
||||
- Fix root label of `bower list` being an absolute path; now uses the package name
|
||||
- Fix `bower update <pkg>` updating all packages; now throws when updating an unknown package
|
||||
- Fix `list` command when package use different names than the `guessed` one ([#429](https://github.com/bower/bower/issues/429))
|
||||
|
||||
## 0.9.0 - 2013-04-25
|
||||
- __Change from `component.json` to `bower.json`__ ([#39](https://github.com/bower/bower/issues/39))
|
||||
- __Compatibility with `node 0.10.x`, including fix hangs/errors when extracting `zip` files__
|
||||
- Fix `--save` and `--save-dev` not working with URLs that get redirected ([#417](https://github.com/bower/bower/issues/417))
|
||||
- Fix `init` command targeting `~commit` instead of `*`. ([#385](https://github.com/bower/bower/issues/385))
|
||||
- Remove temporary directories before exiting ([#345](https://github.com/bower/bower/issues/345))
|
||||
- Integrate `update-notifier` ([#202](https://github.com/bower/bower/issues/202))
|
||||
- Use `json` name when a package name was inferred ([#192](https://github.com/bower/bower/issues/192))
|
||||
- Fix `bin/bower` not exiting with an exit code greater than zero when an error occurs ([#187](https://github.com/bower/bower/issues/187))
|
||||
- Fix `--save` and `--save-dev` saving resolved shorthands instead of the actual shorthands
|
||||
- Fix bower using user defined git templates ([#324](https://github.com/bower/bower/issues/324))
|
||||
- Add command abbreviations ([#262](https://github.com/bower/bower/issues/262))
|
||||
- Improve help messages and fix abuse of colors in output
|
||||
- Wait for every package to resolve before printing error messages ([#290](https://github.com/bower/bower/issues/290))
|
||||
- Add `shorthand_resolver` to allow shorthands to be resolved to repositories other than GitHub ([#278](https://github.com/bower/bower/issues/278))
|
||||
|
||||
## 0.8.6 - 2013-04-03
|
||||
- Emergency fix for `node 0.8.x` users to make `zip` extraction work again
|
||||
|
||||
## 0.8.5 - 2013-03-04
|
||||
- Fix `cache-clean` command clearing the completion cache when the command was called with specific packages
|
||||
- Add error message when an error is caught parsing an invalid `component.json`
|
||||
|
||||
## 0.8.4 - 2013-03-01
|
||||
- Fix some more duplicate async callbacks being called twice
|
||||
- Preserve new lines when saving `component.json` ([#285](https://github.com/bower/bower/issues/285))
|
||||
|
||||
## 0.8.3 - 2013-02-27
|
||||
- Fix error when using the `update` command ([#282](https://github.com/bower/bower/issues/282))
|
||||
|
||||
## 0.8.2 - 2013-02-26
|
||||
- Fix some errors in windows while removing directories, had to downgrade `rimraf` ([#274](https://github.com/bower/bower/issues/274))
|
||||
- Prevent duplicate package names in error summaries ([#277](https://github.com/bower/bower/issues/277))
|
||||
|
||||
## 0.8.1 - 2013-02-25
|
||||
|
||||
- Fix some async callbacks being fired twice ([#274](https://github.com/twitter/bower/issues/274))
|
||||
- Fix some async callbacks being fired twice ([#274](https://github.com/bower/bower/issues/274))
|
||||
|
||||
## 0.8.0 - 2013-02-24
|
||||
- __Add init command similar to `npm init`__ ([#219](https://github.com/twitter/bower/issues/219))
|
||||
- __Add devDependencies__ support ([#251](https://github.com/twitter/bower/issues/251))
|
||||
- __Add `--save-dev` flag to install/uninstall commands__ ([#258](https://github.com/twitter/bower/issues/258))
|
||||
- `cache-clean` command now clears links pointing to nonexistent folders ([#182](https://github.com/twitter/bower/issues/182))
|
||||
- Fix issue when downloading assets behind a proxy using `https` ([#230](https://github.com/twitter/bower/issues/230))
|
||||
- Fix --save saving unresolved components ([#240](https://github.com/twitter/bower/issues/240))
|
||||
- Fix issue when extracting some zip files ([#225](https://github.com/twitter/bower/issues/225))
|
||||
- __Add init command similar to `npm init`__ ([#219](https://github.com/bower/bower/issues/219))
|
||||
- __Add devDependencies__ support ([#251](https://github.com/bower/bower/issues/251))
|
||||
- __Add `--save-dev` flag to install/uninstall commands__ ([#258](https://github.com/bower/bower/issues/258))
|
||||
- `cache-clean` command now clears links pointing to nonexistent folders ([#182](https://github.com/bower/bower/issues/182))
|
||||
- Fix issue when downloading assets behind a proxy using `https` ([#230](https://github.com/bower/bower/issues/230))
|
||||
- Fix --save saving unresolved components ([#240](https://github.com/bower/bower/issues/240))
|
||||
- Fix issue when extracting some zip files ([#225](https://github.com/bower/bower/issues/225))
|
||||
- Fix automatic conflict resolver not selecting the correct version
|
||||
- Add `--sources` option to the `list` command ([#235](https://github.com/twitter/bower/issues/235))
|
||||
- Automatically clear cache when git commands fail with code 128 ([#216](https://github.com/twitter/bower/issues/216))
|
||||
- Fix `bower` not working correctly behind a proxy in some commands ([#208](https://github.com/twitter/bower/issues/208))
|
||||
- Add `--sources` option to the `list` command ([#235](https://github.com/bower/bower/issues/235))
|
||||
- Automatically clear cache when git commands fail with code 128 ([#216](https://github.com/bower/bower/issues/216))
|
||||
- Fix `bower` not working correctly behind a proxy in some commands ([#208](https://github.com/bower/bower/issues/208))
|
||||
|
||||
## 0.7.1 - 2013-02-20
|
||||
- Remove postinstall script from `bower` installation
|
||||
|
||||
## 0.7.0 - 2013-02-01
|
||||
- __Ability to resolve conflicts__ ([#214](https://github.com/twitter/bower/issues/214))
|
||||
- __Ability to search and publish to different endpoints by specifiying them in the `.bowerrc` file__
|
||||
- __Ability to resolve conflicts__ ([#214](https://github.com/bower/bower/issues/214))
|
||||
- __Ability to search and publish to different endpoints by specifying them in the `.bowerrc` file__
|
||||
- __Experimental autocompletion__
|
||||
- __Ability to exclude (ignore) files__
|
||||
- Fix minor issues in the cache clean command
|
||||
- Better error message for invalid semver tags ([#185](https://github.com/twitter/bower/issues/185))
|
||||
- Better error message for invalid semver tags ([#185](https://github.com/bower/bower/issues/185))
|
||||
- Only show discover message in the list command only if there are packages
|
||||
- Fix mismatch issue due to reading cached component.json files ([#214](https://github.com/twitter/bower/issues/214))
|
||||
- Better error messages when reading invalid .bowerrc files ([#220](https://github.com/twitter/bower/issues/220))
|
||||
- Fix update command when used in packages pointing to assets ([#197](https://github.com/twitter/bower/issues/197))
|
||||
- Bower now obeys packages's `.bowerrc` if they define a different `json` ([#205](https://github.com/twitter/bower/issues/205))
|
||||
- Fix mismatch issue due to reading cached component.json files ([#214](https://github.com/bower/bower/issues/214))
|
||||
- Better error messages when reading invalid .bowerrc files ([#220](https://github.com/bower/bower/issues/220))
|
||||
- Fix update command when used in packages pointing to assets ([#197](https://github.com/bower/bower/issues/197))
|
||||
- Bower now obeys packages's `.bowerrc` if they define a different `json` ([#205](https://github.com/bower/bower/issues/205))
|
||||
|
||||
## 0.6.8 - 2012-12-14
|
||||
- Improve list command
|
||||
- Does not fetch versions if not necessary (for --map and --paths options)
|
||||
- Add --offline option to prevent versions from being fetched
|
||||
- Fix uninstall command not firing the `end` event
|
||||
- Fix error when executing an unknown command ([#179](https://github.com/twitter/bower/issues/179))
|
||||
- Fix error when executing an unknown command ([#179](https://github.com/bower/bower/issues/179))
|
||||
- Fix help for the ls command (alias of list)
|
||||
|
||||
## 0.6.7 - 2012-12-10
|
||||
- Fix uninstall removing all unsaved dependencies ([#178](https://github.com/twitter/bower/issues/178))
|
||||
- Fix uninstall removing all unsaved dependencies ([#178](https://github.com/bower/bower/issues/178))
|
||||
- Fix uninstall --force flag in some cases
|
||||
- Add --silent option to the register option, to avoid questioning
|
||||
- Fix possible issues with options in some commands
|
||||
@@ -62,18 +629,18 @@
|
||||
- Fix bower not fetching latest commits correctly in some cases
|
||||
|
||||
## 0.6.4 - 2012-11-29
|
||||
- Fix permission on downloaded files ([#160](https://github.com/twitter/bower/issues/160))
|
||||
- Fix permission on downloaded files ([#160](https://github.com/bower/bower/issues/160))
|
||||
|
||||
## 0.6.3 - 2012-11-24
|
||||
- Fix version not being correctly set for local packages ([#155](https://github.com/twitter/bower/issues/155))
|
||||
- Fix version not being correctly set for local packages ([#155](https://github.com/bower/bower/issues/155))
|
||||
|
||||
## 0.6.2 - 2012-11-23
|
||||
- Fix uninstall --save when there is no component.json
|
||||
|
||||
## 0.6.1 - 2012-11-22
|
||||
- Fix uninstall when the project component.json has no deps saved ([#153](https://github.com/twitter/bower/issues/153))
|
||||
- Fix uncaught errors when using file writter (they are now caught and reported)
|
||||
- Fix temporary directories not being deleted when an exception occurs ([#153](https://github.com/twitter/bower/issues/140))
|
||||
- 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 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
|
||||
- __Add link command__ (similar to npm)
|
||||
@@ -88,13 +655,13 @@
|
||||
|
||||
## 0.5.0 - 2012-11-19
|
||||
- __Remove package.json support__
|
||||
- __Support for local path repositories__ ([#132](https://github.com/twitter/bower/issues/132))
|
||||
- __Support for local path repositories__ ([#132](https://github.com/bower/bower/issues/132))
|
||||
- `install --save` now saves the correct tag (e.g: ~0.0.1) instead of 'latest'
|
||||
- `install --save` now saves packages pointing directly to assets correctly
|
||||
- Bower automatically creates a component.json when install with `--save` is used
|
||||
- Fix issues with list command ([#142](https://github.com/twitter/bower/issues/142))
|
||||
- Fix local paths not being saved when installing with --save ([#114](https://github.com/twitter/bower/issues/114))
|
||||
- `uninstall` now uninstalls nested dependencies if they are not shared ([#83](https://github.com/twitter/bower/issues/83))
|
||||
- Fix issues with list command ([#142](https://github.com/bower/bower/issues/142))
|
||||
- Fix local paths not being saved when installing with --save ([#114](https://github.com/bower/bower/issues/114))
|
||||
- `uninstall` now uninstalls nested dependencies if they are not shared ([#83](https://github.com/bower/bower/issues/83))
|
||||
- `uninstall` now warns when a dependency conflict occurs and aborts.
|
||||
It will only proceed if the `--force` flag is passed
|
||||
- Bower now detects mismatches between the version specified in the component.json and the tag, informing the user
|
||||
|
||||
167
CONTRIBUTING.md
Normal file
167
CONTRIBUTING.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Contributing to Bower
|
||||
|
||||
Bower is a large community project with many different developers contributing at all levels to the project. We're **actively** looking for more contributors right now. If you're interested in becoming a Bower maintainer or supporting in any way, please fill 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.
|
||||
|
||||
<a name="bugs"></a>
|
||||
## 🐛 [Bug reports](https://github.com/bower/bower/wiki/Report-a-Bug)
|
||||
|
||||
## 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 Discord https://discord.gg/0fFM7QF0KpZRh2cY
|
||||
|
||||
If you'd like to attend the meetings, please fill the [support form](http://goo.gl/forms/P1ndzCNoiG), and you'll get an invite.
|
||||
|
||||
## Using the issue tracker
|
||||
|
||||
The issue tracker is the preferred channel for [bug reports](#bugs),
|
||||
[features requests](#features) and [submitting pull
|
||||
requests](#pull-requests), but please respect the following restrictions:
|
||||
|
||||
* Please **do not** use the issue tracker for personal support requests. Use
|
||||
[Stack Overflow](http://stackoverflow.com/questions/tagged/bower),
|
||||
[Discord Channel](https://discordapp.com/channels/119103197720739842/123728452816732160),
|
||||
[Mailing List](http://groups.google.com/group/twitter-bower),
|
||||
(twitter-bower@googlegroups.com), or
|
||||
[#bower](http://webchat.freenode.net/?channels=bower) on Freenode.
|
||||
|
||||
* Please **do not** derail or troll issues. Keep the discussion on topic and
|
||||
respect the opinions of others.
|
||||
|
||||
<a name="features"></a>
|
||||
## Feature requests
|
||||
|
||||
Feature requests are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature. Please
|
||||
provide as much detail and context as possible.
|
||||
|
||||
|
||||
<a name="pull-requests"></a>
|
||||
## Pull requests
|
||||
|
||||
Good pull requests - patches, improvements, new features - are a fantastic
|
||||
help. They should remain focused in scope and avoid containing unrelated
|
||||
commits.
|
||||
|
||||
**Please ask first** before embarking on any significant pull request (e.g.
|
||||
implementing features, refactoring code), otherwise you risk spending a lot of
|
||||
time working on something that the project's developers might not want to merge
|
||||
into the project.
|
||||
|
||||
Please adhere to the coding conventions used throughout a project (indentation,
|
||||
accurate comments, etc.) and any other requirements (such as test coverage).
|
||||
|
||||
Adhering to the following this process is the best way to get your work
|
||||
included in the project:
|
||||
|
||||
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
|
||||
and configure the remotes:
|
||||
|
||||
```bash
|
||||
# Clone your fork of the repo into the current directory
|
||||
git clone https://github.com/<your-username>/bower
|
||||
# Navigate to the newly cloned directory
|
||||
cd bower
|
||||
# Assign the original repo to a remote called "upstream"
|
||||
git remote add upstream https://github.com/bower/bower
|
||||
```
|
||||
|
||||
2. If you cloned a while ago, get the latest changes from upstream:
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git pull upstream master
|
||||
```
|
||||
|
||||
3. Create a new topic branch (off the main project development branch) to
|
||||
contain your feature, change, or fix:
|
||||
|
||||
```bash
|
||||
git checkout -b <topic-branch-name>
|
||||
```
|
||||
|
||||
4. Make sure to update, or add to the tests when appropriate. Patches and
|
||||
features will not be accepted without tests. Run `npm test` to check that
|
||||
all tests pass after you've made changes.
|
||||
|
||||
5. Commit your changes in logical chunks. Please adhere to these [git commit
|
||||
message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
or your code is unlikely be merged into the main project. Use Git's
|
||||
[interactive rebase](https://help.github.com/articles/interactive-rebase)
|
||||
feature to tidy up your commits before making them public.
|
||||
|
||||
6. Locally merge (or rebase) the upstream development branch into your topic branch:
|
||||
|
||||
```bash
|
||||
git pull [--rebase] upstream master
|
||||
```
|
||||
|
||||
7. Push your topic branch up to your fork:
|
||||
|
||||
```bash
|
||||
git push origin <topic-branch-name>
|
||||
```
|
||||
|
||||
8. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||
with a clear title and description.
|
||||
|
||||
9. If you are asked to amend your changes before they can be merged in, please
|
||||
use `git commit --amend` (or rebasing for multi-commit Pull Requests) and
|
||||
force push to your remote feature branch. You may also be asked to squash
|
||||
commits.
|
||||
|
||||
10. If you are asked to squash your commits, then please use `git rebase -i master`. It will ask you to pick your commits - pick the major commits and squash the rest.
|
||||
|
||||
**IMPORTANT**: By submitting a patch, you agree to license your work under the
|
||||
same license as that used by the project.
|
||||
|
||||
|
||||
<a name="maintainers"></a>
|
||||
## Maintainers
|
||||
|
||||
If you have commit access, please follow this process for merging patches and cutting new releases.
|
||||
|
||||
### Reviewing changes
|
||||
|
||||
1. Check that a change is within the scope and philosophy of the project.
|
||||
2. Check that a change has any necessary tests and a proper, descriptive commit message.
|
||||
3. Checkout the change and test it locally.
|
||||
4. If the change is good, and authored by someone who cannot commit to
|
||||
`master`, please try to avoid using GitHub's merge button. Apply the change
|
||||
to `master` locally (feel free to amend any minor problems in the author's
|
||||
original commit if necessary).
|
||||
5. If the change is good, and authored by another maintainer/collaborator, give
|
||||
them a "Ship it!" comment and let them handle the merge.
|
||||
|
||||
### Submitting changes
|
||||
|
||||
1. All non-trivial changes should be put up for review using GitHub Pull
|
||||
Requests.
|
||||
2. Your change should not be merged into `master` (or another feature branch),
|
||||
without at least one "Ship it!" comment from another maintainer/collaborator
|
||||
on the project. "Looks good to me" is not the same as "Ship it!".
|
||||
3. Try to avoid using GitHub's merge button. Locally rebase your change onto
|
||||
`master` and then push to GitHub.
|
||||
4. Once a feature branch has been merged into its target branch, please delete
|
||||
the feature branch from the remote repository.
|
||||
|
||||
### Releasing a new version
|
||||
|
||||
1. Include all new functional changes in the CHANGELOG.
|
||||
2. Use a dedicated commit to increment the version. The version needs to be
|
||||
added to the `CHANGELOG.md` (inc. date) and the `package.json`.
|
||||
3. The commit message must be of `v0.0.0` format.
|
||||
4. Create an annotated tag for the version: `git tag -m "v0.0.0" v0.0.0`.
|
||||
5. Push the changes and tags to GitHub: `git push --tags origin master`.
|
||||
6. Publish the new version to npm: `npm publish`.
|
||||
213
Gruntfile.js
Normal file
213
Gruntfile.js
Normal file
@@ -0,0 +1,213 @@
|
||||
'use strict';
|
||||
|
||||
var tmp = require('tmp');
|
||||
var childProcess = require('child_process');
|
||||
var arraydiff = require('arr-diff');
|
||||
var fs = require('fs');
|
||||
var wrench = require('wrench');
|
||||
var inquirer = require('inquirer');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = function (grunt) {
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
grunt.initConfig({
|
||||
eslint: {
|
||||
options: {
|
||||
fix: true
|
||||
},
|
||||
files: [
|
||||
'Gruntfile.js',
|
||||
'bin/*',
|
||||
'lib/**/*.js',
|
||||
'test/**/*.js',
|
||||
'!test/assets/**/*',
|
||||
'!test/reports/**/*',
|
||||
'!test/sample/**/*',
|
||||
'!test/tmp/**/*'
|
||||
]
|
||||
},
|
||||
simplemocha: {
|
||||
options: {
|
||||
reporter: 'spec',
|
||||
timeout: '15000'
|
||||
},
|
||||
full: {
|
||||
src: ['test/test.js']
|
||||
},
|
||||
short: {
|
||||
options: {
|
||||
reporter: 'dot'
|
||||
},
|
||||
src: ['test/test.js']
|
||||
}
|
||||
},
|
||||
exec: {
|
||||
'assets': {
|
||||
command: 'node test/packages.js && node test/packages-svn.js'
|
||||
},
|
||||
'assets-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 -- --timeout 30000 -R dot test/test.js'
|
||||
},
|
||||
'coveralls': {
|
||||
command: 'npm run coveralls < test/reports/lcov.info',
|
||||
exitCodes: [0, 1, 2, 3] // Alow for failure for coverage report
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
files: ['<%= eslint.files %>'],
|
||||
tasks: ['eslint', 'simplemocha:short']
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('assets', ['exec:assets-force']);
|
||||
grunt.registerTask('test', ['eslint', 'exec:assets', 'simplemocha:full']);
|
||||
grunt.registerTask('cover', 'exec:cover');
|
||||
grunt.registerTask('travis', ['eslint', 'exec:assets', 'exec:cover', 'exec:coveralls']);
|
||||
grunt.registerTask('default', 'test');
|
||||
|
||||
grunt.task.registerTask('publish', 'Perform final checks and publish Bower', function () {
|
||||
var npmVersion = JSON.parse(childProcess.execSync('npm version --json').toString()).npm.split('.');
|
||||
var npmMajor = parseInt(npmVersion[0], 10);
|
||||
var npmMinor = parseInt(npmVersion[1], 10);
|
||||
|
||||
var jsonPackage = require('./package');
|
||||
|
||||
if (npmMajor !== 3 || npmMinor < 5) {
|
||||
grunt.log.writeln('You need to use at least npm@3.5 to publish bower.');
|
||||
grunt.log.writeln('It is because npm 2.x produces too long paths that Windows does not handle.');
|
||||
grunt.log.writeln('Please upgrade it: npm install -g npm');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var version = jsonPackage.version;
|
||||
var changelog = fs.readFileSync('./CHANGELOG.md');
|
||||
|
||||
if (changelog.indexOf('## ' + version) === -1) {
|
||||
grunt.log.writeln('Please add changelog.md entry for this bower version (' + version + ')');
|
||||
|
||||
var lastRelease = childProcess.execSync('git tag | tail -1').toString().trim();
|
||||
|
||||
grunt.log.writeln('Commits since last release (' + lastRelease + '): \n');
|
||||
|
||||
grunt.log.writeln(childProcess.execSync('git log --oneline ' + lastRelease + '..').toString());
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (childProcess.execSync('git rev-parse --abbrev-ref HEAD').toString().trim() !== 'master') {
|
||||
grunt.log.writeln('You need to release bower from the "master" branch');
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.env.SKIP_TESTS !== '1') {
|
||||
grunt.log.writeln('Reinstalling dependencies...');
|
||||
childProcess.execSync('rm -rf node_modules && npm install', { stdio: [0, 1, 2] });
|
||||
|
||||
grunt.log.writeln('Running test suite...');
|
||||
childProcess.execSync('grunt test', { stdio: [0, 1, 2] });
|
||||
}
|
||||
|
||||
var dir = tmp.dirSync().name;
|
||||
|
||||
wrench.copyDirSyncRecursive(__dirname, dir, {
|
||||
forceDelete: true,
|
||||
include: function (path) {
|
||||
return !path.match(/node_modules|\.git|test/);
|
||||
}
|
||||
});
|
||||
|
||||
grunt.log.writeln('Installing production dependencies...');
|
||||
childProcess.execSync('npm install --production --silent', { cwd: dir, stdio: [0, 1, 2] });
|
||||
|
||||
delete jsonPackage.dependencies;
|
||||
delete jsonPackage.devDependencies;
|
||||
delete jsonPackage.scripts;
|
||||
|
||||
fs.writeFileSync(path.resolve(dir, 'package.json'), JSON.stringify(jsonPackage, null, ' ') + '\n');
|
||||
|
||||
|
||||
grunt.log.writeln('Moving node_modules to lib directory...');
|
||||
|
||||
wrench.copyDirSyncRecursive(path.resolve(dir, 'node_modules'), path.resolve(dir, 'lib', 'node_modules'));
|
||||
wrench.rmdirSyncRecursive(path.resolve(dir, 'node_modules'));
|
||||
|
||||
grunt.log.writeln('Testing bower on sample project...');
|
||||
|
||||
childProcess.execSync(
|
||||
'cd test/sample && rm -rf bower_components && ' + dir + '/bin/bower install --force', { stdio: [0, 1, 2] }
|
||||
);
|
||||
|
||||
var expectedPackages = (
|
||||
'SHA-1 ace-builds almond angular angular-animate angular-bootstrap angular-charts angular-contenteditable ' +
|
||||
'angular-deckgrid angular-fullscreen angular-gravatar angular-hotkeys angular-local-storage angular-marked ' +
|
||||
'angular-moment angular-sanitize angular-touch angular-ui-router angular-ui-sortable ' +
|
||||
'angulartics asEvented bootstrap coffee-script d3 es6-shim font-awesome howler jquery ' +
|
||||
'jquery-ui jquery-waypoints js-beautify lodash lz-string marked moment ng-file-upload peerjs ' +
|
||||
'requirejs restangular slimScroll slimScrollHorizontal venturocket-angular-slider'
|
||||
).split(' ');
|
||||
|
||||
var installedPackages = fs.readdirSync('./test/sample/bower_components');
|
||||
|
||||
var installedDiff = arraydiff(expectedPackages, installedPackages);
|
||||
|
||||
if (installedDiff.length > 0) {
|
||||
grunt.log.writeln('ERROR. Some packages were not installed by bower: ');
|
||||
grunt.log.writeln(installedDiff.join(', '));
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
grunt.log.writeln('\nBower production bundle installed in:');
|
||||
grunt.log.writeln(dir + '\n');
|
||||
|
||||
var questions = [
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'review',
|
||||
message: 'Did you review all the changes with "git diff"?',
|
||||
default: false
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'changelog',
|
||||
message: 'Are you sure the CHANGELOG.md contains all changes?',
|
||||
default: false
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'tests',
|
||||
message: 'Are you sure all tests are passing on Travis and Appveyor?',
|
||||
default: false
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'publish',
|
||||
message: 'Are you SURE you want to publish ' + jsonPackage.name + '@' + jsonPackage.version + '?',
|
||||
default: false
|
||||
}
|
||||
];
|
||||
|
||||
var done = this.async();
|
||||
|
||||
inquirer.prompt(questions, function (answers) {
|
||||
if (!answers.review || !answers.changelog || !answers.tests || !answers.publish) {
|
||||
grunt.log.writeln('Please publish bower after you fix this issue');
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
grunt.log.writeln('\nPlease remember to tag this relese, and add a release on Github!');
|
||||
grunt.log.writeln('\nAlso, please remember to test published Bower one more time!');
|
||||
grunt.log.writeln('\nPublishing Bower...');
|
||||
|
||||
childProcess.execSync('npm publish --tag beta', { cwd: dir, stdio: [0, 1, 2] });
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
};
|
||||
20
LICENSE
20
LICENSE
@@ -1,7 +1,19 @@
|
||||
Copyright (c) 2012 Twitter and other contributors
|
||||
Copyright (c) 2013-present 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 the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
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
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
465
README.md
465
README.md
@@ -1,363 +1,200 @@
|
||||
BOWER [](http://travis-ci.org/twitter/bower)
|
||||
=====
|
||||
# Bower - A package manager for the web
|
||||
|
||||
### Introduction
|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
|
||||
Bower is a package manager for the web. Bower lets you easily install assets such as images, CSS and JavaScript, and manages dependencies for you.
|
||||
> ..psst! While Bower is maintained, we recommend [yarn](https://yarnpkg.com/) and [webpack](https://webpack.js.org/) for new front-end projects!
|
||||
|
||||
For example, to install a package, run:
|
||||
[](https://travis-ci.org/bower/bower)
|
||||
[](https://ci.appveyor.com/project/bower/bower)
|
||||
[](https://coveralls.io/r/bower/bower?branch=master)
|
||||
[](https://discord.gg/0fFM7QF0KpZRh2cY)
|
||||
|
||||
bower install jquery
|
||||
<img align="right" height="300" src="http://bower.io/img/bower-logo.png">
|
||||
|
||||
This will download jQuery to `./components/jquery`. That's it. The idea is that Bower does package management and package management only.
|
||||
---
|
||||
|
||||
### 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 is installed using [Node](http://nodejs.org/) and [npm](http://npmjs.org/) (oh my, how meta).
|
||||
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 bower -g
|
||||
**View complete docs on [bower.io](http://bower.io)**
|
||||
|
||||
### Usage
|
||||
[View all packages available through Bower's registry](http://bower.io/search/).
|
||||
|
||||
Your best friend at this stage is probably `bower --help`.
|
||||
## Install
|
||||
|
||||
To install a package:
|
||||
|
||||
bower install jquery
|
||||
bower install git://github.com/components/jquery.git
|
||||
bower install components/jquery (same as above)
|
||||
bower install http://foo.com/jquery.awesome-plugin.js
|
||||
bower install ./repos/jquery
|
||||
|
||||
As you can see, packages can be installed by name, Git endpoint, GitHub shorthand, URL or local path.
|
||||
If you install from a URL that points to a zip or tar file, bower will automatically extract its contents.
|
||||
When tags are available in the endpoint, you can specify a [semver](http://semver.org/) tag to fetch concrete versions:
|
||||
|
||||
bower install jquery#1.8.1
|
||||
bower install git://github.com/components/jquery.git#~1.8.1
|
||||
bower install components/jquery#1.8.x
|
||||
|
||||
Bower also works with private Git repositories. Simply reference them by their SSH endpoint:
|
||||
|
||||
bower install git@github.com:user/private-package.git
|
||||
|
||||
[View all packages available through Bower's registry](http://sindresorhus.com/bower-components/).
|
||||
|
||||
During install you can have Bower add an entry to your component.json as well:
|
||||
|
||||
bower install --save jquery
|
||||
|
||||
To update a package, reference it by name:
|
||||
|
||||
bower update jquery-ui
|
||||
|
||||
To list installed packages:
|
||||
|
||||
bower list
|
||||
|
||||
To search for packages:
|
||||
|
||||
bower search [name]
|
||||
|
||||
To list all the available packages, just call `bower search` without specifying a name.
|
||||
|
||||
To clean the cache:
|
||||
|
||||
bower cache-clean [name]
|
||||
|
||||
Several packages can be cleaned at the same time.
|
||||
To clean the entire cache, just call `bower cache-clean` without any names.
|
||||
Also, both the install and update commands have a `--force` flag that tells bower to bypass the cache and always fetch remote sources.
|
||||
|
||||
You can disable colors by using the `--no-color` flag.
|
||||
|
||||
### Bower Configuration
|
||||
|
||||
Bower can be configured by creating a .bowerrc file in your home folder (usually ~/.bowerrc) with one or all of the following configuration parameters. You can also configure Bower on a per-project basis by creating a .bowerrc file in the project directory, Bower will merge this configuration with the configuration found in your home directory. This allows you to version your project specific Bower configuration with the rest of your code base.
|
||||
|
||||
```json
|
||||
{
|
||||
"directory" : "components",
|
||||
"json" : "component.json",
|
||||
"endpoint" : "https://bower.herokuapp.com"
|
||||
}
|
||||
```sh
|
||||
$ npm install -g bower
|
||||
```
|
||||
|
||||
To run your own Bower Endpoint for custom components/packages that are behind a firewall you can use a simple implementation of bower server at https://github.com/twitter/bower-server.
|
||||
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.
|
||||
|
||||
The __searchpath__ array provides additional URLs of read-only Bower registries that should be consulted to look up components. This is most typically used if your business wishes to
|
||||
house some components internally while still taking advantage of public Bower registries. For example, you might configure the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"directory" : "components",
|
||||
"json" : "component.json",
|
||||
"endpoint" : "http://bower.mycompany.com",
|
||||
"searchpath" : ["https://bower.herokuapp.com"]
|
||||
}
|
||||
## Usage
|
||||
|
||||
See complete command line reference at [bower.io/docs/api/](http://bower.io/docs/api/)
|
||||
|
||||
### Installing packages and dependencies
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Bower will first look to **http://bower.mycompany.com** while trying to find your components. If not found, the main registry at **https://bower.herokuapp.com** will be consulted to see if a copy of the resource can be retrieved.
|
||||
### Using packages
|
||||
|
||||
We discourage using bower components statically for performance and security reasons (if component has an `upload.php` file that is not ignored, that can be easily exploited to do malicious stuff).
|
||||
|
||||
### Defining a package
|
||||
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 a module loader (like [RequireJS](http://requirejs.org/)).
|
||||
|
||||
You can create a `component.json` file in your project's root, specifying 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.
|
||||
### Uninstalling packages
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "myProject",
|
||||
"version": "1.0.0",
|
||||
"main": "./path/to/main.css",
|
||||
"dependencies": {
|
||||
"jquery": "~1.7.2"
|
||||
}
|
||||
}
|
||||
To uninstall a locally installed package:
|
||||
|
||||
```sh
|
||||
$ bower uninstall <package-name>
|
||||
```
|
||||
|
||||
Put this under your project's root, listing all of your dependencies. When you run `bower install`, Bower will read this `component.json` file, resolve all the relevant dependencies and install them.
|
||||
### prezto and oh-my-zsh users
|
||||
|
||||
For now, `name`, `version`, `main`, `dependencies`, `devDependencies`, and `ignore` are the only properties that are used by Bower. If you have several files you're distributing as part of your package, pass an array to `main` like this:
|
||||
On `prezto` or `oh-my-zsh`, do not forget to `alias bower='noglob bower'` or `bower install jquery\#1.9.1`
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "myProject",
|
||||
"version": "1.0.0",
|
||||
"main": ["./path/to/app.css", "./path/to/app.js", "./path/to/sprite.img"],
|
||||
"dependencies": {
|
||||
"jquery": "~1.7.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Bower only recognizes versions that follow the [semver](http://semver.org/) specification.
|
||||
There should only be at most one file per file type in the `main` list. So only one `.js` or `.css`.
|
||||
|
||||
You can also point to packages by adding their URL or file path in the dependency's property.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"eventEmitter": "Wolfy87/EventEmitter", // GitHub short URL
|
||||
"eventEmitter": "Wolfy87/EventEmitter#>=3", // with version
|
||||
"eventEmitter": "git://github.com/Wolfy87/EventEmitter",
|
||||
"eventEmitter": "git@github.com:Wolfy87/EventEmitter.git"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Chances are you have a bunch of extra stuff in the repo that are not needed in production. List these non-necessary file paths in `ignore`.
|
||||
|
||||
```json
|
||||
{
|
||||
"ignore": [
|
||||
"tests/",
|
||||
"**/*.txt"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You may add non-essential packages in `devDependencies`. This is useful for packages aren't required to support the package, but that are used in your project, i.e. to build documentation, run a demo, or run tests.
|
||||
|
||||
```json
|
||||
{
|
||||
"devDependencies": [
|
||||
"qunit": "~1"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Installing dependencies
|
||||
|
||||
Dependencies are installed locally via the `bower install` command. First they’re resolved to find conflicts. Then they’re downloaded and unpacked in a local subdirectory called `./components`, for example:
|
||||
|
||||
|
||||
```
|
||||
/component.json
|
||||
/components/jquery/index.js
|
||||
/components/jquery/component.json
|
||||
```
|
||||
|
||||
You can also install packages one at a time `bower install git://my/git/thing`
|
||||
|
||||
There are no system wide dependencies, no dependencies are shared between different apps, and the dependency tree is flat.
|
||||
|
||||
### Deploying
|
||||
|
||||
The easiest approach is to use Bower statically, just reference the packages manually from a script tag:
|
||||
|
||||
<script src="components/jquery/index.js"></script>
|
||||
|
||||
For more complex projects, you'll probably want to concatenate your scripts. Bower is just a package manager, but there are lots of awesome libraries out there to help you do this, such as [Sprockets](https://github.com/sstephenson/sprockets) and [RequireJS](http://requirejs.org/).
|
||||
|
||||
For example, to use Sprockets:
|
||||
|
||||
```ruby
|
||||
environment = Sprockets::Environment.new
|
||||
environment.append_path 'components'
|
||||
environment.append_path 'public'
|
||||
run environment
|
||||
```
|
||||
|
||||
### Package Consumption
|
||||
|
||||
Bower also makes available a source mapping – this can be used by build tools to easily consume Bower components.
|
||||
|
||||
If you pass the option `--map` to bower's `list` command, it will generate a JSON with dependency objects. Alternatively, you can pass the `--paths` flag to the `list` command to get a simple path to name mapping:
|
||||
|
||||
```json
|
||||
{
|
||||
"backbone": "components/backbone/index.js",
|
||||
"jquery": "components/jquery/index.js",
|
||||
"underscore": "components/underscore/index.js"
|
||||
}
|
||||
```
|
||||
|
||||
### Authoring packages
|
||||
|
||||
To register a new package, it's as simple as specifying a `component.json`, pushing the package to a Git endpoint, say GitHub, and running:
|
||||
|
||||
bower register myawesomepackagename git://github.com/maccmans/face
|
||||
|
||||
There's no authentication or user management. It's on a first come, first served basis. Think of it like a URL shortener. Now anyone can run `bower install myawesomepackagename`, and get your library installed.
|
||||
|
||||
There is no direct way to unregister a package yet. Meanwhile you can request it [here](https://github.com/twitter/bower/issues/120).
|
||||
|
||||
### Philosophy
|
||||
|
||||
Currently, people are managing dependencies, such as JavaScript libraries, manually. This sucks, and we want to change it.
|
||||
|
||||
In a nutshell, Bower is a generic tool which will resolve dependencies and lock packages down to a version. It runs over Git, and is package-agnostic. A package may contain JavaScript, CSS, images, etc., and doesn't rely on any particular transport (AMD, CommonJS, etc.).
|
||||
|
||||
Bower then makes available a simple programmatic API which exposes the package dependency model, so that existing build tools (like Sprockets, LoadBuilder, curls.js, Ender, etc.) can consume it and build files accordingly.
|
||||
|
||||
|
||||
### Programmatic API
|
||||
|
||||
Bower provides a pretty powerful programmatic api. All commands can be accessed through the `bower.commands` object.
|
||||
|
||||
```js
|
||||
var bower = require('bower');
|
||||
|
||||
bower.commands
|
||||
.install(paths, options)
|
||||
.on('end', function (data) {
|
||||
data && console.log(data);
|
||||
});
|
||||
|
||||
bower.commands
|
||||
.search('jquery', {})
|
||||
.on('packages', function(packages) {
|
||||
/* `packages` is a list of packages returned by searching for 'jquery' */
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
Commands emit four types of events: `data`, `end`, `result`, and `error`. `error` will only be emitted if something goes wrong. Not all commands emit all events; for a detailed look, check out the code in `lib/commands`. `data` is typically a colorized string, ready to show to an end user. `search` and `lookup` emit `packages` and `package`, respectively. Those events contain a json representation of the result of the command.
|
||||
|
||||
For a better of idea how this works, you may want to check out [our bin file](https://github.com/twitter/bower/blob/master/bin/bower).
|
||||
|
||||
For the install command, there is an additional `package` event that is emitted for each installed/uninstalled package.
|
||||
|
||||
|
||||
### Completion
|
||||
|
||||
**experimental**
|
||||
|
||||
Based on the completion feature and fantastic work done in
|
||||
[npm](https://npmjs.org/doc/completion.html), Bower now has an experimental
|
||||
`completion` command that works similarly.
|
||||
|
||||
This command will output a Bash / ZSH script to put into your `~/.bashrc`, `~/.bash_profile` or `~/.zshrc` file.
|
||||
|
||||
```
|
||||
bower completion >> ~/.bash_profile
|
||||
```
|
||||
|
||||
*This doesn't work for Windows user, even with Cygwin.*
|
||||
### Never run Bower with sudo
|
||||
|
||||
Bower is a user command; there is no need to execute it with superuser permissions.
|
||||
|
||||
### Windows users
|
||||
|
||||
A lot of people are experiencing problems using bower on windows because [msysgit](http://code.google.com/p/msysgit/) must be installed correctly.
|
||||
Be sure to check the option shown above, otherwise it will simply not work:
|
||||
To use Bower on Windows, you must install
|
||||
[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
|
||||
|
||||
To use Bower on Ubuntu, you might need to link `nodejs` executable to `node`:
|
||||
|
||||
```
|
||||
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).
|
||||
|
||||
|
||||
### FAQ
|
||||
## Support
|
||||
|
||||
**What distinguishes Bower from Jam, Volo or Ender? What does it do better?**
|
||||
* [Discord chat](https://discord.gg/0fFM7QF0KpZRh2cY)
|
||||
* [StackOverflow](http://stackoverflow.com/questions/tagged/bower)
|
||||
* [Mailinglist](http://groups.google.com/group/twitter-bower) - twitter-bower@googlegroups.com
|
||||
|
||||
Bower is a lower level component than Jam, Volo, or Ender. These managers could consume Bower as a dependency.
|
||||
## Contributing
|
||||
|
||||
Bower's aim is simply to install Git paths, resolve dependencies from a `component.json`, check versions, and then provide an API which reports on these things. Nothing more. This is a major diversion from past attempts at browser package management.
|
||||
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).
|
||||
|
||||
Bower is working under the assumption that there is a single, common problem in frontend application development: dependency resolution. Past attempts (Jam, Volo, Ender) try to tackle this problem in such a way that they actually end up alienating and further segregating the JavaScript community around transports (Sprockets, CommonJS, RequireJS, regular script tags).
|
||||
|
||||
Bower offers a generic, unopinionated solution to the problem of package management, while exposing an API that can be consumed by a more opinionated build stack.
|
||||
|
||||
**Volo is an arguably more established project and works with the GitHub search API. Will it take long for Bower to contain a decent number of packages?**
|
||||
|
||||
Bower (being a Git powered package manager) should, in theory, be capable of consuming every package that Volo does, with the additional benefit of supporting internal networks and other Git repositories not hosted on GitHub.
|
||||
|
||||
**We recently saw what happened when the main NPM registry went down. Is a single point of failure possible for Bower and if so, do you have redundancy planned?**
|
||||
|
||||
There's no redundancy planned at the moment, as Bower just installs Git URLs. It's up to the URL provider to establish redundancy.
|
||||
|
||||
**Isn't having a `package.json` file going to conflict with my npm's `package.json`? Will this be a problem?**
|
||||
|
||||
Don't use a `package.json` – use a `component.json`.
|
||||
|
||||
**Bower is an open-source Twitter project. How well can we expect it to be maintained in the future?**
|
||||
|
||||
Twitter is in the process of migrating its frontend architecture onto Bower, so it's fairly safe to say it will be maintained and invested in going forward.
|
||||
* [Bug reports](https://github.com/bower/bower/wiki/Report-a-Bug)
|
||||
* [Feature requests](CONTRIBUTING.md#features)
|
||||
* [Pull requests](CONTRIBUTING.md#pull-requests)
|
||||
|
||||
|
||||
### Contact
|
||||
Note that on Windows for tests to pass you need to configure Git before cloning:
|
||||
|
||||
Have a question?
|
||||
|
||||
- [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
|
||||
```
|
||||
git config --global core.autocrlf input
|
||||
```
|
||||
|
||||
|
||||
### Authors
|
||||
## Backers
|
||||
|
||||
+ [@fat](http://github.com/fat)
|
||||
+ [@maccman](http://github.com/maccman)
|
||||
+ [@satazor](http://github.com/satazor)
|
||||
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/bower#backer)]
|
||||
|
||||
Thanks for assistance and contributions:
|
||||
<a href="https://opencollective.com/bower/backer/0/website" target="_blank"><img src="https://opencollective.com/bower/backer/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/1/website" target="_blank"><img src="https://opencollective.com/bower/backer/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/2/website" target="_blank"><img src="https://opencollective.com/bower/backer/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/3/website" target="_blank"><img src="https://opencollective.com/bower/backer/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/4/website" target="_blank"><img src="https://opencollective.com/bower/backer/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/5/website" target="_blank"><img src="https://opencollective.com/bower/backer/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/6/website" target="_blank"><img src="https://opencollective.com/bower/backer/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/7/website" target="_blank"><img src="https://opencollective.com/bower/backer/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/8/website" target="_blank"><img src="https://opencollective.com/bower/backer/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/9/website" target="_blank"><img src="https://opencollective.com/bower/backer/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/10/website" target="_blank"><img src="https://opencollective.com/bower/backer/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/11/website" target="_blank"><img src="https://opencollective.com/bower/backer/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/12/website" target="_blank"><img src="https://opencollective.com/bower/backer/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/13/website" target="_blank"><img src="https://opencollective.com/bower/backer/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/14/website" target="_blank"><img src="https://opencollective.com/bower/backer/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/15/website" target="_blank"><img src="https://opencollective.com/bower/backer/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/16/website" target="_blank"><img src="https://opencollective.com/bower/backer/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/17/website" target="_blank"><img src="https://opencollective.com/bower/backer/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/18/website" target="_blank"><img src="https://opencollective.com/bower/backer/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/19/website" target="_blank"><img src="https://opencollective.com/bower/backer/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/20/website" target="_blank"><img src="https://opencollective.com/bower/backer/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/21/website" target="_blank"><img src="https://opencollective.com/bower/backer/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/22/website" target="_blank"><img src="https://opencollective.com/bower/backer/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/23/website" target="_blank"><img src="https://opencollective.com/bower/backer/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/24/website" target="_blank"><img src="https://opencollective.com/bower/backer/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/25/website" target="_blank"><img src="https://opencollective.com/bower/backer/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/26/website" target="_blank"><img src="https://opencollective.com/bower/backer/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/27/website" target="_blank"><img src="https://opencollective.com/bower/backer/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/28/website" target="_blank"><img src="https://opencollective.com/bower/backer/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/backer/29/website" target="_blank"><img src="https://opencollective.com/bower/backer/29/avatar.svg"></a>
|
||||
|
||||
|
||||
## Sponsors
|
||||
|
||||
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/bower#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/bower/sponsor/0/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/1/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/2/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/3/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/4/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/5/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/6/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/7/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/8/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/9/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/10/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/11/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/12/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/13/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/14/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/15/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/16/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/17/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/18/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/19/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/20/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/21/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/22/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/23/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/24/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/25/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/26/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/27/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/28/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/bower/sponsor/29/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/29/avatar.svg"></a>
|
||||
|
||||
+ [@addyosmani](http://github.com/addyosmani)
|
||||
+ [@angus-c](http://github.com/angus-c)
|
||||
+ [@borismus](http://github.com/borismus)
|
||||
+ [@chriseppstein](http://github.com/chriseppstein)
|
||||
+ [@danwrong](http://github.com/danwrong)
|
||||
+ [@desandro](http://github.com/desandro)
|
||||
+ [@isaacs](http://github.com/isaacs)
|
||||
+ [@josh](http://github.com/josh)
|
||||
+ [@jrburke](http://github.com/jrburke)
|
||||
+ [@mklabs](http://github.com/mklabs)
|
||||
+ [@paulirish](http://github.com/paulirish)
|
||||
+ [@rvagg](http://github.com/rvagg)
|
||||
+ [@sindresorhus](http://github.com/sindresorhus)
|
||||
+ [@SlexAxton](http://github.com/SlexAxton)
|
||||
+ [@sstephenson](http://github.com/sstephenson)
|
||||
+ [@tomdale](http://github.com/tomdale)
|
||||
+ [@uzquiano](http://github.com/uzquiano)
|
||||
+ [@visionmedia](http://github.com/visionmedia)
|
||||
+ [@wagenet](http://github.com/wagenet)
|
||||
+ [@wycats](http://github.com/wycats)
|
||||
+ [@sindresorhus](http://github.com/sindresorhus)
|
||||
+ [@hemanth](http://github.com/hemanth)
|
||||
+ [@wibblymat](http://github.com/wibblymat)
|
||||
+ [@marcelombc](http://github.com/marcelombc)
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2012 Twitter, Inc.
|
||||
Copyright (c) 2012-present Twitter and [other contributors](https://github.com/bower/bower/graphs/contributors)
|
||||
|
||||
Licensed under the MIT License
|
||||
|
||||
43
bin/bower
43
bin/bower
@@ -1,44 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var semver = require('semver');
|
||||
var nopt = require('nopt');
|
||||
var path = require('path');
|
||||
var pkg = require(path.join(__dirname, '..', 'package.json'));
|
||||
|
||||
var template = require('../lib/util/template');
|
||||
var bower = require('../lib');
|
||||
|
||||
var command;
|
||||
var options;
|
||||
var shorthand;
|
||||
var input = process.argv;
|
||||
var cmdList = Object.keys(bower.commands);
|
||||
var nodeVer = process.version;
|
||||
var reqVer = pkg.engines.node;
|
||||
|
||||
process.title = 'bower';
|
||||
|
||||
if (reqVer && !semver.satisfies(nodeVer, reqVer)) {
|
||||
throw new Error('Required: node ' + reqVer);
|
||||
}
|
||||
|
||||
shorthand = { 'v': ['--version'] };
|
||||
options = { version: Boolean };
|
||||
options = nopt(options, shorthand, process.argv);
|
||||
|
||||
bower.version = pkg.version;
|
||||
|
||||
if (options.version) return console.log(bower.version);
|
||||
if (~cmdList.indexOf(command = options.argv.remain && options.argv.remain.shift())) bower.command = command;
|
||||
|
||||
bower.commands[bower.command || 'help'].line(input)
|
||||
.on('data', function (data) {
|
||||
if (data) console.log(data);
|
||||
})
|
||||
.on('end', function (data) {
|
||||
if (data) console.log(data);
|
||||
})
|
||||
.on('error', function (err) {
|
||||
if (options.verbose) throw err;
|
||||
else console.log(template('error', { message: err.message }, true));
|
||||
});
|
||||
require('../lib/bin/bower');
|
||||
|
||||
145
lib/bin/bower.js
Normal file
145
lib/bin/bower.js
Normal file
@@ -0,0 +1,145 @@
|
||||
process.bin = process.title = 'bower';
|
||||
|
||||
var Q = require('q');
|
||||
var mout = require('mout');
|
||||
var Logger = require('bower-logger');
|
||||
var userHome = require('user-home');
|
||||
var bower = require('../');
|
||||
var version = require('../version');
|
||||
var cli = require('../util/cli');
|
||||
var rootCheck = require('../util/rootCheck');
|
||||
|
||||
var options;
|
||||
var renderer;
|
||||
var loglevel;
|
||||
var command;
|
||||
var commandFunc;
|
||||
var logger;
|
||||
var levels = Logger.LEVELS;
|
||||
|
||||
options = cli.readOptions({
|
||||
'version': { type: Boolean, shorthand: 'v' },
|
||||
'help': { type: Boolean, shorthand: 'h' },
|
||||
'allow-root': { type: Boolean }
|
||||
});
|
||||
|
||||
// Handle print of version
|
||||
if (options.version) {
|
||||
process.stdout.write(version + '\n');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
// Root check
|
||||
rootCheck(options, bower.config);
|
||||
|
||||
// Set loglevel
|
||||
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 {
|
||||
loglevel = levels[bower.config.loglevel] || levels.info;
|
||||
}
|
||||
|
||||
// Get the command to execute
|
||||
while (options.argv.remain.length) {
|
||||
command = options.argv.remain.join(' ');
|
||||
|
||||
// Alias lookup
|
||||
if (bower.abbreviations[command]) {
|
||||
command = bower.abbreviations[command].replace(/\s/g, '.');
|
||||
break;
|
||||
}
|
||||
|
||||
command = command.replace(/\s/g, '.');
|
||||
|
||||
// Direct lookup
|
||||
if (mout.object.has(bower.commands, command)) {
|
||||
break;
|
||||
}
|
||||
|
||||
options.argv.remain.pop();
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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);
|
||||
|
||||
// If the method failed to interpret the process arguments
|
||||
// show the command help
|
||||
if (!logger) {
|
||||
logger = bower.commands.help(command);
|
||||
command = 'help';
|
||||
}
|
||||
}
|
||||
|
||||
// Get the renderer and configure it with the executed command
|
||||
renderer = cli.getRenderer(command, logger.json, bower.config);
|
||||
|
||||
function handleLogger(logger, renderer) {
|
||||
logger
|
||||
.on('end', function (data) {
|
||||
if (!bower.config.silent && !bower.config.quiet) {
|
||||
renderer.end(data);
|
||||
}
|
||||
})
|
||||
.on('error', function (err) {
|
||||
if (command !== 'help' && (err.code === 'EREADOPTIONS' || err.code === 'EINVFORMAT')) {
|
||||
logger = bower.commands.help(command);
|
||||
renderer = cli.getRenderer('help', logger.json, bower.config);
|
||||
handleLogger(logger, renderer);
|
||||
} else {
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleLogger(logger, renderer);
|
||||
|
||||
// Warn if HOME is not SET
|
||||
if (!userHome) {
|
||||
logger.warn('no-home', 'HOME environment variable not set. User config will not be loaded.');
|
||||
}
|
||||
|
||||
if (bower.config.interactive) {
|
||||
var updateNotifier = require('update-notifier');
|
||||
|
||||
// Check for newer version of Bower
|
||||
var notifier = updateNotifier({ pkg: { name: 'bower', version: version } });
|
||||
|
||||
if (notifier.update && levels.info >= loglevel) {
|
||||
notifier.notify();
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
// ==========================================
|
||||
// BOWER: CacheClean API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var async = require('async');
|
||||
var nopt = require('nopt');
|
||||
var rimraf = require('rimraf');
|
||||
var path = require('path');
|
||||
var glob = require('glob');
|
||||
var fs = require('fs');
|
||||
var _ = require('lodash');
|
||||
|
||||
var help = require('./help');
|
||||
var config = require('../core/config');
|
||||
var template = require('../util/template');
|
||||
var fileExists = require('../util/file-exists');
|
||||
|
||||
var optionTypes = { help: Boolean };
|
||||
var shorthand = { 'h': ['--help'] };
|
||||
|
||||
var processCachedPackage = function (emitter, pkg, next) {
|
||||
removeCachedPackage(pkg, function (err, exists) {
|
||||
if (err) {
|
||||
emitter.emit('error', err);
|
||||
return next();
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
template('action', { name: 'cache cleared', shizzle: pkg })
|
||||
.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
var removeCachedPackage = function (pkg, next) {
|
||||
var folder = path.join(config.cache, pkg);
|
||||
|
||||
fileExists(folder, function (exists) {
|
||||
if (!exists) return next(null, false);
|
||||
rimraf(folder, function (err) {
|
||||
if (err) return next(err);
|
||||
next(null, true);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var processLinkedPackage = function (emitter, pkg, next) {
|
||||
checkAndRemoveLinkToPackage(pkg, function (err, removed) {
|
||||
if (err) {
|
||||
emitter.emit('error', err);
|
||||
return next();
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
template('action', { name: 'link cleared', shizzle: pkg })
|
||||
.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
var checkAndRemoveLinkToPackage = function (pkg, next) {
|
||||
var folder = path.join(config.links, pkg);
|
||||
|
||||
fs.readlink(folder, function (err, linkString) {
|
||||
if (err && err.code === 'ENOENT') return next();
|
||||
|
||||
fileExists(linkString, function (exists) {
|
||||
if (!exists) {
|
||||
return rimraf(folder, function (err) {
|
||||
if (err) return next(err);
|
||||
next(null, true);
|
||||
});
|
||||
}
|
||||
|
||||
next(null, false);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function (pkgs) {
|
||||
var emitter = new Emitter;
|
||||
|
||||
async.parallel({
|
||||
cache: function (next) {
|
||||
if (!pkgs || !pkgs.length) {
|
||||
glob('./*', { cwd: config.cache }, function (err, dirs) {
|
||||
if (err) {
|
||||
emitter.emit('error', err);
|
||||
return next();
|
||||
}
|
||||
|
||||
var pkgs = dirs.map(function (dir) { return dir.replace(/^\.\//, ''); });
|
||||
async.forEach(pkgs, processCachedPackage.bind(this, emitter), next);
|
||||
});
|
||||
} else {
|
||||
pkgs = _.uniq(pkgs);
|
||||
async.forEach(pkgs, processCachedPackage.bind(this, emitter), next);
|
||||
}
|
||||
},
|
||||
links: function (next) {
|
||||
if (!pkgs || !pkgs.length) {
|
||||
glob('./*', { cwd: config.links }, function (err, dirs) {
|
||||
if (err) {
|
||||
emitter.emit('error', err);
|
||||
return next();
|
||||
}
|
||||
|
||||
var pkgs = dirs.map(function (dir) { return dir.replace(/^\.\//, ''); });
|
||||
async.forEach(pkgs, processLinkedPackage.bind(this, emitter), next);
|
||||
});
|
||||
} else {
|
||||
pkgs = _.uniq(pkgs);
|
||||
async.forEach(pkgs, processLinkedPackage.bind(this, emitter), next);
|
||||
}
|
||||
},
|
||||
completion: function (next) {
|
||||
rimraf(config.completion, function (err) {
|
||||
if (err) {
|
||||
emitter.emit('error', err);
|
||||
return next();
|
||||
}
|
||||
|
||||
template('action', { name: 'completion cleared', shizzle: 'completion cache' })
|
||||
.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
}, emitter.emit.bind(emitter, 'end'));
|
||||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
var pkgs = options.argv.remain.slice(1);
|
||||
|
||||
if (options.help) return help('cache-clean');
|
||||
return module.exports(pkgs);
|
||||
};
|
||||
|
||||
module.exports.completion = function (opts, cb) {
|
||||
glob('./*', { cwd: config.cache }, function (err, dirs) {
|
||||
if (err) return cb(err);
|
||||
dirs = dirs.map(function (dir) {
|
||||
return dir.replace(/^\.\//, '');
|
||||
});
|
||||
cb(null, dirs);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.completion.options = shorthand;
|
||||
185
lib/commands/cache/clean.js
vendored
Normal file
185
lib/commands/cache/clean.js
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
var fs = require('../../util/fs');
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
var rimraf = require('../../util/rimraf');
|
||||
var endpointParser = require('bower-endpoint-parser');
|
||||
var PackageRepository = require('../../core/PackageRepository');
|
||||
var semver = require('../../util/semver');
|
||||
var defaultConfig = require('../../config');
|
||||
|
||||
function clean(logger, endpoints, options, config) {
|
||||
var decEndpoints;
|
||||
var names;
|
||||
|
||||
options = options || {};
|
||||
config = defaultConfig(config);
|
||||
|
||||
// If endpoints is an empty array, null them
|
||||
if (endpoints && !endpoints.length) {
|
||||
endpoints = null;
|
||||
}
|
||||
|
||||
// Generate decomposed endpoints and names based on the endpoints
|
||||
if (endpoints) {
|
||||
decEndpoints = endpoints.map(function (endpoint) {
|
||||
return endpointParser.decompose(endpoint);
|
||||
});
|
||||
names = decEndpoints.map(function (decEndpoint) {
|
||||
return decEndpoint.name || decEndpoint.source;
|
||||
});
|
||||
}
|
||||
|
||||
return Q.all([
|
||||
clearPackages(decEndpoints, config, logger),
|
||||
clearLinks(names, config, logger)
|
||||
])
|
||||
.spread(function (entries) {
|
||||
return entries;
|
||||
});
|
||||
}
|
||||
|
||||
function clearPackages(decEndpoints, config, logger) {
|
||||
var repository = new PackageRepository(config, logger);
|
||||
|
||||
return repository.list()
|
||||
.then(function (entries) {
|
||||
var promises;
|
||||
|
||||
// Filter entries according to the specified packages
|
||||
if (decEndpoints) {
|
||||
entries = entries.filter(function (entry) {
|
||||
return !!mout.array.find(decEndpoints, function (decEndpoint) {
|
||||
var entryPkgMeta = entry.pkgMeta;
|
||||
|
||||
// Check if name or source match the entry
|
||||
if (decEndpoint.name !== entryPkgMeta.name &&
|
||||
decEndpoint.source !== entryPkgMeta.name &&
|
||||
decEndpoint.source !== entryPkgMeta._source
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If target is a wildcard, simply return true
|
||||
if (decEndpoint.target === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's a semver target, compare using semver spec
|
||||
if (semver.validRange(decEndpoint.target)) {
|
||||
return semver.satisfies(entryPkgMeta.version, decEndpoint.target);
|
||||
}
|
||||
|
||||
// Otherwise, compare against target/release
|
||||
return decEndpoint.target === entryPkgMeta._target ||
|
||||
decEndpoint.target === entryPkgMeta._release;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
promises = entries.map(function (entry) {
|
||||
return repository.eliminate(entry.pkgMeta)
|
||||
.then(function () {
|
||||
logger.info('deleted', 'Cached package ' + entry.pkgMeta.name + ': ' + entry.canonicalDir, {
|
||||
file: entry.canonicalDir
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Q.all(promises)
|
||||
.then(function () {
|
||||
if (!decEndpoints) {
|
||||
// Ensure that everything is cleaned,
|
||||
// even invalid packages in the cache
|
||||
return repository.clear();
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
return entries;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clearLinks(names, config, logger) {
|
||||
var promise;
|
||||
var dir = config.storage.links;
|
||||
|
||||
// If no names are passed, grab all links
|
||||
if (!names) {
|
||||
promise = Q.nfcall(fs.readdir, dir)
|
||||
.fail(function (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return [];
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
// Otherwise use passed ones
|
||||
} else {
|
||||
promise = Q.resolve(names);
|
||||
}
|
||||
|
||||
return promise
|
||||
.then(function (names) {
|
||||
var promises;
|
||||
var linksToRemove = [];
|
||||
|
||||
// Decide which links to delete
|
||||
promises = names.map(function (name) {
|
||||
var link = path.join(config.storage.links, name);
|
||||
|
||||
return Q.nfcall(fs.readlink, link)
|
||||
.then(function (linkTarget) {
|
||||
// Link exists, check if it points to a folder
|
||||
// that still exists
|
||||
return Q.nfcall(fs.stat, linkTarget)
|
||||
.then(function (stat) {
|
||||
// Target is not a folder..
|
||||
if (!stat.isDirectory()) {
|
||||
linksToRemove.push(link);
|
||||
}
|
||||
})
|
||||
// Error occurred reading the link
|
||||
.fail(function () {
|
||||
linksToRemove.push(link);
|
||||
});
|
||||
// Ignore if link does not exist
|
||||
}, function (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
linksToRemove.push(link);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Q.all(promises)
|
||||
.then(function () {
|
||||
var promises;
|
||||
|
||||
// Remove each link that was declared as invalid
|
||||
promises = linksToRemove.map(function (link) {
|
||||
return Q.nfcall(rimraf, link)
|
||||
.then(function () {
|
||||
logger.info('deleted', 'Invalid link: ' + link, {
|
||||
file: link
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Q.all(promises);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
clean.readOptions = function (argv) {
|
||||
var cli = require('../../util/cli');
|
||||
var options = cli.readOptions(argv);
|
||||
var endpoints = options.argv.remain.slice(2);
|
||||
|
||||
delete options.argv;
|
||||
|
||||
return [endpoints, options];
|
||||
};
|
||||
|
||||
module.exports = clean;
|
||||
43
lib/commands/cache/list.js
vendored
Normal file
43
lib/commands/cache/list.js
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
var mout = require('mout');
|
||||
var PackageRepository = require('../../core/PackageRepository');
|
||||
var defaultConfig = require('../../config');
|
||||
|
||||
function list(logger, packages, options, config) {
|
||||
var repository;
|
||||
|
||||
config = defaultConfig(config);
|
||||
repository = new PackageRepository(config, logger);
|
||||
|
||||
// If packages is an empty array, null them
|
||||
if (packages && !packages.length) {
|
||||
packages = null;
|
||||
}
|
||||
|
||||
return repository.list()
|
||||
.then(function (entries) {
|
||||
if (packages) {
|
||||
// Filter entries according to the specified packages
|
||||
entries = entries.filter(function (entry) {
|
||||
return !!mout.array.find(packages, function (pkg) {
|
||||
return pkg === entry.pkgMeta.name;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return entries;
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
list.readOptions = function (argv) {
|
||||
var cli = require('../../util/cli');
|
||||
var options = cli.readOptions(argv);
|
||||
var packages = options.argv.remain.slice(2);
|
||||
|
||||
delete options.argv;
|
||||
|
||||
return [packages, options];
|
||||
};
|
||||
|
||||
module.exports = list;
|
||||
@@ -1,112 +0,0 @@
|
||||
// ==========================================
|
||||
// BOWER: Completion API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var path = require('path');
|
||||
var nopt = require('nopt');
|
||||
var mkdirp = require('mkdirp');
|
||||
|
||||
var template = require('../util/template');
|
||||
var complete = require('../util/completion');
|
||||
var config = require('../core/config');
|
||||
var help = require('./help');
|
||||
|
||||
var optionTypes = { help: Boolean };
|
||||
var shorthand = { 'h': ['--help'] };
|
||||
|
||||
module.exports = function (argv, env) {
|
||||
env = env || process.env;
|
||||
|
||||
var emitter = new Emitter;
|
||||
var commands = require('../commands');
|
||||
|
||||
// top level flags
|
||||
var flags = ['--no-color', '--help', '--version'];
|
||||
|
||||
var done = function done() {
|
||||
process.nextTick(function () {
|
||||
emitter.emit('end');
|
||||
});
|
||||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
// if the COMP_* isn't in the env, then just dump the script.
|
||||
if (!env.COMP_CWORD) {
|
||||
template('completion').on('data', emitter.emit.bind(emitter, 'end'));
|
||||
return emitter;
|
||||
}
|
||||
|
||||
// parse environment and arguments, returns a hash of completion config.
|
||||
var opts = complete(argv, env);
|
||||
|
||||
// if only one word, complete the list of command or top level flags
|
||||
if (opts.w === 1) {
|
||||
if (opts.word.charAt(0) === '-') complete.log(flags, opts);
|
||||
else complete.log(Object.keys(commands), opts);
|
||||
return done();
|
||||
}
|
||||
|
||||
// try to find the bower command. first thing after all the configs.
|
||||
var parsed = opts.conf = nopt({}, {}, opts.partialWords, 0);
|
||||
var cmd = parsed.argv.remain[0];
|
||||
|
||||
// unable to find a command, complete the lisf of commands
|
||||
if (!cmd) {
|
||||
complete.log(Object.keys(commands), opts);
|
||||
return done();
|
||||
}
|
||||
|
||||
// if words[0] is a bower command, then complete on it.
|
||||
cmd = commands[cmd];
|
||||
|
||||
if (cmd && cmd.completion) {
|
||||
// prior to that, ensure the completion cache directory is created first
|
||||
mkdirp(path.join(config.completion), function (err) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
|
||||
var options = cmd.completion.options;
|
||||
if (options && opts.word.charAt(0) === '-') {
|
||||
complete.log(Object.keys(options).map(function (option) {
|
||||
return opts.word.charAt(1) === '-' ? options[option][0] : '-' + option;
|
||||
}), opts);
|
||||
return done();
|
||||
}
|
||||
|
||||
cmd.completion(opts, function (err, data) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
|
||||
// completing options, then append top level flags
|
||||
if (opts.word.charAt(0) === '-') data = data.concat(flags);
|
||||
|
||||
complete.log(data, opts);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
// otherwise, do nothing
|
||||
return emitter;
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var emitter = new Emitter;
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
|
||||
if (options.help) return help('completion');
|
||||
|
||||
module.exports(options.argv.remain.slice(2), process.env)
|
||||
.on('data', emitter.emit.bind(emitter, 'data'))
|
||||
.on('error', emitter.emit.bind(emitter, 'error'))
|
||||
.on('end', emitter.emit.bind(emitter, 'end'));
|
||||
|
||||
return emitter;
|
||||
};
|
||||
@@ -1,49 +1,39 @@
|
||||
// ==========================================
|
||||
// BOWER: Help API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var Q = require('q');
|
||||
var path = require('path');
|
||||
var fs = require('../util/fs');
|
||||
var createError = require('../util/createError');
|
||||
|
||||
var events = require('events');
|
||||
var nopt = require('nopt');
|
||||
var _ = require('lodash');
|
||||
function help(logger, name, config) {
|
||||
var json;
|
||||
|
||||
var template = require('../util/template');
|
||||
var config = require('../core/config');
|
||||
if (name) {
|
||||
json = path.resolve(__dirname, '../templates/json/help-' + name.replace(/\s+/g, '/') + '.json');
|
||||
} else {
|
||||
json = path.resolve(__dirname, '../templates/json/help.json');
|
||||
}
|
||||
|
||||
module.exports = function (name) {
|
||||
var context = {};
|
||||
var emitter = new events.EventEmitter;
|
||||
var commands = require('../commands');
|
||||
return Q.promise(function (resolve) {
|
||||
fs.exists(json, resolve);
|
||||
})
|
||||
.then(function (exists) {
|
||||
if (!exists) {
|
||||
throw createError('Unknown command: ' + name, 'EUNKNOWNCMD', {
|
||||
command: name
|
||||
});
|
||||
}
|
||||
|
||||
// Aliases
|
||||
// At the moment we just have the ls, but we might have more
|
||||
switch (name) {
|
||||
case 'ls':
|
||||
name = 'list';
|
||||
break;
|
||||
}
|
||||
return require(json);
|
||||
});
|
||||
}
|
||||
|
||||
var validCommand = !!(name && commands[name]);
|
||||
var templateName = validCommand ? 'help-' + name : 'help';
|
||||
// -------------------
|
||||
|
||||
if (!validCommand) context = { commands: Object.keys(commands).join(', ') };
|
||||
_.extend(context, config);
|
||||
help.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
var options = cli.readOptions(argv);
|
||||
var name = options.argv.remain.slice(1).join(' ');
|
||||
|
||||
template(templateName, context)
|
||||
.on('data', emitter.emit.bind(emitter, 'end'));
|
||||
|
||||
return emitter;
|
||||
return [name];
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt({}, {}, argv);
|
||||
var paths = options.argv.remain.slice(1);
|
||||
return module.exports(paths[0]);
|
||||
};
|
||||
|
||||
module.exports.completion = function (opts, cb) {
|
||||
return cb(null, Object.keys(require('../commands')));
|
||||
};
|
||||
module.exports = help;
|
||||
|
||||
58
lib/commands/home.js
Normal file
58
lib/commands/home.js
Normal file
@@ -0,0 +1,58 @@
|
||||
var Project = require('../core/Project');
|
||||
var open = require('opn');
|
||||
var endpointParser = require('bower-endpoint-parser');
|
||||
var createError = require('../util/createError');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
function home(logger, name, config) {
|
||||
var project;
|
||||
var promise;
|
||||
var decEndpoint;
|
||||
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
// Get the package meta
|
||||
// If no name is specified, read the project json
|
||||
// If a name is specified, fetch from the package repository
|
||||
if (!name) {
|
||||
promise = project.hasJson()
|
||||
.then(function (json) {
|
||||
if (!json) {
|
||||
throw createError('You are not inside a package', 'ENOENT');
|
||||
}
|
||||
|
||||
return project.getJson();
|
||||
});
|
||||
} else {
|
||||
decEndpoint = endpointParser.decompose(name);
|
||||
promise = project.getPackageRepository().fetch(decEndpoint)
|
||||
.spread(function (canonicalDir, pkgMeta) {
|
||||
return pkgMeta;
|
||||
});
|
||||
}
|
||||
|
||||
// Get homepage and open it
|
||||
return promise.then(function (pkgMeta) {
|
||||
var homepage = pkgMeta.homepage;
|
||||
|
||||
if (!homepage) {
|
||||
throw createError('No homepage set for ' + pkgMeta.name, 'ENOHOME');
|
||||
}
|
||||
|
||||
open(homepage, { wait: false });
|
||||
return homepage;
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
home.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
var options = cli.readOptions(argv);
|
||||
var name = options.argv.remain[1];
|
||||
|
||||
return [name];
|
||||
};
|
||||
|
||||
module.exports = home;
|
||||
@@ -1,24 +1,81 @@
|
||||
// ==========================================
|
||||
// BOWER: Public Commands List
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
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) {
|
||||
function runApi() {
|
||||
var command = require(id);
|
||||
var commandArgs = [].slice.call(arguments);
|
||||
|
||||
return withLogger(function (logger) {
|
||||
commandArgs.unshift(logger);
|
||||
|
||||
return command.apply(undefined, commandArgs);
|
||||
});
|
||||
}
|
||||
|
||||
function runFromArgv(argv) {
|
||||
var commandArgs;
|
||||
var command = require(id);
|
||||
|
||||
commandArgs = command.readOptions(argv);
|
||||
|
||||
return withLogger(function (logger) {
|
||||
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;
|
||||
}
|
||||
|
||||
runApi.line = runFromArgv;
|
||||
|
||||
return runApi;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
'help': require('./help'),
|
||||
'install': require('./install'),
|
||||
'list': require('./list'),
|
||||
'ls': require('./list'),
|
||||
'uninstall': require('./uninstall'),
|
||||
'update': require('./update'),
|
||||
'link': require('./link'),
|
||||
'lookup': require('./lookup'),
|
||||
'info': require('./info'),
|
||||
'init': require('./init'),
|
||||
'register': require('./register'),
|
||||
'search': require('./search'),
|
||||
'cache-clean': require('./cache-clean'),
|
||||
'completion': require('./completion')
|
||||
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,50 +1,70 @@
|
||||
// ==========================================
|
||||
// BOWER: Lookup API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
var endpointParser = require('bower-endpoint-parser');
|
||||
var PackageRepository = require('../core/PackageRepository');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var nopt = require('nopt');
|
||||
function info(logger, endpoint, property, config) {
|
||||
if (!endpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
var template = require('../util/template');
|
||||
var source = require('../core/source');
|
||||
var install = require('./install');
|
||||
var help = require('./help');
|
||||
// handle @ as version divider
|
||||
var splitParts = endpoint.split('/');
|
||||
splitParts[splitParts.length - 1] = splitParts[splitParts.length - 1].replace('@', '#');
|
||||
endpoint = splitParts.join('/');
|
||||
|
||||
var optionTypes = { help: Boolean };
|
||||
var shorthand = { 'h': ['--help'] };
|
||||
|
||||
module.exports = function (name) {
|
||||
var emitter = new Emitter;
|
||||
var repository;
|
||||
var decEndpoint;
|
||||
|
||||
if (name) {
|
||||
source.info(name, function (err, result) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
emitter.emit('end', result);
|
||||
});
|
||||
}
|
||||
config = defaultConfig(config);
|
||||
repository = new PackageRepository(config, logger);
|
||||
|
||||
return emitter;
|
||||
decEndpoint = endpointParser.decompose(endpoint);
|
||||
|
||||
return Q.all([
|
||||
getPkgMeta(repository, decEndpoint, property),
|
||||
decEndpoint.target === '*' && !property ? repository.versions(decEndpoint.source) : null
|
||||
])
|
||||
.spread(function (pkgMeta, versions) {
|
||||
if (versions) {
|
||||
return {
|
||||
name: decEndpoint.source,
|
||||
versions: versions,
|
||||
latest: pkgMeta
|
||||
};
|
||||
}
|
||||
|
||||
return pkgMeta;
|
||||
});
|
||||
}
|
||||
|
||||
function getPkgMeta(repository, decEndpoint, property) {
|
||||
return repository.fetch(decEndpoint)
|
||||
.spread(function (canonicalDir, pkgMeta) {
|
||||
pkgMeta = mout.object.filter(pkgMeta, function (value, key) {
|
||||
return key.charAt(0) !== '_';
|
||||
});
|
||||
|
||||
// Retrieve specific property
|
||||
if (property) {
|
||||
pkgMeta = mout.object.get(pkgMeta, property);
|
||||
}
|
||||
|
||||
return pkgMeta;
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
info.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];
|
||||
|
||||
return [pkg, property];
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var emitter = new Emitter;
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
var names = options.argv.remain.slice(1);
|
||||
|
||||
if (options.help || !names.length) return help('info');
|
||||
|
||||
module.exports(names[0])
|
||||
.on('error', emitter.emit.bind(emitter, 'error'))
|
||||
.on('end', function (data) {
|
||||
template('info', data).on('data', emitter.emit.bind(emitter, 'end'));
|
||||
});
|
||||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
module.exports.completion = install.completion;
|
||||
module.exports.completion.options = shorthand;
|
||||
module.exports = info;
|
||||
|
||||
@@ -1,150 +1,313 @@
|
||||
// ==========================================
|
||||
// BOWER: Init API
|
||||
// ==========================================
|
||||
// Copyright 2013 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var mout = require('mout');
|
||||
var fs = require('../util/fs');
|
||||
var path = require('path');
|
||||
var Q = require('q');
|
||||
var endpointParser = require('bower-endpoint-parser');
|
||||
var Project = require('../core/Project');
|
||||
var defaultConfig = require('../config');
|
||||
var GitHubResolver = require('../core/resolvers/GitHubResolver');
|
||||
var cmd = require('../util/cmd');
|
||||
var createError = require('../util/createError');
|
||||
|
||||
function init(logger, config) {
|
||||
var project;
|
||||
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var util = require('util');
|
||||
config = config || {};
|
||||
|
||||
var nopt = require('nopt');
|
||||
var promptly = require('promptly');
|
||||
var _ = require('lodash');
|
||||
|
||||
var help = require('./help');
|
||||
var Manager = require('../core/manager');
|
||||
var config = require('../core/config');
|
||||
|
||||
var optionTypes = { help: Boolean };
|
||||
var shorthand = { 'h': ['--help'] };
|
||||
|
||||
var commonIgnore = ['**/.*', 'node_modules', 'components'];
|
||||
|
||||
var Init = function () {
|
||||
Manager.call(this);
|
||||
};
|
||||
|
||||
util.inherits(Init, Manager);
|
||||
|
||||
Init.prototype.getDependenciesJSON = function () {
|
||||
var dependencies = Object.keys(this.dependencies);
|
||||
var remaining = dependencies.length;
|
||||
var json = {};
|
||||
|
||||
dependencies.forEach(function (name) {
|
||||
var pkg = this.dependencies[name][0];
|
||||
|
||||
pkg.on('loadJSON', function () {
|
||||
// TODO: use fetch endpoint here
|
||||
json[pkg.name] = '~' + pkg.version;
|
||||
remaining -= 1;
|
||||
if (remaining === 0) {
|
||||
this.manager.emit('loadDependencies', json);
|
||||
}
|
||||
}).loadJSON();
|
||||
}, this);
|
||||
|
||||
if (remaining === 0) {
|
||||
this.emit('loadDependencies', json);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Init.prototype.getMain = function (name) {
|
||||
name = path.basename(name, '.js');
|
||||
if (fs.existsSync(path.join(process.cwd(), 'index.js'))) {
|
||||
return 'index.js';
|
||||
} else if (fs.existsSync(path.join(process.cwd(), name + '.js'))) {
|
||||
return name + '.js';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
Init.prototype.showPrompt = function (question, cb) {
|
||||
var prompt = question.prompt + ': [' + (question.yesno ? 'y' : question.value) + ']';
|
||||
if (question.yesno) {
|
||||
promptly.confirm(prompt, {'default': 'y'}, cb);
|
||||
} else {
|
||||
promptly.prompt(prompt, {'default': question.value}, cb);
|
||||
}
|
||||
this.emit('prompt', prompt);
|
||||
};
|
||||
|
||||
Init.prototype.prompts = function (dependencies) {
|
||||
var main = this.json.main || this.getMain(this.json.name) || '';
|
||||
|
||||
var questions = _.compact([
|
||||
{key: 'name', prompt: 'name', value: this.json.name, yesno: false},
|
||||
{key: 'version', prompt: 'version', value: this.json.version, yesno: false},
|
||||
{key: 'main', prompt: 'main file', value: main, yesno: false},
|
||||
_.size(dependencies) ? {key: 'dependencies', prompt: 'add current components as dependencies? (y/n)', value: dependencies, yesno: true} : null,
|
||||
{key: 'ignore', prompt: 'add commonly ignored files to ignore list? (y/n)', value: commonIgnore, yesno: true}
|
||||
]);
|
||||
var index = 0;
|
||||
var question = questions[index];
|
||||
|
||||
var cb = function (err, value) {
|
||||
if (question.yesno) {
|
||||
if (value) {
|
||||
this.json[question.key] = question.value;
|
||||
}
|
||||
} else if (value) {
|
||||
this.json[question.key] = value;
|
||||
if (!config.cwd) {
|
||||
config.cwd = process.cwd();
|
||||
}
|
||||
index += 1;
|
||||
if (index < questions.length) {
|
||||
question = questions[index];
|
||||
this.showPrompt(question, cb);
|
||||
} else {
|
||||
this.emit('prompts');
|
||||
|
||||
config = defaultConfig(config);
|
||||
|
||||
// This command requires interactive to be enabled
|
||||
if (!config.interactive) {
|
||||
throw createError('Register requires an interactive shell', 'ENOINT', {
|
||||
details: 'Note that you can manually force an interactive shell with --config.interactive'
|
||||
});
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
this.showPrompt(question, cb);
|
||||
};
|
||||
project = new Project(config, logger);
|
||||
|
||||
Init.prototype.save = function (data) {
|
||||
fs.writeFileSync(path.join(this.cwd, config.json), JSON.stringify(data, null, 2));
|
||||
};
|
||||
// Start with existing JSON details
|
||||
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.bind(null, config))
|
||||
// Set dependencies based on the response
|
||||
.spread(setDependencies.bind(null, project))
|
||||
// All done!
|
||||
.spread(saveJson.bind(null, project, logger));
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
var init = new Init();
|
||||
function readJson(project, logger) {
|
||||
return project.hasJson()
|
||||
.then(function (json) {
|
||||
if (json) {
|
||||
logger.warn('existing', 'The existing ' + path.basename(json) + ' file will be used and filled in');
|
||||
}
|
||||
|
||||
init
|
||||
.on('resolveLocal', init.loadJSON.bind(init))
|
||||
.on('loadJSON', init.getDependenciesJSON.bind(init))
|
||||
.on('loadDependencies', init.prompts.bind(init))
|
||||
.on('prompts', function () {
|
||||
init.save(init.json);
|
||||
init.emit('end');
|
||||
return project.getJson();
|
||||
});
|
||||
}
|
||||
|
||||
function saveJson(project, logger, json) {
|
||||
// Cleanup empty props (null values, empty strings, objects and arrays)
|
||||
mout.object.forOwn(json, function (value, key) {
|
||||
if (!validConfigValue(value)) {
|
||||
delete json[key];
|
||||
}
|
||||
});
|
||||
|
||||
logger.info('json', 'Generated json', { json: json });
|
||||
|
||||
// Confirm the json with the user
|
||||
return Q.nfcall(logger.prompt.bind(logger), {
|
||||
type: 'confirm',
|
||||
message: 'Looks good?',
|
||||
default: true
|
||||
})
|
||||
.resolveLocal();
|
||||
.then(function (good) {
|
||||
if (!good) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return init;
|
||||
};
|
||||
|
||||
module.exports.Init = Init; // Purely for testing
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
if (options.help) return help('init');
|
||||
return module.exports();
|
||||
};
|
||||
|
||||
module.exports.completion = function (opts, cb) {
|
||||
var word = opts.word;
|
||||
|
||||
// completing options?
|
||||
if (word.charAt(0) === '-') {
|
||||
return cb(null, Object.keys(optionTypes).map(function (option) {
|
||||
return '--' + option;
|
||||
}));
|
||||
}
|
||||
// Save json (true forces file creation)
|
||||
return project.saveJson(true);
|
||||
});
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
// Name
|
||||
if (!json.name) {
|
||||
json.name = path.basename(config.cwd);
|
||||
}
|
||||
|
||||
// Main
|
||||
if (!json.main) {
|
||||
// Remove '.js' from the end of the package name if it is there
|
||||
name = path.basename(json.name, '.js');
|
||||
|
||||
if (fs.existsSync(path.join(config.cwd, 'index.js'))) {
|
||||
json.main = 'index.js';
|
||||
} else if (fs.existsSync(path.join(config.cwd, name + '.js'))) {
|
||||
json.main = name + '.js';
|
||||
}
|
||||
}
|
||||
|
||||
// Homepage
|
||||
if (!json.homepage) {
|
||||
// Set as GitHub homepage if it's a GitHub repository
|
||||
promise = promise.then(function () {
|
||||
return cmd('git', ['config', '--get', 'remote.origin.url'])
|
||||
.spread(function (stdout) {
|
||||
var pair;
|
||||
|
||||
stdout = stdout.trim();
|
||||
if (!stdout) {
|
||||
return;
|
||||
}
|
||||
|
||||
pair = GitHubResolver.getOrgRepoPair(stdout);
|
||||
if (pair) {
|
||||
json.homepage = 'https://github.com/' + pair.org + '/' + pair.repo;
|
||||
}
|
||||
})
|
||||
.fail(function () { });
|
||||
});
|
||||
}
|
||||
|
||||
if (!json.authors) {
|
||||
promise = promise.then(function () {
|
||||
// Get the user name configured in git
|
||||
return cmd('git', ['config', '--get', '--global', 'user.name'])
|
||||
.spread(function (stdout) {
|
||||
var gitEmail;
|
||||
var gitName = stdout.trim();
|
||||
|
||||
// Abort if no name specified
|
||||
if (!gitName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the user email configured in git
|
||||
return cmd('git', ['config', '--get', '--global', 'user.email'])
|
||||
.spread(function (stdout) {
|
||||
gitEmail = stdout.trim();
|
||||
}, function () {})
|
||||
.then(function () {
|
||||
json.authors = gitName;
|
||||
json.authors += gitEmail ? ' <' + gitEmail + '>' : '';
|
||||
});
|
||||
}, function () {});
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(function () {
|
||||
return json;
|
||||
});
|
||||
}
|
||||
|
||||
function promptUser(logger, json) {
|
||||
var questions = [
|
||||
{
|
||||
'name': 'name',
|
||||
'message': 'name',
|
||||
'default': json.name,
|
||||
'type': 'input'
|
||||
},
|
||||
{
|
||||
'name': 'description',
|
||||
'message': 'description',
|
||||
'default': json.description,
|
||||
'type': 'input'
|
||||
},
|
||||
{
|
||||
'name': 'main',
|
||||
'message': 'main file',
|
||||
'default': json.main,
|
||||
'type': 'input'
|
||||
},
|
||||
{
|
||||
'name': 'keywords',
|
||||
'message': 'keywords',
|
||||
'default': json.keywords ? json.keywords.toString() : null,
|
||||
'type': 'input'
|
||||
},
|
||||
{
|
||||
'name': 'authors',
|
||||
'message': 'authors',
|
||||
'default': json.authors ? json.authors.toString() : null,
|
||||
'type': 'input'
|
||||
},
|
||||
{
|
||||
'name': 'license',
|
||||
'message': 'license',
|
||||
'default': json.license || 'MIT',
|
||||
'type': 'input'
|
||||
},
|
||||
{
|
||||
'name': 'homepage',
|
||||
'message': 'homepage',
|
||||
'default': json.homepage,
|
||||
'type': 'input'
|
||||
},
|
||||
{
|
||||
'name': 'dependencies',
|
||||
'message': 'set currently installed components as dependencies?',
|
||||
'default': !mout.object.size(json.dependencies) && !mout.object.size(json.devDependencies),
|
||||
'type': 'confirm'
|
||||
},
|
||||
{
|
||||
'name': 'ignore',
|
||||
'message': 'add commonly ignored files to ignore list?',
|
||||
'default': true,
|
||||
'type': 'confirm'
|
||||
},
|
||||
{
|
||||
'name': 'private',
|
||||
'message': 'would you like to mark this package as private which prevents it from being accidentally published to the registry?',
|
||||
'default': !!json.private,
|
||||
'type': 'confirm'
|
||||
}
|
||||
];
|
||||
|
||||
return Q.nfcall(logger.prompt.bind(logger), questions)
|
||||
.then(function (answers) {
|
||||
json.name = answers.name;
|
||||
json.description = answers.description;
|
||||
json.main = answers.main;
|
||||
json.keywords = toArray(answers.keywords);
|
||||
json.authors = toArray(answers.authors, ',');
|
||||
json.license = answers.license;
|
||||
json.homepage = answers.homepage;
|
||||
json.private = answers.private || null;
|
||||
|
||||
return [json, answers];
|
||||
});
|
||||
}
|
||||
|
||||
function toArray(value, splitter) {
|
||||
var arr = value.split(splitter || /[\s,]/);
|
||||
|
||||
// Trim values
|
||||
arr = arr.map(function (item) {
|
||||
return item.trim();
|
||||
});
|
||||
|
||||
// Filter empty values
|
||||
arr = arr.filter(function (item) {
|
||||
return !!item;
|
||||
});
|
||||
|
||||
return arr.length ? arr : null;
|
||||
}
|
||||
|
||||
function setIgnore(config, json, answers) {
|
||||
if (answers.ignore) {
|
||||
json.ignore = mout.array.combine(json.ignore || [], [
|
||||
'**/.*',
|
||||
'node_modules',
|
||||
'bower_components',
|
||||
config.directory,
|
||||
'test',
|
||||
'tests'
|
||||
]);
|
||||
}
|
||||
|
||||
return [json, answers];
|
||||
}
|
||||
|
||||
function setDependencies(project, json, answers) {
|
||||
if (answers.dependencies) {
|
||||
return project.getTree()
|
||||
.spread(function (tree, flattened, extraneous) {
|
||||
if (extraneous.length) {
|
||||
json.dependencies = {};
|
||||
|
||||
// Add extraneous as dependencies
|
||||
extraneous.forEach(function (extra) {
|
||||
var jsonEndpoint;
|
||||
|
||||
// Skip linked packages
|
||||
if (extra.linked) {
|
||||
return;
|
||||
}
|
||||
|
||||
jsonEndpoint = endpointParser.decomposed2json(extra.endpoint);
|
||||
mout.object.mixIn(json.dependencies, jsonEndpoint);
|
||||
});
|
||||
}
|
||||
|
||||
return [json, answers];
|
||||
});
|
||||
}
|
||||
|
||||
return [json, answers];
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
init.readOptions = function (argv) {
|
||||
return [];
|
||||
};
|
||||
|
||||
module.exports = init;
|
||||
|
||||
@@ -1,81 +1,51 @@
|
||||
// ==========================================
|
||||
// BOWER: Install API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var endpointParser = require('bower-endpoint-parser');
|
||||
var Project = require('../core/Project');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var nopt = require('nopt');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
function install(logger, endpoints, options, config) {
|
||||
var project;
|
||||
var decEndpoints;
|
||||
|
||||
var Manager = require('../core/manager');
|
||||
var config = require('../core/config');
|
||||
var source = require('../core/source');
|
||||
var save = require('../util/save');
|
||||
var help = require('./help');
|
||||
options = options || {};
|
||||
config = defaultConfig(config);
|
||||
if (options.save === undefined) {
|
||||
options.save = config.defaultSave;
|
||||
}
|
||||
project = new Project(config, logger);
|
||||
|
||||
var optionTypes = { help: Boolean, save: Boolean, 'save-dev': Boolean, force: Boolean, 'force-latest': Boolean, production: Boolean };
|
||||
var shorthand = { 'h': ['--help'], 'S': ['--save'], 'D': ['--save-dev'], 'f': ['--force'], 'F': ['--force-latest'], 'p': ['--production'] };
|
||||
// Convert endpoints to decomposed endpoints
|
||||
endpoints = endpoints || [];
|
||||
decEndpoints = endpoints.map(function (endpoint) {
|
||||
|
||||
module.exports = function (paths, options) {
|
||||
options = options || {};
|
||||
// handle @ as version divider
|
||||
var splitParts = endpoint.split('/');
|
||||
splitParts[splitParts.length - 1] = splitParts[splitParts.length - 1].replace('@', '#');
|
||||
endpoint = splitParts.join('/');
|
||||
|
||||
var emitter = new Emitter;
|
||||
var manager = new Manager(paths, {
|
||||
force: options.force,
|
||||
forceLatest: options['force-latest'],
|
||||
production: options.production
|
||||
});
|
||||
|
||||
manager
|
||||
.on('data', emitter.emit.bind(emitter, 'data'))
|
||||
.on('error', emitter.emit.bind(emitter, 'error'))
|
||||
.on('resolve', function (resolved) {
|
||||
// Handle save/save-dev
|
||||
if (resolved && (options.save || options['save-dev'])) {
|
||||
save(manager, paths, !options.save, emitter.emit.bind(emitter, 'end'));
|
||||
} else {
|
||||
emitter.emit('end');
|
||||
}
|
||||
})
|
||||
.resolve();
|
||||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
var paths = options.argv.remain.slice(1);
|
||||
|
||||
if (options.help) return help('install');
|
||||
return module.exports(paths, options);
|
||||
};
|
||||
|
||||
module.exports.completion = function (opts, cb) {
|
||||
var cache = path.join(config.completion, 'install.json');
|
||||
var done = function done(err, results) {
|
||||
if (err) return cb(err);
|
||||
var names = results.map(function (pkg) {
|
||||
return pkg.name;
|
||||
return endpointParser.decompose(endpoint);
|
||||
});
|
||||
|
||||
return cb(null, names);
|
||||
};
|
||||
return project.install(decEndpoints, options, config);
|
||||
}
|
||||
|
||||
fs.readFile(cache, function (err, body) {
|
||||
if (!err) return done(null, JSON.parse(body));
|
||||
// -------------------
|
||||
|
||||
// expected error, do the first request and cache the results
|
||||
source.all(function (err, results) {
|
||||
if (err) return cb(err);
|
||||
fs.writeFile(cache, JSON.stringify(results, null, 2), function (err) {
|
||||
done(err, results);
|
||||
});
|
||||
});
|
||||
});
|
||||
install.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
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-exact': {type: Boolean, shorthand: 'E'}
|
||||
}, argv);
|
||||
|
||||
var packages = options.argv.remain.slice(1);
|
||||
|
||||
delete options.argv;
|
||||
|
||||
return [packages, options];
|
||||
};
|
||||
|
||||
module.exports.completion.options = shorthand;
|
||||
module.exports = install;
|
||||
|
||||
@@ -1,121 +1,85 @@
|
||||
// ==========================================
|
||||
// BOWER: Link API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var path = require('path');
|
||||
var rimraf = require('../util/rimraf');
|
||||
var Q = require('q');
|
||||
var Project = require('../core/Project');
|
||||
var createLink = require('../util/createLink');
|
||||
var defaultConfig = require('../config');
|
||||
var relativeToBaseDir = require('../util/relativeToBaseDir');
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var nopt = require('nopt');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var mkdirp = require('mkdirp');
|
||||
var rimraf = require('rimraf');
|
||||
function link(logger, name, localName, config) {
|
||||
if (name) {
|
||||
return linkTo(logger, name, localName, config);
|
||||
} else {
|
||||
return linkSelf(logger, config);
|
||||
}
|
||||
}
|
||||
|
||||
var Manager = require('../core/manager');
|
||||
var help = require('./help');
|
||||
var template = require('../util/template');
|
||||
var config = require('../core/config');
|
||||
var isRepo = require('../util/is-repo');
|
||||
function linkSelf(logger, config) {
|
||||
var project;
|
||||
|
||||
var optionTypes = { help: Boolean };
|
||||
var shorthand = { 'h': ['--help'] };
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
function linkSelf(emitter) {
|
||||
var manager = new Manager;
|
||||
return project.getJson()
|
||||
.then(function (json) {
|
||||
var src = config.cwd;
|
||||
var dst = path.join(config.storage.links, json.name);
|
||||
|
||||
manager
|
||||
.on('error', emitter.emit.bind('error'))
|
||||
.once('loadJSON', function () {
|
||||
var destPath = path.join(config.links, manager.name);
|
||||
var srcPath = process.cwd();
|
||||
|
||||
deleteLink(destPath, function (err) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
|
||||
createLink(srcPath, destPath, function (err) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
|
||||
template('link', { src: srcPath, dest: destPath })
|
||||
.on('data', emitter.emit.bind(emitter, 'end'));
|
||||
// Delete previous link if any
|
||||
return Q.nfcall(rimraf, dst)
|
||||
// Link globally
|
||||
.then(function () {
|
||||
return createLink(src, dst);
|
||||
})
|
||||
.then(function () {
|
||||
return {
|
||||
src: src,
|
||||
dst: dst
|
||||
};
|
||||
});
|
||||
});
|
||||
}).loadJSON();
|
||||
}
|
||||
|
||||
function linkTo(name, emitter) {
|
||||
var destPath = path.join(process.cwd(), config.directory, name);
|
||||
var srcPath = path.join(config.links, name);
|
||||
|
||||
deleteLink(destPath, function (err) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
|
||||
createLink(srcPath, destPath, function (err) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
|
||||
template('link', { src: srcPath, dest: destPath })
|
||||
.on('data', emitter.emit.bind(emitter, 'end'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deleteLink(dest, callback) {
|
||||
// Delete symlink if already present
|
||||
// Beware that if the target is a git repo, we can't proceed
|
||||
isRepo(dest, function (is) {
|
||||
if (is) return callback(new Error(dest + ' is a local repository, please remove it manually'));
|
||||
function linkTo(logger, name, localName, config) {
|
||||
var src;
|
||||
var dst;
|
||||
var project;
|
||||
|
||||
fs.lstat(dest, function (err) {
|
||||
if (!err || err.code !== 'ENOENT') rimraf(dest, callback);
|
||||
else callback();
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
localName = localName || name;
|
||||
src = path.join(config.storage.links, name);
|
||||
dst = path.join(relativeToBaseDir(config.cwd)(config.directory), localName);
|
||||
|
||||
// Delete destination folder if any
|
||||
return Q.nfcall(rimraf, dst)
|
||||
// Link locally
|
||||
.then(function () {
|
||||
return createLink(src, dst);
|
||||
})
|
||||
// Install linked package deps
|
||||
.then(function () {
|
||||
return project.update([localName]);
|
||||
})
|
||||
.then(function (installed) {
|
||||
return {
|
||||
src: src,
|
||||
dst: dst,
|
||||
installed: installed
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createLink(src, dest, callback) {
|
||||
var destDir = path.dirname(dest);
|
||||
// -------------------
|
||||
|
||||
// Create directory
|
||||
mkdirp(destDir, function (err) {
|
||||
if (err) return callback(err);
|
||||
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];
|
||||
|
||||
fs.lstat(src, function (err) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
return callback(new Error('Attempting to link an unknown package: ' + path.basename(src)));
|
||||
}
|
||||
|
||||
// Create symlink
|
||||
fs.symlink(src, dest, 'dir', function (err) {
|
||||
callback(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function (name) {
|
||||
var emitter = new Emitter;
|
||||
|
||||
if (!name) linkSelf(emitter);
|
||||
else linkTo(name, emitter);
|
||||
|
||||
return emitter;
|
||||
return [name, localName];
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
var name = options.argv.remain[1];
|
||||
|
||||
if (options.help) return help('link');
|
||||
return module.exports(name);
|
||||
};
|
||||
|
||||
module.exports.completion = function (opts, cb) {
|
||||
fs.readdir(config.links, function (err, dirs) {
|
||||
// ignore ENOENT, ~/.bower/links not created yet
|
||||
if (err && err.code === 'ENOENT') return cb(null, []);
|
||||
cb(err, dirs);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.completion.options = shorthand;
|
||||
module.exports = link;
|
||||
|
||||
@@ -1,230 +1,166 @@
|
||||
// ==========================================
|
||||
// BOWER: List API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
var Project = require('../core/Project');
|
||||
var semver = require('../util/semver');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var archy = require('archy');
|
||||
var nopt = require('nopt');
|
||||
var path = require('path');
|
||||
var _ = require('lodash');
|
||||
function list(logger, options, config) {
|
||||
var project;
|
||||
|
||||
var template = require('../util/template');
|
||||
var Manager = require('../core/manager');
|
||||
var Package = require('../core/package');
|
||||
var config = require('../core/config');
|
||||
var help = require('./help');
|
||||
options = options || {};
|
||||
|
||||
var shorthand = { 'h': ['--help'], 'o': ['--offline'] };
|
||||
var optionTypes = { help: Boolean, paths: Boolean, map: Boolean, offline: Boolean, sources: Boolean };
|
||||
// Make relative option true by default when used with paths
|
||||
if (options.paths && options.relative == null) {
|
||||
options.relative = true;
|
||||
}
|
||||
|
||||
var getTree = function (packages, subPackages, result) {
|
||||
result = result || {};
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
_.each(subPackages || packages, function (pkg) {
|
||||
return project.getTree(options)
|
||||
.spread(function (tree, flattened) {
|
||||
// Relativize paths
|
||||
// Also normalize paths on windows
|
||||
project.walkTree(tree, function (node) {
|
||||
if (node.missing) {
|
||||
return;
|
||||
}
|
||||
|
||||
result[pkg.name] = {};
|
||||
if (options.relative) {
|
||||
node.canonicalDir = path.relative(config.cwd, node.canonicalDir);
|
||||
}
|
||||
if (options.paths) {
|
||||
node.canonicalDir = normalize(node.canonicalDir);
|
||||
}
|
||||
}, true);
|
||||
|
||||
Object.keys(pkg.json.dependencies || {}).forEach(function (name) {
|
||||
result[pkg.name][name] = {};
|
||||
// Note that we need to to parse the flattened tree because it might
|
||||
// contain additional packages
|
||||
mout.object.forOwn(flattened, function (node) {
|
||||
if (node.missing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.relative) {
|
||||
node.canonicalDir = path.relative(config.cwd, node.canonicalDir);
|
||||
}
|
||||
if (options.paths) {
|
||||
node.canonicalDir = normalize(node.canonicalDir);
|
||||
}
|
||||
});
|
||||
|
||||
// Render paths?
|
||||
if (options.paths) {
|
||||
return paths(flattened);
|
||||
}
|
||||
|
||||
// Do not check for new versions?
|
||||
if (config.offline) {
|
||||
return tree;
|
||||
}
|
||||
|
||||
// Check for new versions
|
||||
return checkVersions(project, tree, logger)
|
||||
.then(function () {
|
||||
return tree;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function checkVersions(project, tree, logger) {
|
||||
var promises;
|
||||
var nodes = [];
|
||||
var repository = project.getPackageRepository();
|
||||
|
||||
// Gather all nodes, ignoring linked nodes
|
||||
project.walkTree(tree, function (node) {
|
||||
if (!node.linked) {
|
||||
nodes.push(node);
|
||||
}
|
||||
}, true);
|
||||
|
||||
if (nodes.length) {
|
||||
logger.info('check-new', 'Checking for new versions of the project dependencies...');
|
||||
}
|
||||
|
||||
// Check for new versions for each node
|
||||
promises = nodes.map(function (node) {
|
||||
var target = node.endpoint.target;
|
||||
|
||||
return repository.versions(node.endpoint.source)
|
||||
.then(function (versions) {
|
||||
node.versions = versions;
|
||||
|
||||
// Do not check if node's target is not a valid semver one
|
||||
if (versions.length && semver.validRange(target)) {
|
||||
node.update = {
|
||||
target: semver.maxSatisfying(versions, target),
|
||||
latest: semver.maxSatisfying(versions, '*')
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var subPackages = {};
|
||||
// Set the versions also for the root node
|
||||
tree.versions = [];
|
||||
|
||||
Object.keys(pkg.json.dependencies || {}).forEach(function (name) {
|
||||
subPackages[name] = packages[name] || new Package(name, null);
|
||||
return Q.all(promises);
|
||||
}
|
||||
|
||||
function paths(flattened) {
|
||||
var ret = {};
|
||||
|
||||
mout.object.forOwn(flattened, function (pkg, name) {
|
||||
var main;
|
||||
|
||||
if (pkg.missing) {
|
||||
return;
|
||||
}
|
||||
|
||||
main = pkg.pkgMeta.main;
|
||||
|
||||
// If no main was specified, fallback to canonical dir
|
||||
if (!main) {
|
||||
ret[name] = pkg.canonicalDir;
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize main
|
||||
if (typeof main === 'string') {
|
||||
main = [main];
|
||||
}
|
||||
|
||||
// Concatenate each main entry with the canonical dir
|
||||
main = main.map(function (part) {
|
||||
return normalize(path.join(pkg.canonicalDir, part).trim());
|
||||
});
|
||||
|
||||
// If only one main file, use a string
|
||||
// Otherwise use an array
|
||||
ret[name] = main.length === 1 ? main[0] : main;
|
||||
});
|
||||
|
||||
getTree(packages, subPackages, result[pkg.name]);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
return result;
|
||||
function normalize(src) {
|
||||
return src.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
list.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
var options = cli.readOptions({
|
||||
'paths': { type: Boolean, shorthand: 'p' },
|
||||
'relative': { type: Boolean, shorthand: 'r' }
|
||||
}, argv);
|
||||
|
||||
delete options.argv;
|
||||
|
||||
return [options];
|
||||
};
|
||||
|
||||
var generatePath = function (name, main) {
|
||||
if (typeof main === 'string') {
|
||||
return path.join(config.directory, name, main);
|
||||
} else if (_.isArray(main)) {
|
||||
main = main.map(function (main) { return generatePath(name, main); });
|
||||
return main.length === 1 ? main[0] : main;
|
||||
}
|
||||
};
|
||||
|
||||
var buildSource = function (pkg, shallow) {
|
||||
var result = {};
|
||||
|
||||
if (pkg) {
|
||||
['main', 'scripts', 'styles', 'templates', 'images'].forEach(function (type) {
|
||||
if (pkg.json[type]) result[type] = generatePath(pkg.name, pkg.json[type]);
|
||||
});
|
||||
}
|
||||
|
||||
if (shallow) {
|
||||
result.main = result.main ? result.main
|
||||
: result.scripts ? result.scripts
|
||||
: result.styles ? result.styles
|
||||
: result.templates ? result.templates
|
||||
: result.images ? result.images
|
||||
: generatePath(pkg.name, '');
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
var shallowTree = function (packages, tree) {
|
||||
var result = {};
|
||||
|
||||
Object.keys(tree).forEach(function (packageName) {
|
||||
result[packageName] = buildSource(packages[packageName], true).main;
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
var deepTree = function (packages, tree) {
|
||||
|
||||
var result = {};
|
||||
|
||||
Object.keys(tree).forEach(function (packageName) {
|
||||
|
||||
result[packageName] = {};
|
||||
result[packageName].source = buildSource(packages[packageName]);
|
||||
|
||||
if (Object.keys(tree[packageName]).length) {
|
||||
result[packageName].dependencies = deepTree(packages, tree[packageName]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
var getNodes = function (packages, tree) {
|
||||
return Object.keys(tree).map(function (key) {
|
||||
var version = packages[key] ? packages[key].version || '' : null;
|
||||
var upgrade;
|
||||
|
||||
if (version && packages[key].tags.indexOf(version)) {
|
||||
upgrade = packages[key].tags[0];
|
||||
}
|
||||
|
||||
if (Object.keys(tree[key]).length) {
|
||||
return {
|
||||
label: template('tree-branch', { 'package': key, version: version, upgrade: upgrade }, true),
|
||||
nodes: getNodes(packages, tree[key])
|
||||
};
|
||||
} else {
|
||||
return template('tree-branch', { 'package': key, version: version, upgrade: upgrade }, true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var getDependencySrcs = function (list) {
|
||||
var srcs = [];
|
||||
var dependency, main;
|
||||
for (var name in list) {
|
||||
dependency = list[name];
|
||||
main = dependency.source && dependency.source.main;
|
||||
|
||||
if (dependency.dependencies) {
|
||||
var depSrcs = getDependencySrcs(dependency.dependencies);
|
||||
srcs.push.apply(srcs, depSrcs);
|
||||
}
|
||||
|
||||
// add main sources to srcs
|
||||
if (main) {
|
||||
if (Array.isArray(main)) {
|
||||
srcs.push.apply(srcs, main);
|
||||
} else {
|
||||
srcs.push(main);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return srcs;
|
||||
};
|
||||
|
||||
var organizeSources = function (tree) {
|
||||
// flat source filepaths
|
||||
var srcs = getDependencySrcs(tree);
|
||||
// remove duplicates, organize by file extension
|
||||
var sources = {};
|
||||
|
||||
srcs.forEach(function (src) {
|
||||
var ext = path.extname(src);
|
||||
sources[ext] = sources[ext] || [];
|
||||
if (sources[ext].indexOf(src) === -1) {
|
||||
sources[ext].push(src);
|
||||
}
|
||||
});
|
||||
|
||||
return sources;
|
||||
};
|
||||
|
||||
module.exports = function (options) {
|
||||
var manager = new Manager;
|
||||
var emitter = new Emitter;
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (options.sources) {
|
||||
options.map = true;
|
||||
}
|
||||
|
||||
var emitOut = function (obj) {
|
||||
// make JSON pretty if started from command line
|
||||
var output = options.argv ? JSON.stringify(obj, null, 2) : obj;
|
||||
emitter.emit('data', output);
|
||||
};
|
||||
manager
|
||||
.on('data', emitter.emit.bind(emitter, 'data'))
|
||||
.on('error', emitter.emit.bind(emitter, 'error'))
|
||||
.on('list', function (packages) {
|
||||
// console.log(packages);
|
||||
// listTree(packages, options);
|
||||
var tree = getTree(packages);
|
||||
if (!options.paths && !options.map) {
|
||||
emitter.emit('data', archy({
|
||||
label: process.cwd(),
|
||||
nodes: getNodes(packages, tree)
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
tree = options.paths ? shallowTree(packages, tree) : deepTree(packages, tree);
|
||||
|
||||
if (options.sources) {
|
||||
// with map, organize it and emit
|
||||
var sources = organizeSources(tree);
|
||||
emitOut(sources);
|
||||
} else {
|
||||
emitOut(tree);
|
||||
}
|
||||
|
||||
})
|
||||
.list(options);
|
||||
|
||||
return emitter;
|
||||
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
if (options.help) return help('list');
|
||||
return module.exports(options);
|
||||
};
|
||||
|
||||
module.exports.completion = function (opts, cb) {
|
||||
if (!/^-/.test(opts.word)) return cb(null, []);
|
||||
|
||||
var results = Object.keys(optionTypes).map(function (option) {
|
||||
return '--' + option;
|
||||
});
|
||||
|
||||
cb(null, results);
|
||||
};
|
||||
|
||||
module.exports.completion.options = shorthand;
|
||||
module.exports = list;
|
||||
|
||||
125
lib/commands/login.js
Normal file
125
lib/commands/login.js
Normal file
@@ -0,0 +1,125 @@
|
||||
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);
|
||||
logger.info('EAUTH', 'Logged in as ' + configstore.get('username'), {});
|
||||
|
||||
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);
|
||||
logger.info('EAUTH', 'Logged in as ' + configstore.get('username'), {});
|
||||
|
||||
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,64 +1,34 @@
|
||||
// ==========================================
|
||||
// BOWER: Lookup API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var Q = require('q');
|
||||
var PackageRepository = require('../core/PackageRepository');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var nopt = require('nopt');
|
||||
|
||||
var template = require('../util/template');
|
||||
var source = require('../core/source');
|
||||
var install = require('./install');
|
||||
var help = require('./help');
|
||||
|
||||
var optionTypes = { help: Boolean };
|
||||
var shorthand = { 'h': ['--help'] };
|
||||
|
||||
module.exports = function (name) {
|
||||
var emitter = new Emitter;
|
||||
|
||||
source.lookup(name, function (err, url) {
|
||||
if (err) {
|
||||
source.search(name, function (err, packages) {
|
||||
if (packages.length) {
|
||||
template('suggestions', { packages: packages, name: name })
|
||||
.on('data', function (data) {
|
||||
emitter.emit('data', data);
|
||||
emitter.emit('end');
|
||||
});
|
||||
} else {
|
||||
template('warning-missing', {name: name})
|
||||
.on('data', function (data) {
|
||||
emitter.emit('data', data);
|
||||
emitter.emit('end');
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var result = { name: name, url: url };
|
||||
emitter.emit('package', result);
|
||||
|
||||
template('lookup', result)
|
||||
.on('data', function (data) {
|
||||
emitter.emit('data', data);
|
||||
emitter.emit('end');
|
||||
});
|
||||
function lookup(logger, name, config) {
|
||||
if (!name) {
|
||||
return new Q(null);
|
||||
}
|
||||
});
|
||||
|
||||
return emitter;
|
||||
config = defaultConfig(config);
|
||||
|
||||
var repository = new PackageRepository(config, logger);
|
||||
var registryClient = repository.getRegistryClient();
|
||||
|
||||
return Q.nfcall(registryClient.lookup.bind(registryClient), name)
|
||||
.then(function (entry) {
|
||||
return !entry ? null : {
|
||||
name: name,
|
||||
url: entry.url
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
lookup.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
var options = cli.readOptions(argv);
|
||||
var name = options.argv.remain[1];
|
||||
|
||||
return [name];
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
var names = options.argv.remain.slice(1);
|
||||
|
||||
if (options.help || !names.length) return help('lookup');
|
||||
return module.exports(names[0]);
|
||||
};
|
||||
|
||||
module.exports.completion = install.completion;
|
||||
module.exports.completion.options = shorthand;
|
||||
module.exports = lookup;
|
||||
|
||||
55
lib/commands/prune.js
Normal file
55
lib/commands/prune.js
Normal file
@@ -0,0 +1,55 @@
|
||||
var mout = require('mout');
|
||||
var Project = require('../core/Project');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
function prune(logger, options, config) {
|
||||
var project;
|
||||
|
||||
options = options || {};
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
return clean(project, options);
|
||||
}
|
||||
|
||||
function clean(project, options, removed) {
|
||||
removed = removed || {};
|
||||
|
||||
// Continually call clean until there is no more extraneous
|
||||
// dependencies to remove
|
||||
return project.getTree(options)
|
||||
.spread(function (tree, flattened, extraneous) {
|
||||
var names = extraneous.map(function (extra) {
|
||||
return extra.endpoint.name;
|
||||
});
|
||||
|
||||
// Uninstall extraneous
|
||||
return project.uninstall(names, options)
|
||||
.then(function (uninstalled) {
|
||||
// Are we done?
|
||||
if (!mout.object.size(uninstalled)) {
|
||||
return removed;
|
||||
}
|
||||
|
||||
// Not yet, recurse!
|
||||
mout.object.mixIn(removed, uninstalled);
|
||||
return clean(project, options, removed);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
prune.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
var options = cli.readOptions({
|
||||
'production': { type: Boolean, shorthand: 'p' },
|
||||
}, argv);
|
||||
|
||||
delete options.argv;
|
||||
|
||||
return [options];
|
||||
};
|
||||
|
||||
module.exports = prune;
|
||||
@@ -1,66 +1,88 @@
|
||||
// ==========================================
|
||||
// BOWER: Lookup API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var Q = require('q');
|
||||
var chalk = require('chalk');
|
||||
var PackageRepository = require('../core/PackageRepository');
|
||||
var createError = require('../util/createError');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var nopt = require('nopt');
|
||||
var readline = require('readline');
|
||||
function register(logger, name, source, config) {
|
||||
var repository;
|
||||
var registryClient;
|
||||
var force;
|
||||
var url;
|
||||
var githubSourceRegex = /^\w[\w-]*\/\w[\w-]*$/;
|
||||
var getGithubUrl = function (source) {
|
||||
return 'git@github.com:' + source + '.git';
|
||||
};
|
||||
|
||||
var template = require('../util/template');
|
||||
var source = require('../core/source');
|
||||
var help = require('./help');
|
||||
config = defaultConfig(config);
|
||||
force = config.force;
|
||||
|
||||
var optionTypes = { help: Boolean };
|
||||
var shorthand = { 'h': ['--help'], '-s': ['--silent'] };
|
||||
name = (name || '').trim();
|
||||
source = (source || '').trim();
|
||||
|
||||
var register = function (name, url, emitter) {
|
||||
source.register(name, url, function (err) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
url = source.match(githubSourceRegex) ? getGithubUrl(source) : source;
|
||||
|
||||
template('register', {name: name, url: url})
|
||||
.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
});
|
||||
};
|
||||
// Bypass any cache
|
||||
config.offline = false;
|
||||
config.force = true;
|
||||
|
||||
module.exports = function (name, url, options) {
|
||||
var emitter = new Emitter;
|
||||
return Q.try(function () {
|
||||
// Verify name and url
|
||||
if (!name || !url) {
|
||||
throw createError('Usage: bower register <name> <url>', 'EINVFORMAT');
|
||||
}
|
||||
|
||||
if (options.silent) register(name, url, emitter);
|
||||
else {
|
||||
var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
// Attempt to resolve the package referenced by the URL to ensure
|
||||
// everything is ok before registering
|
||||
repository = new PackageRepository(config, logger);
|
||||
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');
|
||||
}
|
||||
|
||||
console.log('Registering a package will make it visible and installable via the registry.');
|
||||
rl.question('Proceed (y/n)? ', function (res) {
|
||||
rl.close();
|
||||
// If non interactive or user forced, bypass confirmation
|
||||
if (!config.interactive || force) {
|
||||
return true;
|
||||
}
|
||||
|
||||
res = res.toLowerCase();
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (res === 'y' || res === 'yes') register(name, url, emitter);
|
||||
// Register
|
||||
registryClient = repository.getRegistryClient();
|
||||
|
||||
logger.action('register', url, {
|
||||
name: name,
|
||||
url: url
|
||||
});
|
||||
|
||||
return Q.nfcall(registryClient.register.bind(registryClient), name, url);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return emitter;
|
||||
// -------------------
|
||||
|
||||
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];
|
||||
|
||||
return [name, url];
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
var args = options.argv.remain.slice(1);
|
||||
|
||||
if (options.help || args.length !== 2) return help('register');
|
||||
return module.exports(args[0], args[1], options);
|
||||
};
|
||||
|
||||
module.exports.completion = function (opts, cb) {
|
||||
var word = opts.word;
|
||||
|
||||
// completing options?
|
||||
if (word.charAt(0) === '-') {
|
||||
return cb(null, Object.keys(optionTypes).map(function (option) {
|
||||
return '--' + option;
|
||||
}));
|
||||
}
|
||||
};
|
||||
module.exports = register;
|
||||
|
||||
@@ -1,61 +1,39 @@
|
||||
// ==========================================
|
||||
// BOWER: Lookup API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var Q = require('q');
|
||||
var PackageRepository = require('../core/PackageRepository');
|
||||
var defaultConfig = require('../config');
|
||||
var cli = require('../util/cli');
|
||||
var createError = require('../util/createError');
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var nopt = require('nopt');
|
||||
function search(logger, name, config) {
|
||||
var registryClient;
|
||||
|
||||
var template = require('../util/template');
|
||||
var source = require('../core/source');
|
||||
var install = require('./install');
|
||||
var help = require('./help');
|
||||
config = defaultConfig(config);
|
||||
|
||||
var optionTypes = { help: Boolean };
|
||||
var shorthand = { 'h': ['--help'] };
|
||||
var repository = new PackageRepository(config, logger);
|
||||
var registryClient = repository.getRegistryClient();
|
||||
|
||||
module.exports = function (name) {
|
||||
var emitter = new Emitter;
|
||||
|
||||
var callback = function (err, results) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
|
||||
emitter.emit('packages', results);
|
||||
|
||||
if (results.length) {
|
||||
template('search', {results: results})
|
||||
.on('data', function (data) {
|
||||
emitter.emit('data', data);
|
||||
emitter.emit('end');
|
||||
});
|
||||
if (name) {
|
||||
return Q.nfcall(registryClient.search.bind(registryClient), name);
|
||||
} else {
|
||||
template('search-empty', {results: results})
|
||||
.on('data', function (data) {
|
||||
emitter.emit('data', data);
|
||||
emitter.emit('end');
|
||||
});
|
||||
// List all packages when in interactive mode + json enabled, and
|
||||
// always when in non-interactive mode
|
||||
if (config.interactive && !config.json) {
|
||||
throw createError('no parameter to bower search', 'EREADOPTIONS');
|
||||
}
|
||||
|
||||
return Q.nfcall(registryClient.list.bind(registryClient));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (name) {
|
||||
source.search(name, callback);
|
||||
} else {
|
||||
source.all(callback);
|
||||
}
|
||||
// -------------------
|
||||
|
||||
return emitter;
|
||||
search.readOptions = function (argv) {
|
||||
var options = cli.readOptions(argv);
|
||||
var terms = options.argv.remain.slice(1);
|
||||
|
||||
var name = terms.join(' ');
|
||||
|
||||
return [name];
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
var names = options.argv.remain.slice(1);
|
||||
|
||||
if (options.help) return help('search');
|
||||
return module.exports(names[0]);
|
||||
};
|
||||
|
||||
module.exports.completion = install.completion;
|
||||
module.exports.completion.options = shorthand;
|
||||
module.exports = search;
|
||||
|
||||
@@ -1,202 +1,115 @@
|
||||
// ==========================================
|
||||
// BOWER: Uninstall API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
var Project = require('../core/Project');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var async = require('async');
|
||||
var nopt = require('nopt');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var _ = require('lodash');
|
||||
function uninstall(logger, names, options, config) {
|
||||
if (!names.length) {
|
||||
return new Q();
|
||||
}
|
||||
|
||||
var template = require('../util/template');
|
||||
var Manager = require('../core/manager');
|
||||
var config = require('../core/config');
|
||||
var help = require('./help');
|
||||
var project;
|
||||
|
||||
var optionTypes = { help: Boolean, force: Boolean, save: Boolean };
|
||||
var shorthand = { 'h': ['--help'], 'S': ['--save'], 'D': ['--save-dev'], 'f': ['--force'] };
|
||||
options = options || {};
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
module.exports = function (names, options) {
|
||||
var packages, uninstallables, packagesCount = {};
|
||||
var emitter = new Emitter;
|
||||
var manager = new Manager;
|
||||
var jsonDeps;
|
||||
return project.getTree(options)
|
||||
.spread(function (tree, flattened) {
|
||||
// Uninstall nodes
|
||||
return project.uninstall(names, options)
|
||||
// Clean out non-shared uninstalled dependencies
|
||||
.then(function (uninstalled) {
|
||||
var names = Object.keys(uninstalled);
|
||||
var children = [];
|
||||
|
||||
options = options || {};
|
||||
// Grab the dependencies of packages that were uninstalled
|
||||
mout.object.forOwn(flattened, function (node) {
|
||||
if (names.indexOf(node.endpoint.name) !== -1) {
|
||||
children.push.apply(children, mout.object.keys(node.dependencies));
|
||||
}
|
||||
});
|
||||
|
||||
manager.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
manager.on('error', emitter.emit.bind(emitter, 'error'));
|
||||
|
||||
var resolveLocal = function () {
|
||||
jsonDeps = manager.json.dependencies || {};
|
||||
packages = _.flatten(_.values(manager.dependencies));
|
||||
uninstallables = packages.filter(function (pkg) {
|
||||
return _.include(names, pkg.name);
|
||||
// Clean them!
|
||||
return clean(project, children, uninstalled);
|
||||
});
|
||||
});
|
||||
async.forEach(packages, function (pkg, next) {
|
||||
pkg.once('loadJSON', next).loadJSON();
|
||||
}, function () {
|
||||
if (showWarnings(options.force) && !options.force) return;
|
||||
expandUninstallabes(options.force);
|
||||
uninstall();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
var showWarnings = function (force) {
|
||||
var foundConflicts = false;
|
||||
function clean(project, names, removed) {
|
||||
removed = removed || {};
|
||||
|
||||
packages.forEach(function (pkg) {
|
||||
if (!pkg.json.dependencies) return;
|
||||
if (containsPkg(uninstallables, pkg)) return;
|
||||
return project.getTree()
|
||||
.spread(function (tree, flattened) {
|
||||
var nodes = [];
|
||||
var dependantsCounter = {};
|
||||
|
||||
var conflicts = _.intersection(
|
||||
Object.keys(pkg.json.dependencies),
|
||||
_.pluck(uninstallables, 'name')
|
||||
);
|
||||
// Grab the nodes of each specified name
|
||||
mout.object.forOwn(flattened, function (node) {
|
||||
if (names.indexOf(node.endpoint.name) !== -1) {
|
||||
nodes.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
if (conflicts.length) {
|
||||
foundConflicts = true;
|
||||
if (!force) {
|
||||
conflicts.forEach(function (conflictName) {
|
||||
emitter.emit('data', template('warning-uninstall', { packageName: pkg.name, conflictName: conflictName }, true));
|
||||
});
|
||||
// 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 !dependantsCounter[node.endpoint.name];
|
||||
});
|
||||
|
||||
// Are we done?
|
||||
if (!nodes.length) {
|
||||
return Q.resolve(removed);
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the nodes after filtering
|
||||
names = nodes.map(function (node) {
|
||||
return node.endpoint.name;
|
||||
});
|
||||
|
||||
// Uninstall them
|
||||
return project.uninstall(names)
|
||||
// Clean out non-shared uninstalled dependencies
|
||||
.then(function (uninstalled) {
|
||||
var children;
|
||||
|
||||
mout.object.mixIn(removed, uninstalled);
|
||||
|
||||
// Grab the dependencies of packages that were uninstalled
|
||||
children = [];
|
||||
nodes.forEach(function (node) {
|
||||
children.push.apply(children, mout.object.keys(node.dependencies));
|
||||
});
|
||||
|
||||
// Recurse!
|
||||
return clean(project, children, removed);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (foundConflicts && !force) {
|
||||
emitter.emit('data', template('warn', { message: 'To proceed, run uninstall with the --force flag'}, true));
|
||||
}
|
||||
// -------------------
|
||||
|
||||
return foundConflicts;
|
||||
};
|
||||
uninstall.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
var expandUninstallabes = function (force) {
|
||||
var x,
|
||||
pkg,
|
||||
forcedUninstallables = {};
|
||||
var options = cli.readOptions({
|
||||
'save': { type: Boolean, shorthand: 'S' },
|
||||
'save-dev': { type: Boolean, shorthand: 'D' }
|
||||
}, argv);
|
||||
|
||||
// Direct JSON deps have a count of 1
|
||||
for (var key in jsonDeps) {
|
||||
packagesCount[key] = 1;
|
||||
}
|
||||
var names = options.argv.remain.slice(1);
|
||||
|
||||
// Count all packages
|
||||
count(packages, packagesCount);
|
||||
delete options.argv;
|
||||
|
||||
if (force) {
|
||||
uninstallables.forEach(function (pkg) {
|
||||
forcedUninstallables[pkg.name] = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Expand the uninstallables deps and nested deps
|
||||
// Also update the count accordingly
|
||||
for (x = uninstallables.length - 1; x >= 0; x -= 1) {
|
||||
parseUninstallableDeps(uninstallables[x]);
|
||||
}
|
||||
|
||||
// Foreach uninstallable, check if it is really to be removed by reading the final count
|
||||
// If the final count is greater than 0, then it is a shared dep
|
||||
// In that case, we remove it from the uninstallables unless it's forced to be uninstalled
|
||||
for (x = uninstallables.length - 1; x >= 0; x -= 1) {
|
||||
pkg = uninstallables[x];
|
||||
if (packagesCount[pkg.name] > 0 && !forcedUninstallables[pkg.name]) uninstallables.splice(x, 1);
|
||||
}
|
||||
};
|
||||
|
||||
var count = function (packages, counts, nested) {
|
||||
packages.forEach(function (pkg) {
|
||||
counts[pkg.name] = (counts[pkg.name] || 0);
|
||||
if (nested) counts[pkg.name] += 1;
|
||||
|
||||
if (pkg.json.dependencies) {
|
||||
for (var key in pkg.json.dependencies) {
|
||||
count(manager.dependencies[key], counts, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var parseUninstallableDeps = function (pkg) {
|
||||
if (!containsPkg(uninstallables, pkg)) uninstallables.push(pkg);
|
||||
packagesCount[pkg.name] -= 1;
|
||||
|
||||
if (pkg.json.dependencies) {
|
||||
for (var key in pkg.json.dependencies) {
|
||||
parseUninstallableDeps(manager.dependencies[key][0]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var containsPkg = function (packages, pkg) {
|
||||
for (var x = packages.length - 1; x >= 0; x -= 1) {
|
||||
if (packages[x].name === pkg.name) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
var uninstall = function () {
|
||||
async.forEach(uninstallables, function (pkg, next) {
|
||||
pkg.on('uninstall', function () {
|
||||
emitter.emit('package', pkg);
|
||||
next();
|
||||
}).uninstall();
|
||||
}, function () {
|
||||
// Finally save
|
||||
if (options.save || options['save-dev']) save(!options.save);
|
||||
emitter.emit('end');
|
||||
});
|
||||
};
|
||||
|
||||
var save = function (dev) {
|
||||
var key = dev ? 'devDependencies' : 'dependencies';
|
||||
|
||||
if (manager.json[key]) {
|
||||
names.forEach(function (name) {
|
||||
delete manager.json[key][name];
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(manager.cwd, config.json), JSON.stringify(manager.json, null, 2));
|
||||
}
|
||||
};
|
||||
|
||||
manager.on('loadJSON', function () {
|
||||
manager.on('resolveLocal', resolveLocal).resolveLocal();
|
||||
}).loadJSON();
|
||||
|
||||
return emitter;
|
||||
return [names, options];
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
var names = options.argv.remain.slice(1);
|
||||
|
||||
if (options.help || !names.length) return help('uninstall');
|
||||
return module.exports(names, options);
|
||||
};
|
||||
|
||||
module.exports.completion = function (opts, cb) {
|
||||
var word = opts.word;
|
||||
|
||||
// completing options?
|
||||
if (opts.words[0] === 'uninstall' && word.charAt(0) === '-') {
|
||||
return cb(null, Object.keys(optionTypes).map(function (option) {
|
||||
return '--' + option;
|
||||
}));
|
||||
}
|
||||
|
||||
fs.readdir(config.directory, function (err, dirs) {
|
||||
// ignore ENOENT, ./components not created yet
|
||||
if (err && err.code === 'ENOENT') return cb(null, []);
|
||||
cb(err, dirs);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.completion.options = shorthand;
|
||||
module.exports = uninstall;
|
||||
|
||||
79
lib/commands/unregister.js
Normal file
79
lib/commands/unregister.js
Normal file
@@ -0,0 +1,79 @@
|
||||
var chalk = require('chalk');
|
||||
var Q = require('q');
|
||||
|
||||
var defaultConfig = require('../config');
|
||||
var PackageRepository = require('../core/PackageRepository');
|
||||
var createError = require('../util/createError');
|
||||
|
||||
function unregister(logger, name, config) {
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
var repository;
|
||||
var registryClient;
|
||||
var force;
|
||||
|
||||
config = defaultConfig(config);
|
||||
force = config.force;
|
||||
|
||||
// Bypass any cache
|
||||
config.offline = false;
|
||||
config.force = true;
|
||||
|
||||
// Trim name
|
||||
name = name.trim();
|
||||
|
||||
repository = new PackageRepository(config, logger);
|
||||
|
||||
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) {
|
||||
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,94 +1,36 @@
|
||||
// ==========================================
|
||||
// BOWER: Update API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var Project = require('../core/Project');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var async = require('async');
|
||||
var nopt = require('nopt');
|
||||
var _ = require('lodash');
|
||||
function update(logger, names, options, config) {
|
||||
var project;
|
||||
|
||||
var Manager = require('../core/manager');
|
||||
var help = require('./help');
|
||||
var uninstall = require('./uninstall');
|
||||
var save = require('../util/save');
|
||||
options = options || {};
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
var optionTypes = { help: Boolean, save: Boolean, force: Boolean, 'force-latest': Boolean };
|
||||
var shorthand = { 'h': ['--help'], 'S': ['--save'], 'f': ['--force'], 'F': ['--force-latest'] };
|
||||
// If names is an empty array, null them
|
||||
if (names && !names.length) {
|
||||
names = null;
|
||||
}
|
||||
|
||||
module.exports = function (names, options) {
|
||||
options = options || {};
|
||||
return project.update(names, options);
|
||||
}
|
||||
|
||||
var emitter = new Emitter;
|
||||
var manager = new Manager([], {
|
||||
force: options.force,
|
||||
forceLatest: options['force-latest']
|
||||
});
|
||||
// -------------------
|
||||
|
||||
manager.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
manager.on('error', emitter.emit.bind(emitter, 'error'));
|
||||
update.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
var installURLS = function (err, arr) {
|
||||
var mappings = {},
|
||||
endpoints = [];
|
||||
var options = cli.readOptions({
|
||||
'force-latest': { type: Boolean, shorthand: 'F' },
|
||||
'production': { type: Boolean, shorthand: 'p' }
|
||||
}, argv);
|
||||
|
||||
arr = _.compact(arr);
|
||||
_.each(arr, function (info) {
|
||||
endpoints.push(info.endpoint);
|
||||
mappings[info.endpoint] = info.name;
|
||||
});
|
||||
var names = options.argv.remain.slice(1);
|
||||
|
||||
options.endpointNames = mappings;
|
||||
delete options.argv;
|
||||
|
||||
// By default the manager will guess the name of the package from the url
|
||||
// But this leads to problems when the package name does not match the one in the url
|
||||
// So the manager now has an option (endpointNames) to deal with this
|
||||
manager = new Manager(endpoints, options);
|
||||
|
||||
manager
|
||||
.on('data', emitter.emit.bind(emitter, 'data'))
|
||||
.on('error', emitter.emit.bind(emitter, 'error'))
|
||||
.on('resolve', function (resolved) {
|
||||
// Handle save
|
||||
if (resolved && options.save) save(manager, null, false, emitter.emit.bind(emitter, 'end'));
|
||||
else emitter.emit('end');
|
||||
})
|
||||
.resolve();
|
||||
};
|
||||
|
||||
manager.once('resolveLocal', function () {
|
||||
names = names.length ? _.uniq(names) : null;
|
||||
|
||||
async.map(_.values(manager.dependencies), function (pkgs, next) {
|
||||
var pkg = pkgs[0];
|
||||
pkg.once('loadJSON', function () {
|
||||
var endpoint = pkg.readEndpoint();
|
||||
if (!endpoint) return next();
|
||||
|
||||
// Add tag only if the endpoint is a repository
|
||||
var json = pkg.json;
|
||||
if (!json.commit && (!json.repository || json.repository === 'git' || json.repository === 'local-repo')) {
|
||||
endpoint += '#' + ((!names || names.indexOf(pkg.name) > -1) ? '~' : '') + pkg.version;
|
||||
}
|
||||
|
||||
next(null, { name: pkg.name, endpoint: endpoint });
|
||||
}).loadJSON();
|
||||
}, installURLS);
|
||||
}).resolveLocal();
|
||||
|
||||
return emitter;
|
||||
return [names, options];
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
if (options.help) return help('update');
|
||||
|
||||
var paths = options.argv.remain.slice(1);
|
||||
return module.exports(paths, options);
|
||||
};
|
||||
|
||||
module.exports.completion = uninstall.completion;
|
||||
module.exports.completion.options = shorthand;
|
||||
module.exports = update;
|
||||
|
||||
161
lib/commands/version.js
Normal file
161
lib/commands/version.js
Normal file
@@ -0,0 +1,161 @@
|
||||
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 defaultConfig = require('../config');
|
||||
var createError = require('../util/createError');
|
||||
|
||||
function version(logger, versionArg, options, config) {
|
||||
options = options || {};
|
||||
|
||||
config = defaultConfig(config);
|
||||
|
||||
return bump(logger, config, versionArg, options.message);
|
||||
}
|
||||
|
||||
function bump(logger, config, versionArg, message) {
|
||||
var cwd = config.cwd || process.cwd();
|
||||
var newVersion;
|
||||
|
||||
if (!versionArg) {
|
||||
throw createError('No <version> agrument provided', 'EREADOPTIONS');
|
||||
}
|
||||
|
||||
return driver.check(cwd)
|
||||
.then(function () {
|
||||
return Q.all([driver.versions(cwd), driver.currentVersion(cwd)]);
|
||||
})
|
||||
.spread(function (versions, currentVersion) {
|
||||
currentVersion = currentVersion || '0.0.0';
|
||||
|
||||
if (semver.valid(versionArg)) {
|
||||
newVersion = semver.valid(versionArg);
|
||||
} else {
|
||||
newVersion = semver.inc(currentVersion, versionArg);
|
||||
|
||||
if (!newVersion) {
|
||||
throw createError('Invalid <version> argument: ' + versionArg, 'EINVALIDVERSION', { version: versionArg });
|
||||
}
|
||||
}
|
||||
|
||||
newVersion = (currentVersion[0] === 'v') ? 'v' + newVersion : newVersion;
|
||||
|
||||
if (versions) {
|
||||
versions.forEach(function (version) {
|
||||
if (semver.eq(version, newVersion)) {
|
||||
throw createError('Version exists: ' + newVersion, 'EVERSIONEXISTS', { versions: versions, newVersion: newVersion });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return driver.bump(cwd, newVersion, message).then(function () {
|
||||
return {
|
||||
oldVersion: currentVersion,
|
||||
newVersion: newVersion
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(function (result) {
|
||||
logger.info('version', 'Bumped package version from ' + result.oldVersion + ' to ' + result.newVersion, result);
|
||||
|
||||
return result.newVersion;
|
||||
});
|
||||
}
|
||||
|
||||
var driver = {
|
||||
check: function (cwd) {
|
||||
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('Version bump requires clean working directory', '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();
|
||||
});
|
||||
}
|
||||
|
||||
return checkGit(cwd).then(function (hasGit) {
|
||||
if (!hasGit) {
|
||||
throw createError('Version bump currently supports only git repositories', 'ENOTGITREPOSITORY');
|
||||
}
|
||||
});
|
||||
},
|
||||
versions: function (cwd) {
|
||||
return Q.nfcall(execFile, 'git', ['tag'], {env: process.env, cwd: cwd})
|
||||
.then(function (res) {
|
||||
var versions = res[0]
|
||||
.split(/\r?\n/)
|
||||
.filter(semver.valid);
|
||||
|
||||
return versions;
|
||||
}, function () {
|
||||
return [];
|
||||
});
|
||||
},
|
||||
currentVersion: function (cwd) {
|
||||
return Q.nfcall(execFile, 'git', ['describe', '--abbrev=0', '--tags'], {env: process.env, cwd: cwd})
|
||||
.then(function (res) {
|
||||
var version = res[0]
|
||||
.split(/\r?\n/)
|
||||
.filter(semver.valid)[0];
|
||||
|
||||
return version;
|
||||
}, function () {
|
||||
return undefined;
|
||||
});
|
||||
},
|
||||
bump: function (cwd, tag, message) {
|
||||
message = message || tag;
|
||||
message = message.replace(/%s/g, tag);
|
||||
return Q.nfcall(execFile, 'git', ['commit', '-m', message, '--allow-empty'], {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;
|
||||
63
lib/config.js
Normal file
63
lib/config.js
Normal file
@@ -0,0 +1,63 @@
|
||||
var tty = require('tty');
|
||||
var object = require('mout').object;
|
||||
var bowerConfig = require('bower-config');
|
||||
var Configstore = require('configstore');
|
||||
|
||||
var current;
|
||||
|
||||
function defaultConfig(config) {
|
||||
config = config || {};
|
||||
|
||||
return readCachedConfig(config.cwd || process.cwd(), config);
|
||||
}
|
||||
|
||||
function readCachedConfig(cwd, overwrites) {
|
||||
current = bowerConfig.create(cwd).load(overwrites);
|
||||
|
||||
var config = current.toObject();
|
||||
|
||||
var configstore = new Configstore('bower-github').all;
|
||||
|
||||
object.mixIn(config, configstore);
|
||||
|
||||
// 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;
|
||||
1094
lib/core/Manager.js
Normal file
1094
lib/core/Manager.js
Normal file
File diff suppressed because it is too large
Load Diff
227
lib/core/PackageRepository.js
Normal file
227
lib/core/PackageRepository.js
Normal file
@@ -0,0 +1,227 @@
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
var ResolveCache = require('./ResolveCache');
|
||||
var resolverFactory = require('./resolverFactory');
|
||||
var createError = require('../util/createError');
|
||||
var RegistryClient = require('bower-registry-client');
|
||||
|
||||
function PackageRepository(config, logger) {
|
||||
|
||||
var registryOptions;
|
||||
|
||||
this._config = config;
|
||||
this._logger = logger;
|
||||
|
||||
// Instantiate the registry
|
||||
registryOptions = mout.object.deepMixIn({}, this._config);
|
||||
registryOptions.cache = this._config.storage.registry;
|
||||
|
||||
this._registryClient = new RegistryClient(registryOptions, logger);
|
||||
|
||||
// Instantiate the resolve cache
|
||||
this._resolveCache = new ResolveCache(this._config);
|
||||
}
|
||||
|
||||
// -----------------
|
||||
|
||||
PackageRepository.prototype.fetch = function (decEndpoint) {
|
||||
var logger;
|
||||
var that = this;
|
||||
var isTargetable;
|
||||
var info = {
|
||||
decEndpoint: decEndpoint
|
||||
};
|
||||
|
||||
// Create a new logger that pipes everything to ours that will be
|
||||
// used to fetch
|
||||
logger = this._logger.geminate();
|
||||
// Intercept all logs, adding additional information
|
||||
logger.intercept(function (log) {
|
||||
that._extendLog(log, info);
|
||||
});
|
||||
|
||||
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 (!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);
|
||||
}
|
||||
|
||||
// Note that we use the resolver methods to query the
|
||||
// cache because transformations/normalisations can occur
|
||||
return that._resolveCache.retrieve(resolver.getSource(), resolver.getTarget())
|
||||
// Decide if we can use the one from the resolve cache
|
||||
.spread(function (canonicalDir, pkgMeta) {
|
||||
// If there's no package in the cache
|
||||
if (!canonicalDir) {
|
||||
// And the offline flag is passed, error out
|
||||
if (that._config.offline) {
|
||||
throw createError('No cached version for ' + resolver.getSource() + '#' + resolver.getTarget(), 'ENOCACHE', {
|
||||
resolver: resolver
|
||||
});
|
||||
}
|
||||
|
||||
// Otherwise, we have to resolve it
|
||||
logger.info('not-cached', resolver.getSource() + (resolver.getTarget() ? '#' + resolver.getTarget() : ''));
|
||||
logger.action('resolve', resolver.getSource() + '#' + resolver.getTarget());
|
||||
|
||||
return that._resolve(resolver, logger);
|
||||
}
|
||||
|
||||
info.canonicalDir = canonicalDir;
|
||||
info.pkgMeta = pkgMeta;
|
||||
|
||||
logger.info('cached', resolver.getSource() + (pkgMeta._release ? '#' + pkgMeta._release : ''));
|
||||
|
||||
// If offline flag is used, use directly the cached one
|
||||
if (that._config.offline) {
|
||||
return [canonicalDir, pkgMeta, isTargetable];
|
||||
}
|
||||
|
||||
// Otherwise check for new contents
|
||||
logger.action('validate', (pkgMeta._release ? pkgMeta._release + ' against ' : '') +
|
||||
resolver.getSource() + (resolver.getTarget() ? '#' + resolver.getTarget() : ''));
|
||||
|
||||
return resolver.hasNew(pkgMeta)
|
||||
.then(function (hasNew) {
|
||||
// If there are no new contents, resolve to
|
||||
// the cached one
|
||||
if (!hasNew) {
|
||||
return [canonicalDir, pkgMeta, isTargetable];
|
||||
}
|
||||
|
||||
// Otherwise resolve to the newest one
|
||||
logger.info('new', 'version for ' + resolver.getSource() + '#' + resolver.getTarget());
|
||||
logger.action('resolve', resolver.getSource() + '#' + resolver.getTarget());
|
||||
|
||||
return that._resolve(resolver, logger);
|
||||
});
|
||||
});
|
||||
})
|
||||
// If something went wrong, also extend the error
|
||||
.fail(function (err) {
|
||||
that._extendLog(err, info);
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
|
||||
PackageRepository.prototype.versions = function (source) {
|
||||
// Resolve the source using the factory because the
|
||||
// source can actually be a registry name
|
||||
return this._getResolver({ source: source })
|
||||
.then(function (resolver) {
|
||||
// If offline, resolve using the cached versions
|
||||
if (this._config.offline) {
|
||||
return this._resolveCache.versions(resolver.getSource());
|
||||
}
|
||||
|
||||
// Otherwise, fetch remotely
|
||||
return resolver.constructor.versions(resolver.getSource());
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
PackageRepository.prototype.eliminate = function (pkgMeta) {
|
||||
return Q.all([
|
||||
this._resolveCache.eliminate(pkgMeta),
|
||||
Q.nfcall(this._registryClient.clearCache.bind(this._registryClient), pkgMeta.name)
|
||||
]);
|
||||
};
|
||||
|
||||
PackageRepository.prototype.clear = function () {
|
||||
return Q.all([
|
||||
this._resolveCache.clear(),
|
||||
Q.nfcall(this._registryClient.clearCache.bind(this._registryClient))
|
||||
]);
|
||||
};
|
||||
|
||||
PackageRepository.prototype.reset = function () {
|
||||
this._resolveCache.reset();
|
||||
this._registryClient.resetCache();
|
||||
};
|
||||
|
||||
PackageRepository.prototype.list = function () {
|
||||
return this._resolveCache.list();
|
||||
};
|
||||
|
||||
PackageRepository.prototype.getRegistryClient = function () {
|
||||
return this._registryClient;
|
||||
};
|
||||
|
||||
PackageRepository.prototype.getResolveCache = function () {
|
||||
return this._resolveCache;
|
||||
};
|
||||
|
||||
PackageRepository.clearRuntimeCache = function () {
|
||||
ResolveCache.clearRuntimeCache();
|
||||
RegistryClient.clearRuntimeCache();
|
||||
resolverFactory.clearRuntimeCache();
|
||||
};
|
||||
|
||||
// ---------------------
|
||||
|
||||
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) {
|
||||
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()];
|
||||
});
|
||||
};
|
||||
|
||||
PackageRepository.prototype._extendLog = function (log, info) {
|
||||
log.data = log.data || {};
|
||||
|
||||
// Store endpoint info in each log
|
||||
if (info.decEndpoint) {
|
||||
log.data.endpoint = mout.object.pick(info.decEndpoint, ['name', 'source', 'target']);
|
||||
}
|
||||
|
||||
// Store the resolver info in each log
|
||||
if (info.resolver) {
|
||||
log.data.resolver = {
|
||||
name: info.resolver.getName(),
|
||||
source: info.resolver.getSource(),
|
||||
target: info.resolver.getTarget()
|
||||
};
|
||||
}
|
||||
|
||||
// Store the canonical dir and its meta in each log
|
||||
if (info.canonicalDir) {
|
||||
log.data.canonicalDir = info.canonicalDir;
|
||||
log.data.pkgMeta = info.pkgMeta;
|
||||
}
|
||||
|
||||
return log;
|
||||
};
|
||||
|
||||
module.exports = PackageRepository;
|
||||
869
lib/core/Project.js
Normal file
869
lib/core/Project.js
Normal file
@@ -0,0 +1,869 @@
|
||||
var glob = require('glob');
|
||||
var path = require('path');
|
||||
var fs = require('../util/fs');
|
||||
var Q = require('q');
|
||||
var mout = require('mout');
|
||||
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 semver = require('../util/semver');
|
||||
var createError = require('../util/createError');
|
||||
var readJson = require('../util/readJson');
|
||||
var validLink = require('../util/validLink');
|
||||
var scripts = require('./scripts');
|
||||
var relativeToBaseDir = require('../util/relativeToBaseDir');
|
||||
|
||||
function Project(config, logger) {
|
||||
this._config = config;
|
||||
this._logger = logger || new Logger();
|
||||
this._manager = new Manager(this._config, this._logger);
|
||||
|
||||
this._options = {};
|
||||
}
|
||||
|
||||
// -----------------
|
||||
|
||||
Project.prototype.install = function (decEndpoints, options, config) {
|
||||
var that = this;
|
||||
var targets = [];
|
||||
var resolved = {};
|
||||
var incompatibles = [];
|
||||
|
||||
// If already working, error out
|
||||
if (this._working) {
|
||||
return Q.reject(createError('Already working', 'EWORKING'));
|
||||
}
|
||||
|
||||
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.incompatible) {
|
||||
incompatibles.push(node);
|
||||
} else if (node.missing || node.different || that._config.force) {
|
||||
targets.push(node);
|
||||
} else {
|
||||
resolved[name] = node;
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Add decomposed endpoints as targets
|
||||
decEndpoints = decEndpoints || [];
|
||||
decEndpoints.forEach(function (decEndpoint) {
|
||||
// Mark as new so that a conflict for this target
|
||||
// always require a choice
|
||||
// Also allows for the target to be converted in case
|
||||
// of being *
|
||||
decEndpoint.newly = true;
|
||||
targets.push(decEndpoint);
|
||||
});
|
||||
|
||||
// Bootstrap the process
|
||||
return that._bootstrap(targets, resolved, incompatibles);
|
||||
})
|
||||
.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 || that._options.saveExact || that._config.save || that._config.saveExact) {
|
||||
// Cycle through the specified endpoints
|
||||
decEndpoints.forEach(function (decEndpoint) {
|
||||
var jsonEndpoint;
|
||||
|
||||
jsonEndpoint = endpointParser.decomposed2json(decEndpoint);
|
||||
|
||||
if (that._options.saveExact || that._config.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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Save JSON, might contain changes to dependencies and resolutions
|
||||
return that.saveJson()
|
||||
.then(function () {
|
||||
return that._manager.postinstall(that._json).then(function () {
|
||||
return installed;
|
||||
});
|
||||
});
|
||||
})
|
||||
.fin(function () {
|
||||
that._installed = null;
|
||||
that._working = false;
|
||||
});
|
||||
};
|
||||
|
||||
Project.prototype.update = function (names, options) {
|
||||
var that = this;
|
||||
var targets = [];
|
||||
var resolved = {};
|
||||
var incompatibles = [];
|
||||
|
||||
// If already working, error out
|
||||
if (this._working) {
|
||||
return Q.reject(createError('Already working', 'EWORKING'));
|
||||
}
|
||||
|
||||
this._options = options || {};
|
||||
this._working = true;
|
||||
|
||||
// Analyse the project
|
||||
return this._analyse()
|
||||
.spread(function (json, tree, flattened) {
|
||||
// If no names were specified, update every package
|
||||
if (!names) {
|
||||
// Mark each root dependency as targets
|
||||
that.walkTree(tree, function (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);
|
||||
// Otherwise, selectively update the specified ones
|
||||
} else {
|
||||
// Error out if some of the specified names
|
||||
// are not installed
|
||||
names.forEach(function (name) {
|
||||
if (!flattened[name]) {
|
||||
throw createError('Package ' + name + ' is not installed', 'ENOTINS', {
|
||||
name: name
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add packages whose names are specified to be updated
|
||||
that.walkTree(tree, function (node, name) {
|
||||
if (names.indexOf(name) !== -1) {
|
||||
// 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);
|
||||
|
||||
// Recover tree
|
||||
that.walkTree(tree, function (node, name) {
|
||||
if (node.missing || node.different) {
|
||||
targets.push(node);
|
||||
} else if (node.incompatible) {
|
||||
incompatibles.push(node);
|
||||
} else {
|
||||
resolved[name] = node;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
// Bootstrap the process
|
||||
return that._bootstrap(targets, resolved, incompatibles)
|
||||
.then(function () {
|
||||
return that._manager.preinstall(that._json);
|
||||
})
|
||||
.then(function () {
|
||||
return that._manager.install(that._json);
|
||||
})
|
||||
.then(function (installed) {
|
||||
// Save JSON, might contain changes to resolutions
|
||||
return that.saveJson()
|
||||
.then(function () {
|
||||
return that._manager.postinstall(that._json).then(function () {
|
||||
return installed;
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.fin(function () {
|
||||
that._installed = null;
|
||||
that._working = false;
|
||||
});
|
||||
};
|
||||
|
||||
function resolveUrlNames(names, flattened)
|
||||
{
|
||||
for (var i = 0; i < names.length; i++)
|
||||
if (! flattened[names[i]])
|
||||
{
|
||||
var url = names[i].trim().replace(/\/$/, '');
|
||||
var packName;
|
||||
for (packName in flattened)
|
||||
if (! ( !flattened[packName].source))
|
||||
if (url == flattened[packName].source.trim().replace(/\/$/, ''))
|
||||
names[i] = packName;
|
||||
}
|
||||
}
|
||||
|
||||
Project.prototype.uninstall = function (names, options) {
|
||||
var that = this;
|
||||
var packages = {};
|
||||
|
||||
// If already working, error out
|
||||
if (this._working) {
|
||||
return Q.reject(createError('Already working', 'EWORKING'));
|
||||
}
|
||||
|
||||
this._options = options || {};
|
||||
this._working = true;
|
||||
|
||||
// Analyse the project
|
||||
return this._analyse()
|
||||
// Fill in the packages to be uninstalled
|
||||
.spread(function (json, tree, flattened) {
|
||||
var promise = Q.resolve();
|
||||
resolveUrlNames(names, flattened);
|
||||
|
||||
names.forEach(function (name) {
|
||||
var decEndpoint = flattened[name];
|
||||
|
||||
// Check if it is not installed
|
||||
if (!decEndpoint || decEndpoint.missing) {
|
||||
packages[name] = null;
|
||||
return;
|
||||
}
|
||||
|
||||
promise = promise
|
||||
.then(function () {
|
||||
var message;
|
||||
var data;
|
||||
var dependantsNames;
|
||||
var dependants = [];
|
||||
|
||||
// Walk the down the tree, gathering dependants of the package
|
||||
that.walkTree(tree, function (node, nodeName) {
|
||||
if (name === nodeName) {
|
||||
dependants.push.apply(dependants, mout.object.values(node.dependants));
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Remove duplicates
|
||||
dependants = mout.array.unique(dependants);
|
||||
|
||||
// Note that the root is filtered from the dependants
|
||||
// as well as other dependants marked to be uninstalled
|
||||
dependants = dependants.filter(function (dependant) {
|
||||
return !dependant.root && names.indexOf(dependant.name) === -1;
|
||||
});
|
||||
|
||||
// If the package has no dependants or the force config is enabled,
|
||||
// mark it to be removed
|
||||
if (!dependants.length || that._config.force) {
|
||||
packages[name] = decEndpoint.canonicalDir;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise we need to figure it out if the user really wants to remove it,
|
||||
// even with dependants
|
||||
// As such we need to prompt the user with a meaningful message
|
||||
dependantsNames = dependants.map(function (dep) { return dep.name; });
|
||||
dependantsNames.sort(function (name1, name2) { return name1.localeCompare(name2); });
|
||||
dependantsNames = mout.array.unique(dependantsNames);
|
||||
dependants = dependants.map(function (dependant) { return that._manager.toData(dependant); });
|
||||
message = dependantsNames.join(', ') + ' depends on ' + decEndpoint.name;
|
||||
data = {
|
||||
name: decEndpoint.name,
|
||||
dependants: dependants
|
||||
};
|
||||
|
||||
// If interactive is disabled, error out
|
||||
if (!that._config.interactive) {
|
||||
throw createError(message, 'ECONFLICT', {
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
that._logger.conflict('mutual', message, data);
|
||||
|
||||
// Prompt the user
|
||||
return Q.nfcall(that._logger.prompt.bind(that._logger), {
|
||||
type: 'confirm',
|
||||
message: 'Continue anyway?',
|
||||
default: true
|
||||
})
|
||||
.then(function (confirmed) {
|
||||
// If the user decided to skip it, remove from the array so that it won't
|
||||
// influence subsequent dependants
|
||||
if (!confirmed) {
|
||||
mout.array.remove(names, name);
|
||||
} else {
|
||||
packages[name] = decEndpoint.canonicalDir;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return promise;
|
||||
})
|
||||
// Remove packages
|
||||
.then(function () {
|
||||
return that._removePackages(packages);
|
||||
})
|
||||
.fin(function () {
|
||||
that._installed = null;
|
||||
that._working = false;
|
||||
});
|
||||
};
|
||||
|
||||
Project.prototype.getTree = function (options) {
|
||||
this._options = options || {};
|
||||
|
||||
return this._analyse()
|
||||
.spread(function (json, tree, flattened) {
|
||||
var extraneous = [];
|
||||
var additionalKeys = ['missing', 'extraneous', 'different', 'linked'];
|
||||
|
||||
// Convert tree
|
||||
tree = this._manager.toData(tree, additionalKeys);
|
||||
|
||||
// Mark incompatibles
|
||||
this.walkTree(tree, function (node) {
|
||||
var version;
|
||||
var target = node.endpoint.target;
|
||||
|
||||
if (node.pkgMeta && semver.validRange(target)) {
|
||||
version = node.pkgMeta.version;
|
||||
|
||||
// Ignore if target is '*' and resolved to a non-semver release
|
||||
if (!version && target === '*') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!version || !semver.satisfies(version, target)) {
|
||||
node.incompatible = true;
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Convert extraneous
|
||||
mout.object.forOwn(flattened, function (pkg) {
|
||||
if (pkg.extraneous) {
|
||||
extraneous.push(this._manager.toData(pkg, additionalKeys));
|
||||
}
|
||||
}, this);
|
||||
|
||||
// Convert flattened
|
||||
flattened = mout.object.map(flattened, function (node) {
|
||||
return this._manager.toData(node, additionalKeys);
|
||||
}, this);
|
||||
|
||||
return [tree, flattened, extraneous];
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Project.prototype.walkTree = function (node, fn, onlyOnce) {
|
||||
var result;
|
||||
var dependencies;
|
||||
var queue = mout.object.values(node.dependencies);
|
||||
|
||||
if (onlyOnce === true) {
|
||||
onlyOnce = [];
|
||||
}
|
||||
|
||||
while (queue.length) {
|
||||
node = queue.shift();
|
||||
result = fn(node, node.endpoint ? node.endpoint.name : node.name);
|
||||
|
||||
// Abort traversal if result is false
|
||||
if (result === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add dependencies to the queue
|
||||
dependencies = mout.object.values(node.dependencies);
|
||||
|
||||
// If onlyOnce was true, do not add if already traversed
|
||||
if (onlyOnce) {
|
||||
dependencies = dependencies.filter(function (dependency) {
|
||||
return !mout.array.find(onlyOnce, function (stacked) {
|
||||
if (dependency.endpoint) {
|
||||
return mout.object.equals(dependency.endpoint, stacked.endpoint);
|
||||
}
|
||||
|
||||
return dependency.name === stacked.name &&
|
||||
dependency.source === stacked.source &&
|
||||
dependency.target === stacked.target;
|
||||
});
|
||||
});
|
||||
|
||||
onlyOnce.push.apply(onlyOnce, dependencies);
|
||||
}
|
||||
|
||||
queue.unshift.apply(queue, dependencies);
|
||||
}
|
||||
};
|
||||
|
||||
Project.prototype.saveJson = function (forceCreate) {
|
||||
var file;
|
||||
var jsonStr = JSON.stringify(this._json, null, ' ') + '\n';
|
||||
var jsonHash = md5(jsonStr);
|
||||
|
||||
// Save only if there's something different
|
||||
if (jsonHash === this._jsonHash) {
|
||||
return Q.resolve();
|
||||
}
|
||||
|
||||
// Error out if the json file does not exist, unless force create
|
||||
// is true
|
||||
if (!this._jsonFile && !forceCreate) {
|
||||
this._logger.warn('no-json', 'No bower.json file to save to, use bower init to create one');
|
||||
return Q.resolve();
|
||||
}
|
||||
|
||||
file = this._jsonFile || path.join(this._config.cwd, 'bower.json');
|
||||
return Q.nfcall(fs.writeFile, file, jsonStr)
|
||||
.then(function () {
|
||||
this._jsonHash = jsonHash;
|
||||
this._jsonFile = file;
|
||||
return this._json;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Project.prototype.hasJson = function () {
|
||||
return this._readJson()
|
||||
.then(function (json) {
|
||||
return json ? this._jsonFile : false;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Project.prototype.getJson = function () {
|
||||
return this._readJson();
|
||||
};
|
||||
|
||||
Project.prototype.getManager = function () {
|
||||
return this._manager;
|
||||
};
|
||||
|
||||
Project.prototype.getPackageRepository = function () {
|
||||
return this._manager.getPackageRepository();
|
||||
};
|
||||
|
||||
// -----------------
|
||||
|
||||
Project.prototype._analyse = function () {
|
||||
return Q.all([
|
||||
this._readJson(),
|
||||
this._readInstalled(),
|
||||
this._readLinks()
|
||||
])
|
||||
.spread(function (json, installed, links) {
|
||||
var root;
|
||||
var jsonCopy = mout.lang.deepClone(json);
|
||||
|
||||
root = {
|
||||
name: json.name,
|
||||
source: this._config.cwd,
|
||||
target: json.version || '*',
|
||||
pkgMeta: jsonCopy,
|
||||
canonicalDir: this._config.cwd,
|
||||
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
|
||||
// It may happen pkgMeta is undefined if package is uninstalled
|
||||
if (!isSaved && pkgMeta && pkgMeta._direct) {
|
||||
decEndpoint.extraneous = true;
|
||||
|
||||
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, installed, 'dependencies');
|
||||
// Do the same for the dev dependencies
|
||||
if (!this._options.production) {
|
||||
this._restoreNode(root, installed, 'devDependencies');
|
||||
}
|
||||
|
||||
// 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, installed, 'dependencies');
|
||||
// Note that it has no dependants, just dependencies!
|
||||
root.dependencies[name] = decEndpoint;
|
||||
}
|
||||
}, this);
|
||||
|
||||
// Remove root from the flattened tree
|
||||
delete installed[json.name];
|
||||
|
||||
return [json, root, installed];
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Project.prototype._bootstrap = function (targets, resolved, incompatibles) {
|
||||
var installed = mout.object.map(this._installed, function (decEndpoint) {
|
||||
return decEndpoint.pkgMeta;
|
||||
});
|
||||
|
||||
this._json.resolutions = this._json.resolutions || {};
|
||||
|
||||
// Configure the manager and kick in the resolve process
|
||||
return this._manager
|
||||
.configure({
|
||||
targets: targets,
|
||||
resolved: resolved,
|
||||
incompatibles: incompatibles,
|
||||
resolutions: this._json.resolutions,
|
||||
installed: installed,
|
||||
forceLatest: this._options.forceLatest
|
||||
})
|
||||
.resolve()
|
||||
.then(function () {
|
||||
// If the resolutions is empty, delete key
|
||||
if (!mout.object.size(this._json.resolutions)) {
|
||||
delete this._json.resolutions;
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Project.prototype._readJson = function () {
|
||||
var that = this;
|
||||
|
||||
if (this._json) {
|
||||
return Q.resolve(this._json);
|
||||
}
|
||||
|
||||
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, logger: that._logger });
|
||||
})
|
||||
.spread(function (json, deprecated, assumed) {
|
||||
var jsonStr;
|
||||
|
||||
if (deprecated) {
|
||||
that._logger.warn('deprecated', 'You are using the deprecated ' + deprecated + ' file');
|
||||
}
|
||||
|
||||
if (!assumed) {
|
||||
that._jsonFile = path.join(that._config.cwd, deprecated ? deprecated : 'bower.json');
|
||||
}
|
||||
|
||||
jsonStr = JSON.stringify(json, null, ' ') + '\n';
|
||||
that._jsonHash = md5(jsonStr);
|
||||
return that._json = json;
|
||||
});
|
||||
};
|
||||
|
||||
Project.prototype._readInstalled = function () {
|
||||
var componentsDir;
|
||||
var that = this;
|
||||
|
||||
if (this._installed) {
|
||||
return Q.resolve(this._installed);
|
||||
}
|
||||
|
||||
// Gather all folders that are actual packages by
|
||||
// looking for the package metadata file
|
||||
componentsDir = relativeToBaseDir(this._config.cwd)(this._config.directory);
|
||||
return this._installed = Q.nfcall(glob, '*/.bower.json', {
|
||||
cwd: componentsDir,
|
||||
dot: true
|
||||
})
|
||||
.then(function (filenames) {
|
||||
var promises;
|
||||
var decEndpoints = {};
|
||||
|
||||
// Foreach bower.json found
|
||||
promises = filenames.map(function (filename) {
|
||||
var name = path.dirname(filename);
|
||||
var metaFile = path.join(componentsDir, filename);
|
||||
|
||||
// Read package metadata
|
||||
return readJson(metaFile)
|
||||
.spread(function (pkgMeta) {
|
||||
decEndpoints[name] = {
|
||||
name: name,
|
||||
source: pkgMeta._originalSource || pkgMeta._source,
|
||||
target: pkgMeta._target,
|
||||
canonicalDir: path.dirname(metaFile),
|
||||
pkgMeta: pkgMeta
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Wait until all files have been read
|
||||
// and resolve with the decomposed endpoints
|
||||
return Q.all(promises)
|
||||
.then(function () {
|
||||
return that._installed = decEndpoints;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Project.prototype._readLinks = function () {
|
||||
var componentsDir;
|
||||
var that = this;
|
||||
|
||||
// Read directory, looking for links
|
||||
componentsDir = relativeToBaseDir(this._config.cwd)(this._config.directory);
|
||||
return Q.nfcall(fs.readdir, componentsDir)
|
||||
.then(function (filenames) {
|
||||
var promises;
|
||||
var decEndpoints = {};
|
||||
|
||||
promises = filenames.map(function (filename) {
|
||||
var dir = path.join(componentsDir, filename);
|
||||
|
||||
// Filter only those that are valid links
|
||||
return validLink(dir)
|
||||
.spread(function (valid, err) {
|
||||
var name;
|
||||
|
||||
if (!valid) {
|
||||
if (err) {
|
||||
that._logger.debug('read-link', 'Link ' + dir + ' is invalid', {
|
||||
filename: dir,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip links to files (see #783)
|
||||
if (!valid.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
name = path.basename(dir);
|
||||
return readJson(dir, {
|
||||
assume: { name: name }
|
||||
})
|
||||
.spread(function (json, deprecated) {
|
||||
if (deprecated) {
|
||||
that._logger.warn('deprecated', 'Package ' + name + ' is using the deprecated ' + deprecated);
|
||||
}
|
||||
|
||||
json._direct = true; // Mark as a direct dep of root
|
||||
decEndpoints[name] = {
|
||||
name: name,
|
||||
source: dir,
|
||||
target: '*',
|
||||
canonicalDir: dir,
|
||||
pkgMeta: json,
|
||||
linked: true
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Wait until all links have been read
|
||||
// and resolve with the decomposed endpoints
|
||||
return Q.all(promises)
|
||||
.then(function () {
|
||||
return decEndpoints;
|
||||
});
|
||||
// Ignore if folder does not exist
|
||||
}, function (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
Project.prototype._removePackages = function (packages) {
|
||||
var that = this;
|
||||
var promises = [];
|
||||
|
||||
return scripts.preuninstall(that._config, that._logger, packages, that._installed, that._json)
|
||||
.then(function () {
|
||||
|
||||
mout.object.forOwn(packages, function (dir, name) {
|
||||
var promise;
|
||||
|
||||
// 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
|
||||
});
|
||||
}
|
||||
|
||||
// Remove from json only if successfully deleted
|
||||
if ((that._options.save || that._config.save) && that._json.dependencies) {
|
||||
promise = promise
|
||||
.then(function () {
|
||||
delete that._json.dependencies[name];
|
||||
});
|
||||
}
|
||||
|
||||
if (that._options.saveDev && that._json.devDependencies) {
|
||||
promise = promise
|
||||
.then(function () {
|
||||
delete that._json.devDependencies[name];
|
||||
});
|
||||
}
|
||||
|
||||
promises.push(promise);
|
||||
});
|
||||
|
||||
return Q.all(promises);
|
||||
|
||||
})
|
||||
.then(function () {
|
||||
return that.saveJson();
|
||||
})
|
||||
// Run post-uninstall hook before resolving with removed packages.
|
||||
.then(scripts.postuninstall.bind(
|
||||
null, that._config, that._logger, packages, that._installed, that._json))
|
||||
// Resolve with removed packages
|
||||
.then(function () {
|
||||
return mout.object.filter(packages, function (dir) {
|
||||
return !!dir;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Project.prototype._restoreNode = function (node, flattened, jsonKey, processed) {
|
||||
var deps;
|
||||
|
||||
// Do not restore if the node is missing
|
||||
if (node.missing) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.dependencies = node.dependencies || {};
|
||||
node.dependants = node.dependants || {};
|
||||
processed = processed || {};
|
||||
|
||||
// Only process deps that are not yet processed
|
||||
deps = mout.object.filter(node.pkgMeta[jsonKey], function (value, key) {
|
||||
return !processed[node.name + ':' + key];
|
||||
});
|
||||
|
||||
mout.object.forOwn(deps, function (value, key) {
|
||||
var local = flattened[key];
|
||||
var json = endpointParser.json2decomposed(key, value);
|
||||
var restored;
|
||||
var compatible;
|
||||
var originalSource;
|
||||
|
||||
// Check if the dependency is not installed
|
||||
if (!local) {
|
||||
flattened[key] = restored = json;
|
||||
restored.missing = true;
|
||||
// Even if it is installed, check if it's compatible
|
||||
// Note that linked packages are interpreted as compatible
|
||||
// This might change in the future: #673
|
||||
} else {
|
||||
compatible = local.linked || (!local.missing && json.target === local.pkgMeta._target);
|
||||
|
||||
if (!compatible) {
|
||||
restored = json;
|
||||
|
||||
if (!local.missing) {
|
||||
restored.pkgMeta = local.pkgMeta;
|
||||
restored.canonicalDir = local.canonicalDir;
|
||||
restored.incompatible = true;
|
||||
} else {
|
||||
restored.missing = true;
|
||||
}
|
||||
} else {
|
||||
restored = local;
|
||||
mout.object.mixIn(local, json);
|
||||
}
|
||||
|
||||
// Check if source changed, marking as different if it did
|
||||
// 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] = 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', processed);
|
||||
|
||||
// Do the same for the incompatible local package
|
||||
if (local && restored !== local) {
|
||||
this._restoreNode(local, flattened, 'dependencies', processed);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
module.exports = Project;
|
||||
406
lib/core/ResolveCache.js
Normal file
406
lib/core/ResolveCache.js
Normal file
@@ -0,0 +1,406 @@
|
||||
var fs = require('../util/fs');
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
var mkdirp = require('mkdirp');
|
||||
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');
|
||||
|
||||
function ResolveCache(config) {
|
||||
// TODO: Make some config entries, such as:
|
||||
// - Max MB
|
||||
// - Max versions per source
|
||||
// - Max MB per source
|
||||
// - 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
|
||||
this._cache = this.constructor._cache.get(this._dir);
|
||||
if (!this._cache) {
|
||||
this._cache = new LRU({
|
||||
max: 100,
|
||||
maxAge: 60 * 5 * 1000 // 5 minutes
|
||||
});
|
||||
this.constructor._cache.set(this._dir, this._cache);
|
||||
}
|
||||
|
||||
// Ensure dir is created
|
||||
mkdirp.sync(this._dir);
|
||||
}
|
||||
|
||||
// -----------------
|
||||
|
||||
ResolveCache.prototype.retrieve = function (source, target) {
|
||||
var sourceId = md5(source);
|
||||
var dir = path.join(this._dir, sourceId);
|
||||
var that = this;
|
||||
|
||||
target = target || '*';
|
||||
|
||||
return this._getVersions(sourceId)
|
||||
.spread(function (versions) {
|
||||
var suitable;
|
||||
|
||||
// If target is a semver, find a suitable version
|
||||
if (semver.validRange(target)) {
|
||||
suitable = semver.maxSatisfying(versions, target, true);
|
||||
|
||||
if (suitable) {
|
||||
return suitable;
|
||||
}
|
||||
}
|
||||
|
||||
// If target is '*' check if there's a cached '_wildcard'
|
||||
if (target === '*') {
|
||||
return mout.array.find(versions, function (version) {
|
||||
return version === '_wildcard';
|
||||
});
|
||||
}
|
||||
|
||||
// Otherwise check if there's an exact match
|
||||
return mout.array.find(versions, function (version) {
|
||||
return version === target;
|
||||
});
|
||||
})
|
||||
.then(function (version) {
|
||||
var canonicalDir;
|
||||
|
||||
if (!version) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Resolve with canonical dir and package meta
|
||||
canonicalDir = path.join(dir, encodeURIComponent(version));
|
||||
return that._readPkgMeta(canonicalDir)
|
||||
.then(function (pkgMeta) {
|
||||
return [canonicalDir, pkgMeta];
|
||||
}, function () {
|
||||
// If there was an error, invalidate the in-memory cache,
|
||||
// delete the cached package and try again
|
||||
that._cache.del(sourceId);
|
||||
|
||||
return Q.nfcall(rimraf, canonicalDir)
|
||||
.then(function () {
|
||||
return that.retrieve(source, target);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ResolveCache.prototype.store = function (canonicalDir, pkgMeta) {
|
||||
var sourceId;
|
||||
var release;
|
||||
var dir;
|
||||
var pkgLock;
|
||||
var promise;
|
||||
var that = this;
|
||||
|
||||
promise = pkgMeta ? Q.resolve(pkgMeta) : this._readPkgMeta(canonicalDir);
|
||||
|
||||
return promise
|
||||
.then(function (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 destination directory exists to prevent issuing lock at all times
|
||||
return Q.nfcall(fs.stat, 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}).finally(function () {
|
||||
lockFile.unlockSync(pkgLock);
|
||||
});
|
||||
}).finally(function () {
|
||||
// Ensure no tmp dir is left on disk.
|
||||
return Q.nfcall(rimraf, canonicalDir);
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
var versions = that._cache.get(sourceId);
|
||||
|
||||
// Add it to the in memory cache
|
||||
// and sort the versions afterwards
|
||||
if (versions && versions.indexOf(release) === -1) {
|
||||
versions.push(release);
|
||||
that._sortVersions(versions);
|
||||
}
|
||||
|
||||
// Resolve with the final location
|
||||
return dir;
|
||||
});
|
||||
};
|
||||
|
||||
ResolveCache.prototype.eliminate = function (pkgMeta) {
|
||||
var sourceId = md5(pkgMeta._source);
|
||||
var release = this._getPkgRelease(pkgMeta);
|
||||
var dir = path.join(this._dir, sourceId, release);
|
||||
var that = this;
|
||||
|
||||
return Q.nfcall(rimraf, dir)
|
||||
.then(function () {
|
||||
var versions = that._cache.get(sourceId) || [];
|
||||
mout.array.remove(versions, release);
|
||||
|
||||
// If this was the last package in the cache,
|
||||
// delete the parent folder (source)
|
||||
// For extra security, check against the file system
|
||||
// if this was really the last package
|
||||
if (!versions.length) {
|
||||
that._cache.del(sourceId);
|
||||
|
||||
return that._getVersions(sourceId)
|
||||
.spread(function (versions) {
|
||||
if (!versions.length) {
|
||||
// Do not keep in-memory cache if it's completely
|
||||
// empty
|
||||
that._cache.del(sourceId);
|
||||
|
||||
return Q.nfcall(rimraf, path.dirname(dir));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ResolveCache.prototype.clear = function () {
|
||||
return Q.nfcall(rimraf, this._dir)
|
||||
.then(function () {
|
||||
return Q.nfcall(fs.mkdir, this._dir);
|
||||
}.bind(this))
|
||||
.then(function () {
|
||||
this._cache.reset();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ResolveCache.prototype.reset = function () {
|
||||
this._cache.reset();
|
||||
return this;
|
||||
};
|
||||
|
||||
ResolveCache.prototype.versions = function (source) {
|
||||
var sourceId = md5(source);
|
||||
|
||||
return this._getVersions(sourceId)
|
||||
.spread(function (versions) {
|
||||
return versions.filter(function (version) {
|
||||
return semver.valid(version);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ResolveCache.prototype.list = function () {
|
||||
var promises;
|
||||
var dirs = [];
|
||||
var that = this;
|
||||
|
||||
// Get the list of directories
|
||||
return Q.nfcall(fs.readdir, this._dir)
|
||||
.then(function (sourceIds) {
|
||||
promises = sourceIds.map(function (sourceId) {
|
||||
return Q.nfcall(fs.readdir, path.join(that._dir, sourceId))
|
||||
.then(function (versions) {
|
||||
versions.forEach(function (version) {
|
||||
var dir = path.join(that._dir, sourceId, version);
|
||||
dirs.push(dir);
|
||||
});
|
||||
}, function (err) {
|
||||
// Ignore lurking files, e.g.: .DS_Store if the user
|
||||
// has navigated throughout the cache
|
||||
if (err.code === 'ENOTDIR' && err.path) {
|
||||
return Q.nfcall(rimraf, err.path);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
return Q.all(promises);
|
||||
})
|
||||
// Read every package meta
|
||||
.then(function () {
|
||||
promises = dirs.map(function (dir) {
|
||||
return that._readPkgMeta(dir)
|
||||
.then(function (pkgMeta) {
|
||||
return {
|
||||
canonicalDir: dir,
|
||||
pkgMeta: pkgMeta
|
||||
};
|
||||
}, function () {
|
||||
// If it fails to read, invalidate the in memory
|
||||
// cache for the source and delete the entry directory
|
||||
var sourceId = path.basename(path.dirname(dir));
|
||||
that._cache.del(sourceId);
|
||||
|
||||
return Q.nfcall(rimraf, dir);
|
||||
});
|
||||
});
|
||||
|
||||
return Q.all(promises);
|
||||
})
|
||||
// Sort by name ASC & release ASC
|
||||
.then(function (entries) {
|
||||
// Ignore falsy entries due to errors reading
|
||||
// package metas
|
||||
entries = entries.filter(function (entry) {
|
||||
return !!entry;
|
||||
});
|
||||
|
||||
return entries.sort(function (entry1, entry2) {
|
||||
var pkgMeta1 = entry1.pkgMeta;
|
||||
var pkgMeta2 = entry2.pkgMeta;
|
||||
var comp = pkgMeta1.name.localeCompare(pkgMeta2.name);
|
||||
|
||||
// Sort by name
|
||||
if (comp) {
|
||||
return comp;
|
||||
}
|
||||
|
||||
// Sort by version
|
||||
if (pkgMeta1.version && pkgMeta2.version) {
|
||||
return semver.compare(pkgMeta1.version, pkgMeta2.version);
|
||||
}
|
||||
if (pkgMeta1.version) {
|
||||
return -1;
|
||||
}
|
||||
if (pkgMeta2.version) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort by target
|
||||
return pkgMeta1._target.localeCompare(pkgMeta2._target);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// ------------------------
|
||||
|
||||
ResolveCache.clearRuntimeCache = function () {
|
||||
// Note that _cache refers to the static _cache variable
|
||||
// that holds other caches per dir!
|
||||
// Do not confuse it with the instance cache
|
||||
|
||||
// Clear cache of each directory
|
||||
this._cache.forEach(function (cache) {
|
||||
cache.reset();
|
||||
});
|
||||
|
||||
// Clear root cache
|
||||
this._cache.reset();
|
||||
};
|
||||
|
||||
// ------------------------
|
||||
|
||||
ResolveCache.prototype._getPkgRelease = function (pkgMeta) {
|
||||
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) {
|
||||
var filename = path.join(dir, '.bower.json');
|
||||
|
||||
return readJson(filename)
|
||||
.spread(function (json) {
|
||||
return json;
|
||||
});
|
||||
};
|
||||
|
||||
ResolveCache.prototype._getVersions = function (sourceId) {
|
||||
var dir;
|
||||
var versions = this._cache.get(sourceId);
|
||||
var that = this;
|
||||
|
||||
if (versions) {
|
||||
return Q.resolve([versions, true]);
|
||||
}
|
||||
|
||||
dir = path.join(this._dir, sourceId);
|
||||
return Q.nfcall(fs.readdir, dir)
|
||||
.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) {
|
||||
// If the directory does not exists, resolve
|
||||
// as an empty array
|
||||
if (err.code === 'ENOENT') {
|
||||
versions = [];
|
||||
that._cache.set(sourceId, versions);
|
||||
return [versions, false];
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
|
||||
ResolveCache.prototype._sortVersions = function (versions) {
|
||||
// Sort DESC
|
||||
versions.sort(function (version1, version2) {
|
||||
var validSemver1 = semver.valid(version1);
|
||||
var validSemver2 = semver.valid(version2);
|
||||
|
||||
// If both are semvers, compare them
|
||||
if (validSemver1 && validSemver2) {
|
||||
return semver.rcompare(version1, version2);
|
||||
}
|
||||
|
||||
// If one of them are semvers, give higher priority
|
||||
if (validSemver1) {
|
||||
return -1;
|
||||
}
|
||||
if (validSemver2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Otherwise they are considered equal
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
// ------------------------
|
||||
|
||||
ResolveCache._cache = new LRU({
|
||||
max: 5,
|
||||
maxAge: 60 * 30 * 1000 // 30 minutes
|
||||
});
|
||||
|
||||
module.exports = ResolveCache;
|
||||
@@ -1,60 +0,0 @@
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var _ = require('lodash');
|
||||
var tmp = require('tmp');
|
||||
|
||||
var fileExists = require('../util/file-exists').sync;
|
||||
|
||||
var temp = process.env.TMPDIR
|
||||
|| process.env.TMP
|
||||
|| process.env.TEMP
|
||||
|| process.platform === 'win32' ? 'c:\\windows\\temp' : '/tmp';
|
||||
|
||||
var home = (process.platform === 'win32'
|
||||
? process.env.USERPROFILE
|
||||
: process.env.HOME) || temp;
|
||||
|
||||
var roaming = process.platform === 'win32'
|
||||
? path.resolve(process.env.APPDATA || home || temp)
|
||||
: path.resolve(home || temp);
|
||||
|
||||
var folder = process.platform === 'win32'
|
||||
? 'bower'
|
||||
: '.bower';
|
||||
|
||||
var proxy = process.env.HTTPS_PROXY
|
||||
|| process.env.https_proxy
|
||||
|| process.env.HTTP_PROXY
|
||||
|| process.env.http_proxy;
|
||||
|
||||
// Bower Config
|
||||
var config;
|
||||
try {
|
||||
config = require('rc') ('bower', {
|
||||
cache : path.join(roaming, folder, 'cache'),
|
||||
links : path.join(roaming, folder, 'links'),
|
||||
completion : path.join(roaming, folder, 'completion'),
|
||||
json : 'component.json',
|
||||
endpoint : 'https://bower.herokuapp.com',
|
||||
directory : 'components',
|
||||
proxy : proxy
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error('Unable to parse global .bowerrc file: ' + e.message);
|
||||
}
|
||||
|
||||
// If there is a local .bowerrc file, merge it
|
||||
var localFile = path.join(process.cwd(), '.bowerrc');
|
||||
if (fileExists(localFile)) {
|
||||
try {
|
||||
_.extend(config, JSON.parse(fs.readFileSync(localFile)));
|
||||
} catch (e) {
|
||||
throw new Error('Unable to parse local .bowerrc file: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Configure tmp package to use graceful degradationn
|
||||
// If an uncaught exception occurs, the temporary directories will be deleted nevertheless
|
||||
tmp.setGracefulCleanup();
|
||||
|
||||
module.exports = config;
|
||||
@@ -1,360 +0,0 @@
|
||||
// ==========================================
|
||||
// BOWER: Manager Object Definition
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
// Events:
|
||||
// - install: fired when everything is installed
|
||||
// - package: fired for each installed packaged
|
||||
// - resolve: fired when deps resolved (with a true/false indicating success or error)
|
||||
// - error: fired on all errors
|
||||
// - data: fired when trying to output data
|
||||
// - end: fired when finished installing
|
||||
// ==========================================
|
||||
|
||||
var events = require('events');
|
||||
var semver = require('semver');
|
||||
var async = require('async');
|
||||
var path = require('path');
|
||||
var glob = require('glob');
|
||||
var fs = require('fs');
|
||||
var _ = require('lodash');
|
||||
|
||||
var Package = require('./package');
|
||||
var UnitWork = require('./unit_work');
|
||||
var config = require('./config');
|
||||
var fileExists = require('../util/file-exists');
|
||||
var template = require('../util/template');
|
||||
var prune = require('../util/prune');
|
||||
|
||||
// read local dependencies (with versions)
|
||||
// read json dependencies (resolving along the way into temp dir)
|
||||
// merge local dependencies with json dependencies
|
||||
// prune and move dependencies into local directory
|
||||
|
||||
var Manager = function (endpoints, opts) {
|
||||
this.dependencies = {};
|
||||
this.cwd = process.cwd();
|
||||
this.endpoints = endpoints || [];
|
||||
this.unitWork = new UnitWork;
|
||||
this.opts = opts || {};
|
||||
this.errors = [];
|
||||
};
|
||||
|
||||
Manager.prototype = Object.create(events.EventEmitter.prototype);
|
||||
Manager.prototype.constructor = Manager;
|
||||
|
||||
Manager.prototype.loadJSON = function () {
|
||||
var json = path.join(this.cwd, config.json);
|
||||
fileExists(json, function (exists) {
|
||||
if (!exists) {
|
||||
// If the json does not exist, assume one
|
||||
this.json = {
|
||||
name: path.basename(this.cwd),
|
||||
version: '0.0.0'
|
||||
},
|
||||
this.name = this.json.name;
|
||||
this.version = this.json.version;
|
||||
return this.emit('loadJSON');
|
||||
}
|
||||
|
||||
fs.readFile(json, 'utf8', function (err, json) {
|
||||
if (err) return this.emit('error', err);
|
||||
try {
|
||||
this.json = JSON.parse(json);
|
||||
} catch (e) {
|
||||
return this.emit('error', new Error('There was an error while reading the ' + config.json));
|
||||
}
|
||||
this.name = this.json.name;
|
||||
this.version = this.json.version;
|
||||
this.emit('loadJSON');
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Manager.prototype.resolve = function () {
|
||||
var resolved = function () {
|
||||
// If there is errors, report them
|
||||
if (this.errors.length) return this.reportErrors();
|
||||
// If there is an error while pruning (conflict) then abort installation
|
||||
if (!this.prune()) return this.emit('resolve', false);
|
||||
// Otherwise all is fine, so we install
|
||||
this.once('install', this.emit.bind(this, 'resolve', true)).install();
|
||||
}.bind(this);
|
||||
|
||||
// Resolve locally first
|
||||
this.once('resolveLocal', function () {
|
||||
if (this.endpoints.length) {
|
||||
// TODO: When resolving specific endpoints we need to restore all the local
|
||||
// packages and their hierarchy (all from the local folder)
|
||||
// If something goes wrong, simply do resolveFromJSON before
|
||||
// calling resolved() (slower)
|
||||
// This will solve issue #200
|
||||
this.once('resolveEndpoints', resolved).resolveEndpoints();
|
||||
} else {
|
||||
this.once('resolveFromJson', resolved).resolveFromJson();
|
||||
}
|
||||
}).resolveLocal();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Manager.prototype.resolveLocal = function () {
|
||||
glob('./' + config.directory + '/*', function (err, dirs) {
|
||||
if (err) return this.emit('error', err);
|
||||
dirs.forEach(function (dir) {
|
||||
var name = path.basename(dir);
|
||||
var pkg = new Package(name, dir, this);
|
||||
|
||||
this.dependencies[name] = [];
|
||||
this.dependencies[name].push(pkg);
|
||||
|
||||
this.gatherPackageErrors(pkg);
|
||||
}.bind(this));
|
||||
this.emit('resolveLocal');
|
||||
}.bind(this));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Manager.prototype.resolveEndpoints = function () {
|
||||
var endpointNames = this.opts.endpointNames || {};
|
||||
|
||||
async.forEach(this.endpoints, function (endpoint, next) {
|
||||
var name = endpointNames[endpoint];
|
||||
var pkg = new Package(name, endpoint, this);
|
||||
|
||||
pkg.root = true;
|
||||
this.dependencies[name] = this.dependencies[name] || [];
|
||||
this.dependencies[name].push(pkg);
|
||||
|
||||
pkg.once('resolve', next).resolve();
|
||||
this.gatherPackageErrors(pkg, next);
|
||||
}.bind(this), this.emit.bind(this, 'resolveEndpoints'));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Manager.prototype.resolveFromJson = function () {
|
||||
this.once('loadJSON', function () {
|
||||
var dependencies = this.json.dependencies || {};
|
||||
|
||||
// add devDependencies
|
||||
if (!this.opts.production && this.json.devDependencies) {
|
||||
dependencies = _.extend({}, dependencies, this.json.devDependencies);
|
||||
}
|
||||
|
||||
async.forEach(Object.keys(dependencies), function (name, next) {
|
||||
var endpoint = dependencies[name];
|
||||
var pkg = new Package(name, endpoint, this);
|
||||
|
||||
pkg.root = true;
|
||||
this.dependencies[name] = this.dependencies[name] || [];
|
||||
this.dependencies[name].push(pkg);
|
||||
|
||||
pkg.once('resolve', next).resolve();
|
||||
this.gatherPackageErrors(pkg, next);
|
||||
}.bind(this), this.emit.bind(this, 'resolveFromJson'));
|
||||
}.bind(this)).loadJSON();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Private
|
||||
Manager.prototype.getDeepDependencies = function () {
|
||||
var result = {};
|
||||
|
||||
for (var name in this.dependencies) {
|
||||
this.dependencies[name].forEach(function (pkg) {
|
||||
result[pkg.name] = result[pkg.name] || [];
|
||||
result[pkg.name].push(pkg);
|
||||
pkg.getDeepDependencies().forEach(function (pkg) {
|
||||
result[pkg.name] = result[pkg.name] || [];
|
||||
result[pkg.name].push(pkg);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Manager.prototype.prune = function () {
|
||||
var result = prune(this.getDeepDependencies(), this.opts.forceLatest);
|
||||
var name;
|
||||
|
||||
// If there is conflicted deps, print them and fail
|
||||
if (result.conflicted) {
|
||||
for (name in result.conflicted) {
|
||||
this.reportConflicts(name, result.conflicted[name]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this.dependencies = {};
|
||||
|
||||
// If there is conflicted deps but they where forcebly resolved
|
||||
// Print a warning about them
|
||||
if (result.forceblyResolved) {
|
||||
for (name in result.forceblyResolved) {
|
||||
this.reportForceblyResolved(name, result.forceblyResolved[name]);
|
||||
this.dependencies[name] = result.forceblyResolved[name];
|
||||
this.dependencies[name][0].root = true;
|
||||
}
|
||||
}
|
||||
|
||||
_.extend(this.dependencies, result.resolved);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Manager.prototype.gatherPackageErrors = function (pkg, next) {
|
||||
var calledNext = false;
|
||||
|
||||
// Listen to all the errors
|
||||
// The first error will call the next callback and we continue to gather more until the end
|
||||
// This makes sense because a package forwards its deep dependencies errors
|
||||
pkg.on('error', function (err, origin) {
|
||||
pkg = origin || pkg;
|
||||
|
||||
// If the error message starts with the package name, strip it
|
||||
if (!err.message.indexOf(pkg.name + ' ')) {
|
||||
err.message = err.message.substr(pkg.name.length + 1);
|
||||
}
|
||||
|
||||
this.errors.push({ pkg: pkg, error: err });
|
||||
if (next && !calledNext) {
|
||||
calledNext = true;
|
||||
next();
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Manager.prototype.install = function () {
|
||||
async.forEach(Object.keys(this.dependencies), function (name, next) {
|
||||
var pkg = this.dependencies[name][0];
|
||||
pkg.once('install', function () {
|
||||
this.emit('package', pkg);
|
||||
next();
|
||||
}.bind(this)).install();
|
||||
pkg.once('error', next);
|
||||
}.bind(this), function () {
|
||||
if (this.errors.length) this.reportErrors();
|
||||
return this.emit('install');
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Manager.prototype.muteDependencies = function () {
|
||||
for (var name in this.dependencies) {
|
||||
this.dependencies[name].forEach(function (pkg) {
|
||||
pkg.removeAllListeners();
|
||||
pkg.on('error', function () {});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Manager.prototype.reportErrors = function () {
|
||||
this.muteDependencies();
|
||||
template('error-summary', { errors: this.errors }).on('data', function (data) {
|
||||
this.emit('data', data);
|
||||
this.emit('resolve', false);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Manager.prototype.reportConflicts = function (name, packages) {
|
||||
var versions = [];
|
||||
var requirements = [];
|
||||
|
||||
packages = packages.filter(function (pkg) { return !!pkg.version; });
|
||||
packages.forEach(function (pkg) {
|
||||
requirements.push({ pkg: pkg, tag: pkg.originalTag || '~' + pkg.version });
|
||||
versions.push((pkg.originalTag || '~' + pkg.version).white);
|
||||
});
|
||||
|
||||
this.emit('error', new Error('No resolvable version for ' + name));
|
||||
this.emit('data', template('conflict', {
|
||||
name: name,
|
||||
requirements: requirements,
|
||||
json: config.json,
|
||||
versions: versions.slice(0, -1).join(', ') + ' or ' + versions[versions.length - 1]
|
||||
}, true));
|
||||
};
|
||||
|
||||
Manager.prototype.reportForceblyResolved = function (name, packages) {
|
||||
var requirements = [];
|
||||
|
||||
packages = packages.filter(function (pkg) { return !!pkg.version; });
|
||||
packages.forEach(function (pkg) {
|
||||
requirements.push({ pkg: pkg, tag: pkg.originalTag || '~' + pkg.version });
|
||||
});
|
||||
|
||||
this.emit('data', template('resolved-conflict', {
|
||||
name: name,
|
||||
requirements: requirements,
|
||||
json: config.json,
|
||||
resolvedTo: packages[0].version,
|
||||
forceLatest: this.opts.forceLatest
|
||||
}, true));
|
||||
};
|
||||
|
||||
|
||||
// ----- list ----- //
|
||||
|
||||
// Used in list command
|
||||
// TODO: not sure if this belongs here.. maybe move it to the list command?
|
||||
Manager.prototype.list = function (options) {
|
||||
options = options || {};
|
||||
// If the user passed the paths or map options, we don't need to fetch versions
|
||||
this._isCheckingVersions = !options.offline && !options.paths && !options.map && options.argv;
|
||||
this.once('resolveLocal', this.getDependencyList.bind(this))
|
||||
.resolveLocal();
|
||||
};
|
||||
|
||||
Manager.prototype.getDependencyList = function () {
|
||||
|
||||
var packages = {};
|
||||
var values;
|
||||
var checkVersions = this._isCheckingVersions;
|
||||
|
||||
if (checkVersions) {
|
||||
template('action', { name: 'discover', shizzle: 'Please wait while newer package versions are being discovered' })
|
||||
.on('data', this.emit.bind(this, 'data'));
|
||||
}
|
||||
|
||||
Object.keys(this.dependencies).forEach(function (key) {
|
||||
packages[key] = this.dependencies[key][0];
|
||||
}.bind(this));
|
||||
|
||||
values = _.values(packages);
|
||||
// Do not proceed if no values
|
||||
if (!values.length) {
|
||||
return packages;
|
||||
}
|
||||
// Load JSON and get version for each package
|
||||
async.forEach(values, function (pkg, next) {
|
||||
pkg.once('loadJSON', function () {
|
||||
// Only check versions if not offline and it's a repo
|
||||
var fetchVersions = checkVersions &&
|
||||
pkg.json.repository &&
|
||||
(pkg.json.repository.type === 'git' || pkg.json.repository.type === 'local-repo');
|
||||
|
||||
if (fetchVersions) {
|
||||
pkg.once('versions', function (versions) {
|
||||
pkg.tags = versions.map(function (ver) {
|
||||
return semver.valid(ver) ? semver.clean(ver) : ver;
|
||||
});
|
||||
next();
|
||||
}).versions();
|
||||
} else {
|
||||
pkg.tags = [];
|
||||
next();
|
||||
}
|
||||
}).loadJSON();
|
||||
}.bind(this), this.emit.bind(this, 'list', packages));
|
||||
};
|
||||
|
||||
module.exports = Manager;
|
||||
@@ -1,819 +0,0 @@
|
||||
// ==========================================
|
||||
// BOWER: Package Object Definition
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
// Events:
|
||||
// - install: fired when package installed
|
||||
// - resolve: fired when deps resolved
|
||||
// - error: fired on all errors
|
||||
// - data: fired when trying to output data
|
||||
// ==========================================
|
||||
|
||||
var fstream = require('fstream');
|
||||
var mkdirp = require('mkdirp');
|
||||
var events = require('events');
|
||||
var rimraf = require('rimraf');
|
||||
var semver = require('semver');
|
||||
var async = require('async');
|
||||
var https = require('https');
|
||||
var http = require('http');
|
||||
var path = require('path');
|
||||
var glob = require('glob');
|
||||
var url = require('url');
|
||||
var tmp = require('tmp');
|
||||
var fs = require('fs');
|
||||
var crypto = require('crypto');
|
||||
var unzip = require('unzip');
|
||||
var tar = require('tar');
|
||||
var _ = require('lodash');
|
||||
|
||||
var config = require('./config');
|
||||
var source = require('./source');
|
||||
var template = require('../util/template');
|
||||
var readJSON = require('../util/read-json');
|
||||
var fileExists = require('../util/file-exists');
|
||||
var isRepo = require('../util/is-repo');
|
||||
var git = require('../util/git-cmd');
|
||||
var UnitWork = require('./unit_work');
|
||||
|
||||
var Package = function (name, endpoint, manager) {
|
||||
this.dependencies = {};
|
||||
this.json = {};
|
||||
this.name = name;
|
||||
this.manager = manager;
|
||||
this.unitWork = manager ? manager.unitWork : new UnitWork;
|
||||
this.opts = manager ? manager.opts : {};
|
||||
|
||||
if (endpoint) {
|
||||
var split;
|
||||
|
||||
if (/^(.*\.git)$/.exec(endpoint)) {
|
||||
this.gitUrl = RegExp.$1.replace(/^git\+/, '');
|
||||
this.tag = false;
|
||||
|
||||
} else if (/^(.*\.git)#(.*)$/.exec(endpoint)) {
|
||||
this.tag = RegExp.$2;
|
||||
this.gitUrl = RegExp.$1.replace(/^git\+/, '');
|
||||
|
||||
} else if (/^(?:(git):|git\+(https?):)\/\/([^#]+)#?(.*)$/.exec(endpoint)) {
|
||||
this.gitUrl = (RegExp.$1 || RegExp.$2) + '://' + RegExp.$3;
|
||||
this.tag = RegExp.$4;
|
||||
|
||||
} else if (semver.validRange(endpoint)) {
|
||||
this.tag = endpoint;
|
||||
|
||||
} else if (/^[\.\/~]\.?[^.]*\.(js|css)/.test(endpoint) && fs.statSync(endpoint).isFile()) {
|
||||
this.path = path.resolve(endpoint);
|
||||
this.assetType = path.extname(endpoint);
|
||||
|
||||
} else if (/^https?:\/\//.exec(endpoint)) {
|
||||
this.assetUrl = endpoint;
|
||||
this.assetType = path.extname(endpoint);
|
||||
|
||||
} else if (fileExists.sync((split = endpoint.split('#', 2))[0]) && fs.statSync(split[0]).isDirectory()) {
|
||||
this.path = path.resolve(split[0]);
|
||||
this.tag = split[1];
|
||||
|
||||
} else if (/^[\.\/~]/.test(endpoint)) {
|
||||
this.path = path.resolve(endpoint);
|
||||
|
||||
} else if (endpoint.split('/').length === 2) {
|
||||
split = endpoint.split('#', 2);
|
||||
this.gitUrl = 'git://github.com/' + split[0] + '.git';
|
||||
this.tag = split[1];
|
||||
} else {
|
||||
split = endpoint.split('#', 2);
|
||||
this.tag = split[1];
|
||||
}
|
||||
|
||||
// Guess names
|
||||
if (!this.name) {
|
||||
if (this.gitUrl) this.name = path.basename(endpoint).replace(/(\.git)?(#.*)?$/, '');
|
||||
else if (this.path) this.name = path.basename(this.path, this.assetType);
|
||||
else if (this.assetUrl) this.name = this.name = path.basename(this.assetUrl, this.assetType);
|
||||
else if (split) this.name = split[0];
|
||||
}
|
||||
|
||||
// Store a reference to the original tag & original path
|
||||
// This is because the tag & paths can get rewriten later
|
||||
if (this.tag) this.originalTag = this.tag;
|
||||
if (this.path) this.originalPath = endpoint;
|
||||
|
||||
// The id is an unique id that describes this package
|
||||
this.id = crypto.createHash('md5').update(this.name + '%' + this.tag + '%' + this.gitUrl + '%' + this.path + '%' + this.assetUrl).digest('hex');
|
||||
|
||||
// Generate a resource id
|
||||
if (this.gitUrl) this.generateResourceId();
|
||||
}
|
||||
|
||||
if (this.manager) {
|
||||
this.on('data', this.manager.emit.bind(this.manager, 'data'));
|
||||
this.on('error', function (err, origin) {
|
||||
// Unlock the unit of work automatically on error (only if the error is from this package)
|
||||
if (!origin && this.unitWork.isLocked(this.name)) this.unitWork.unlock(this.name, this);
|
||||
// Propagate the error event to the parent package/manager
|
||||
this.manager.emit('error', err, origin || this);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
// Cache a self bound function
|
||||
this.waitUnlock = this.waitUnlock.bind(this);
|
||||
|
||||
this.setMaxListeners(30); // Increase the number of listeners because a package can have more than the default 10 dependencies
|
||||
};
|
||||
|
||||
Package.prototype = Object.create(events.EventEmitter.prototype);
|
||||
|
||||
Package.prototype.constructor = Package;
|
||||
|
||||
Package.prototype.resolve = function () {
|
||||
// Ensure that nobody is resolving the same dep at the same time
|
||||
// If there is, we wait for the unlock event
|
||||
if (this.unitWork.isLocked(this.name)) return this.unitWork.on('unlock', this.waitUnlock);
|
||||
|
||||
var data = this.unitWork.retrieve(this.name);
|
||||
if (data) {
|
||||
// Check if this exact package is the last resolved one
|
||||
// If so, we copy the resolved result and we don't need to do anything else
|
||||
if (data.id === this.id) {
|
||||
this.unserialize(data);
|
||||
this.emit('resolve');
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// If not, we lock and resolve it
|
||||
this.unitWork.lock(this.name, this);
|
||||
|
||||
if (this.assetUrl) {
|
||||
this.download();
|
||||
} else if (this.gitUrl) {
|
||||
this.clone();
|
||||
} else if (this.path) {
|
||||
this.copy();
|
||||
} else {
|
||||
this.once('lookup', this.clone).lookup();
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Package.prototype.lookup = function () {
|
||||
source.lookup(this.name, function (err, url) {
|
||||
if (err) return this.emit('error', err);
|
||||
this.lookedUp = true;
|
||||
this.gitUrl = url;
|
||||
this.generateResourceId();
|
||||
this.emit('lookup');
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.install = function () {
|
||||
// Only print the installing action if this package has been resolved
|
||||
if (this.unitWork.retrieve(this.name)) {
|
||||
template('action', { name: 'installing', shizzle: this.name + (this.version ? '#' + this.version : '') })
|
||||
.on('data', this.emit.bind(this, 'data'));
|
||||
}
|
||||
|
||||
var localPath = this.localPath;
|
||||
|
||||
if (path.resolve(this.path) === localPath) {
|
||||
this.emit('install');
|
||||
return this;
|
||||
}
|
||||
|
||||
// Remove stuff from the local path (if any)
|
||||
// Rename path to the local path
|
||||
// Beware that if the local path exists and is a git repository, the process is aborted
|
||||
isRepo(localPath, function (is) {
|
||||
if (is) {
|
||||
var err = new Error('Local path is a local repository');
|
||||
err.details = 'To avoid losing work, please remove ' + localPath + ' manually.';
|
||||
return this.emit('error', err, this);
|
||||
}
|
||||
|
||||
mkdirp(path.dirname(localPath), function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
rimraf(localPath, function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
return fs.rename(this.path, localPath, function (err) {
|
||||
if (!err) return this.cleanUpLocal();
|
||||
|
||||
var writter = fstream.Writer({
|
||||
type: 'Directory',
|
||||
path: localPath
|
||||
});
|
||||
writter
|
||||
.on('error', this.emit.bind(this, 'error'))
|
||||
.on('end', rimraf.bind(this, this.path, this.cleanUpLocal.bind(this)));
|
||||
|
||||
fstream.Reader(this.path)
|
||||
.on('error', this.emit.bind(this, 'error'))
|
||||
.pipe(writter);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Package.prototype.cleanUpLocal = function () {
|
||||
this.once('readLocalConfig', function () {
|
||||
this.json.name = this.name;
|
||||
this.json.version = this.commit ? '0.0.0' : this.version || '0.0.0';
|
||||
|
||||
// Detect commit and save it in the json for later use
|
||||
if (this.commit) this.json.commit = this.commit;
|
||||
else delete this.json.commit;
|
||||
|
||||
if (this.gitUrl) this.json.repository = { type: 'git', url: this.gitUrl };
|
||||
else if (this.gitPath) this.json.repository = { type: 'local-repo', path: this.originalPath };
|
||||
else if (this.originalPath) this.json.repository = { type: 'local', path: this.originalPath };
|
||||
else if (this.assetUrl) this.json = this.generateAssetJSON();
|
||||
|
||||
var jsonStr = JSON.stringify(this.json, null, 2);
|
||||
|
||||
fs.writeFile(path.join(this.localPath, this.localConfig.json), jsonStr);
|
||||
if (this.gitUrl || this.gitPath) fs.writeFile(path.join(this.gitPath, this.localConfig.json), jsonStr);
|
||||
|
||||
this.removeLocalPaths();
|
||||
}.bind(this)).readLocalConfig();
|
||||
};
|
||||
|
||||
// finish clean up local by removing .git/ and any ignored files
|
||||
Package.prototype.removeLocalPaths = function () {
|
||||
var removePatterns = ['.git'];
|
||||
if (this.json.ignore) {
|
||||
removePatterns.push.apply(removePatterns, this.json.ignore);
|
||||
}
|
||||
|
||||
var removePaths = [];
|
||||
|
||||
// 3: done
|
||||
var pathsRemoved = function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
this.emit('install');
|
||||
}.bind(this);
|
||||
|
||||
// 2: trigger after paths have been globbed
|
||||
var rimrafPaths = function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
async.forEach(removePaths, function (removePath, next) {
|
||||
// rimraf all the paths
|
||||
rimraf(path.join(this.localPath, removePath), next);
|
||||
}.bind(this), pathsRemoved);
|
||||
}.bind(this);
|
||||
|
||||
// 1: get paths
|
||||
var globOpts = { dot: true, cwd: this.localPath };
|
||||
async.forEach(removePatterns, function (removePattern, next) {
|
||||
// glob path for file path pattern matching
|
||||
glob(removePattern, globOpts, function (err, globPaths) {
|
||||
if (err) return next(err);
|
||||
removePaths.push.apply(removePaths, globPaths);
|
||||
next();
|
||||
}.bind(this));
|
||||
}.bind(this), rimrafPaths);
|
||||
};
|
||||
|
||||
Package.prototype.generateAssetJSON = function () {
|
||||
return {
|
||||
name: this.name,
|
||||
main: this.assetType !== '.zip' && this.assetType !== '.tar' ? 'index' + this.assetType : '',
|
||||
version: '0.0.0',
|
||||
repository: { type: 'asset', url: this.assetUrl }
|
||||
};
|
||||
};
|
||||
|
||||
Package.prototype.uninstall = function () {
|
||||
template('action', { name: 'uninstalling', shizzle: this.path })
|
||||
.on('data', this.emit.bind(this, 'data'));
|
||||
rimraf(this.path, function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
this.emit('uninstall');
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
// Private
|
||||
Package.prototype.readLocalConfig = function () {
|
||||
if (this.localConfig) return this.emit('readLocalConfig');
|
||||
|
||||
var checkExistence = function () {
|
||||
fileExists(path.join(this.path, this.localConfig.json), function (exists) {
|
||||
if (!exists) {
|
||||
this.localConfig.json = 'component.json';
|
||||
}
|
||||
|
||||
this.emit('readLocalConfig');
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
|
||||
fs.readFile(path.join(this.path, '.bowerrc'), function (err, file) {
|
||||
// If the local .bowerrc file do not exists then we check if the
|
||||
// json specific in the config exists (if not, we fallback to component.json)
|
||||
if (err) {
|
||||
this.localConfig = { json: config.json };
|
||||
checkExistence();
|
||||
} else {
|
||||
// If the local .bowerrc file exists, we read it and check if a custom json file
|
||||
// is defined. If not, we check if the global config json file exists (if not, we fallback to component.json)
|
||||
try {
|
||||
this.localConfig = JSON.parse(file);
|
||||
} catch (e) {
|
||||
return this.emit('error', new Error('Unable to parse local .bowerrc file: ' + e.message));
|
||||
}
|
||||
|
||||
if (!this.localConfig.json) {
|
||||
this.localConfig.json = config.json;
|
||||
return checkExistence();
|
||||
}
|
||||
|
||||
this.emit('readLocalConfig');
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.loadJSON = function () {
|
||||
if (!this.path || this.assetUrl) return this.emit('loadJSON');
|
||||
|
||||
this.once('readLocalConfig', function () {
|
||||
var jsonFile = path.join(this.path, this.localConfig.json);
|
||||
fileExists(jsonFile, function (exists) {
|
||||
// If the json does not exists, we attempt to get the version
|
||||
if (!exists) {
|
||||
return this.once('describeTag', function (tag) {
|
||||
tag = semver.clean(tag);
|
||||
if (!tag) this.version = this.tag;
|
||||
else {
|
||||
this.version = tag;
|
||||
if (!this.tag) this.tag = this.version;
|
||||
}
|
||||
|
||||
this.emit('loadJSON');
|
||||
}.bind(this)).describeTag();
|
||||
}
|
||||
|
||||
readJSON(jsonFile, function (err, json) {
|
||||
if (err) {
|
||||
err.details = 'An error was caught when reading the ' + this.localConfig.json + ':' + err.message;
|
||||
return this.emit('error', err);
|
||||
}
|
||||
|
||||
this.json = json;
|
||||
this.version = this.commit || json.commit || json.version;
|
||||
this.commit = this.commit || json.commit;
|
||||
// Only overwrite the name if not already set
|
||||
// This is because some packages have different names declared in the registry and the json
|
||||
if (!this.name) this.name = json.name;
|
||||
|
||||
// Read the endpoint from the json to ensure it is set correctly
|
||||
this.readEndpoint();
|
||||
|
||||
// Detect if the tag mismatches the json.version
|
||||
// This is very often to happen because developers tag their new releases but forget to update the json accordingly
|
||||
var cleanedTag;
|
||||
if (this.tag && (cleanedTag = semver.clean(this.tag)) && cleanedTag !== this.version) {
|
||||
// Only print the warning once
|
||||
if (!this.unitWork.retrieve('mismatch#' + this.name + '_' + cleanedTag)) {
|
||||
template('warning-mismatch', { name: this.name, json: this.localConfig.json, tag: cleanedTag, version: this.version || 'N/A' })
|
||||
.on('data', this.emit.bind(this, 'data'));
|
||||
this.unitWork.store('mismatch#' + this.name + '_' + cleanedTag, true);
|
||||
}
|
||||
// Assume the tag
|
||||
this.version = cleanedTag;
|
||||
}
|
||||
|
||||
this.emit('loadJSON');
|
||||
}.bind(this), this);
|
||||
}.bind(this));
|
||||
}.bind(this)).readLocalConfig();
|
||||
};
|
||||
|
||||
Package.prototype.download = function () {
|
||||
template('action', { name: 'downloading', shizzle: this.assetUrl })
|
||||
.on('data', this.emit.bind(this, 'data'));
|
||||
|
||||
var src;
|
||||
|
||||
if (config.proxy) {
|
||||
src = url.parse(config.proxy);
|
||||
src.path = this.assetUrl;
|
||||
} else {
|
||||
src = url.parse(this.assetUrl);
|
||||
}
|
||||
|
||||
tmp.dir({ prefix: 'bower-' + this.name + '-', mode: parseInt('0777', 8) & (~process.umask()) }, function (err, tmpPath) {
|
||||
if (err) return this.emit('error', err);
|
||||
|
||||
var req = src.protocol === 'https:' ? https : http;
|
||||
req.get(src, function (res) {
|
||||
// If assetUrl results in a redirect we update the assetUrl to the redirect to url
|
||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||
template('action', { name: 'redirect detected', shizzle: this.assetUrl })
|
||||
.on('data', this.emit.bind(this, 'data'));
|
||||
this.assetUrl = res.headers.location;
|
||||
return this.download();
|
||||
}
|
||||
|
||||
// Detect not OK status codes
|
||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||
return this.emit('error', new Error(res.statusCode + ' status code for ' + this.assetUrl));
|
||||
}
|
||||
|
||||
var file = fs.createWriteStream(path.join((this.path = tmpPath), 'index' + this.assetType));
|
||||
|
||||
res.on('data', function (data) {
|
||||
file.write(data);
|
||||
});
|
||||
|
||||
res.on('end', function () {
|
||||
file.end();
|
||||
|
||||
var next = function () {
|
||||
this.once('loadJSON', this.saveUnit).loadJSON();
|
||||
}.bind(this);
|
||||
|
||||
if (this.assetType === '.zip' || this.assetType === '.tar') this.once('extract', next).extract();
|
||||
else next();
|
||||
}.bind(this));
|
||||
}.bind(this)).on('error', this.emit.bind(this, 'error'));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.extract = function () {
|
||||
var file = path.join(this.path, 'index' + this.assetType);
|
||||
template('action', { name: 'extracting', shizzle: file }).on('data', this.emit.bind(this, 'data'));
|
||||
|
||||
fs.createReadStream(file).pipe(this.assetType === '.zip' ? unzip.Extract({ path: this.path }) : tar.Extract({ path: this.path }))
|
||||
.on('error', this.emit.bind(this, 'error'))
|
||||
.on('close', function () {
|
||||
// Delete zip
|
||||
fs.unlink(file, function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
|
||||
// If we extracted only a folder, move all the files within it to the original path
|
||||
fs.readdir(this.path, function (err, files) {
|
||||
if (err) return this.emit('error', err);
|
||||
|
||||
if (files.length !== 1) return this.emit('extract');
|
||||
|
||||
var dir = path.join(this.path, files[0]);
|
||||
fs.stat(dir, function (err, stat) {
|
||||
if (err) return this.emit('error', err);
|
||||
if (!stat.isDirectory()) return this.emit('extract');
|
||||
|
||||
fs.readdir(dir, function (err, files) {
|
||||
if (err) return this.emit('error', err);
|
||||
|
||||
async.forEachSeries(files, function (file, next) {
|
||||
fs.rename(path.join(dir, file), path.join(this.path, file), next);
|
||||
}.bind(this), function (err) {
|
||||
if (err) return this.emit('error');
|
||||
|
||||
fs.rmdir(dir, function (err) {
|
||||
if (err) return this.emit('error');
|
||||
this.emit('extract');
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.copy = function () {
|
||||
template('action', { name: 'copying', shizzle: this.path }).on('data', this.emit.bind(this, 'data'));
|
||||
|
||||
tmp.dir({ prefix: 'bower-' + this.name + '-' }, function (err, tmpPath) {
|
||||
if (err) return this.emit('error', err);
|
||||
|
||||
fs.stat(this.path, function (err, stats) {
|
||||
if (err) return this.emit('error', err);
|
||||
|
||||
// Copy file permission for directory
|
||||
fs.chmod(tmpPath, stats.mode, function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
|
||||
if (this.assetType) {
|
||||
return fs.readFile(this.path, function (err, data) {
|
||||
fs.writeFile(path.join((this.path = tmpPath), 'index' + this.assetType), data, function () {
|
||||
this.once('loadJSON', this.saveUnit).loadJSON();
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
this.once('loadJSON', function () {
|
||||
if (this.gitUrl) return this.saveUnit();
|
||||
|
||||
// Check if the copied directory is a git repository and is a local endpoint
|
||||
// If so, treat it like a repository.
|
||||
fileExists(path.join(this.path, '.git'), function (exists) {
|
||||
if (!exists) return this.saveUnit();
|
||||
|
||||
this.gitPath = this.path;
|
||||
this.once('loadJSON', this.saveUnit.bind(this)).checkout();
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
|
||||
var writter = fstream.Writer({
|
||||
type: 'Directory',
|
||||
path: tmpPath
|
||||
})
|
||||
.on('error', this.emit.bind(this, 'error'))
|
||||
.on('end', this.loadJSON.bind(this));
|
||||
|
||||
fstream.Reader(this.path)
|
||||
.on('error', this.emit.bind(this, 'error'))
|
||||
.pipe(writter);
|
||||
|
||||
this.path = tmpPath;
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.getDeepDependencies = function (result) {
|
||||
result = result || [];
|
||||
for (var name in this.dependencies) {
|
||||
result.push(this.dependencies[name]);
|
||||
this.dependencies[name].getDeepDependencies(result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
Package.prototype.saveUnit = function () {
|
||||
this.unitWork.store(this.name, this.serialize(), this);
|
||||
this.unitWork.unlock(this.name, this);
|
||||
this.addDependencies();
|
||||
};
|
||||
|
||||
Package.prototype.addDependencies = function () {
|
||||
var dependencies = this.json.dependencies || {};
|
||||
var callbacks = Object.keys(dependencies).map(function (name) {
|
||||
return function (callback) {
|
||||
var endpoint = dependencies[name];
|
||||
this.dependencies[name] = new Package(name, endpoint, this);
|
||||
this.dependencies[name].once('resolve', callback).resolve();
|
||||
}.bind(this);
|
||||
}.bind(this));
|
||||
async.parallel(callbacks, function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
this.emit('resolve');
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.exists = function (callback) {
|
||||
fileExists(this.localPath, callback);
|
||||
};
|
||||
|
||||
Package.prototype.clone = function () {
|
||||
template('action', { name: 'cloning', shizzle: this.gitUrl }).on('data', this.emit.bind(this, 'data'));
|
||||
this.path = this.gitPath;
|
||||
this.once('cache', function () {
|
||||
this.once('loadJSON', this.copy.bind(this)).checkout();
|
||||
}.bind(this)).cache();
|
||||
};
|
||||
|
||||
Package.prototype.cache = function () {
|
||||
// If the force options is true, we need to erase from the cache
|
||||
// Be aware that a similar package might already flushed it
|
||||
// To prevent that we check the unit of work storage
|
||||
if (this.opts.force && !this.unitWork.retrieve('flushed#' + this.name + '_' + this.resourceId)) {
|
||||
rimraf(this.path, function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
this.unitWork.store('flushed#' + this.name + '_' + this.resourceId, true);
|
||||
this.cache();
|
||||
}.bind(this));
|
||||
return this;
|
||||
}
|
||||
|
||||
mkdirp(config.cache, function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
fileExists(this.path, function (exists) {
|
||||
if (exists) {
|
||||
template('action', { name: 'cached', shizzle: this.gitUrl }).on('data', this.emit.bind(this, 'data'));
|
||||
return this.emit('cache');
|
||||
}
|
||||
template('action', { name: 'caching', shizzle: this.gitUrl }).on('data', this.emit.bind(this, 'data'));
|
||||
var url = this.gitUrl;
|
||||
if (config.proxy) {
|
||||
url = url.replace(/^git:/, 'https:');
|
||||
}
|
||||
|
||||
mkdirp(this.path, function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
|
||||
var cp = git(['clone', url, this.path], null, this);
|
||||
cp.on('close', function (code) {
|
||||
if (code) return;
|
||||
this.emit('cache');
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.checkout = function () {
|
||||
template('action', { name: 'fetching', shizzle: this.name })
|
||||
.on('data', this.emit.bind(this, 'data'));
|
||||
|
||||
this.once('versions', function (versions) {
|
||||
if (!versions.length) {
|
||||
this.emit('checkout');
|
||||
this.loadJSON();
|
||||
}
|
||||
|
||||
// If tag is specified, try to satisfy it
|
||||
if (this.tag) {
|
||||
if (!semver.validRange(this.tag)) {
|
||||
return this.emit('error', new Error('Tag ' + this.tag + ' is not a valid semver range/version'));
|
||||
}
|
||||
|
||||
versions = versions.filter(function (version) {
|
||||
return semver.satisfies(version, this.tag);
|
||||
}.bind(this));
|
||||
|
||||
if (!versions.length) {
|
||||
var error = new Error('Could not find tag satisfying: ' + this.name + '#' + this.tag);
|
||||
error.details = 'The tag ' + this.tag + ' could not be found within the repository';
|
||||
return this.emit('error', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Use latest version
|
||||
this.tag = versions[0];
|
||||
if (!semver.valid(this.tag)) this.commit = this.tag; // If the version is not valid, then its a commit
|
||||
|
||||
if (this.tag) {
|
||||
template('action', {
|
||||
name: 'checking out',
|
||||
shizzle: this.name + '#' + this.tag
|
||||
}).on('data', this.emit.bind(this, 'data'));
|
||||
|
||||
// Checkout the tag
|
||||
git([ 'checkout', this.tag, '-f'], { cwd: this.path }, this).on('close', function (code) {
|
||||
if (code) return;
|
||||
// Ensure that checkout the tag as it is, removing all untracked files
|
||||
git(['clean', '-f', '-d'], { cwd: this.path }, this).on('close', function (code) {
|
||||
if (code) return;
|
||||
this.emit('checkout');
|
||||
this.loadJSON();
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
}).versions();
|
||||
};
|
||||
|
||||
Package.prototype.describeTag = function () {
|
||||
var cp = git(['describe', '--always', '--tag'], { cwd: this.gitPath || this.path, ignoreCodes: [128] }, this);
|
||||
var tag = '';
|
||||
|
||||
cp.stdout.setEncoding('utf8');
|
||||
cp.stdout.on('data', function (data) {
|
||||
tag += data;
|
||||
});
|
||||
|
||||
cp.on('close', function (code) {
|
||||
if (code === 128) tag = 'unspecified'.grey; // Not a git repo
|
||||
this.emit('describeTag', tag.replace(/\n$/, ''));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.versions = function () {
|
||||
this.once('fetch', function () {
|
||||
var cp = git(['tag'], { cwd: this.gitPath }, this);
|
||||
|
||||
var versions = '';
|
||||
|
||||
cp.stdout.setEncoding('utf8');
|
||||
cp.stdout.on('data', function (data) {
|
||||
versions += data;
|
||||
});
|
||||
|
||||
cp.on('close', function (code) {
|
||||
if (code) return;
|
||||
versions = versions.split('\n');
|
||||
versions = versions.filter(function (ver) {
|
||||
return semver.valid(ver);
|
||||
});
|
||||
versions = versions.sort(function (a, b) {
|
||||
return semver.gt(a, b) ? -1 : 1;
|
||||
});
|
||||
|
||||
if (versions.length) return this.emit('versions', versions);
|
||||
|
||||
// If there is no versions tagged in the repo
|
||||
// then we grab the hash of the last commit
|
||||
versions = '';
|
||||
cp = git(['log', '-n', 1, '--format=%H'], { cwd: this.gitPath }, this);
|
||||
|
||||
cp.stdout.setEncoding('utf8');
|
||||
cp.stdout.on('data', function (data) {
|
||||
versions += data;
|
||||
});
|
||||
cp.on('close', function (code) {
|
||||
if (code) return;
|
||||
versions = _.compact(versions.split('\n'));
|
||||
this.emit('versions', versions);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this)).fetch();
|
||||
};
|
||||
|
||||
Package.prototype.fetch = function () {
|
||||
fileExists(this.gitPath, function (exists) {
|
||||
if (!exists) return this.emit('error', new Error('Unable to fetch package ' + this.name + ' (if the cache was deleted, run install again)'));
|
||||
|
||||
var cp = git(['fetch', '--prune'], { cwd: this.gitPath }, this);
|
||||
cp.on('close', function (code) {
|
||||
if (code) return;
|
||||
cp = git(['reset', '--hard', this.gitUrl ? 'origin/HEAD' : 'HEAD'], { cwd: this.gitPath }, this);
|
||||
cp.on('close', function (code) {
|
||||
if (code) return;
|
||||
this.emit('fetch');
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.readEndpoint = function (replace) {
|
||||
if (!this.json.repository) return;
|
||||
|
||||
if (this.json.repository.type === 'git') {
|
||||
if (replace || !this.gitUrl) {
|
||||
this.gitUrl = this.json.repository.url;
|
||||
this.generateResourceId();
|
||||
}
|
||||
return { type: 'git', endpoint: this.gitUrl };
|
||||
}
|
||||
if (this.json.repository.type === 'local-repo') {
|
||||
if (replace || !this.gitPath) {
|
||||
this.gitPath = path.resolve(this.json.repository.path);
|
||||
}
|
||||
return { type: 'local', endpoint: this.path };
|
||||
}
|
||||
if (this.json.repository.type === 'local') {
|
||||
if (replace || !this.path) {
|
||||
this.path = path.resolve(this.json.repository.path);
|
||||
}
|
||||
return { type: 'local', endpoint: this.path };
|
||||
}
|
||||
if (this.json.repository.type === 'asset') {
|
||||
if (replace || !this.assetUrl) {
|
||||
this.assetUrl = this.json.repository.url;
|
||||
this.assetType = path.extname(this.assetUrl);
|
||||
}
|
||||
return { type: 'asset', endpoint: this.assetUrl };
|
||||
}
|
||||
};
|
||||
|
||||
Package.prototype.waitUnlock = function (name) {
|
||||
if (this.name === name) {
|
||||
this.unitWork.removeListener('unlock', this.waitUnlock);
|
||||
this.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
Package.prototype.serialize = function () {
|
||||
return {
|
||||
id: this.id,
|
||||
resourceId: this.resourceId,
|
||||
path: this.path,
|
||||
originalPath: this.originalPath,
|
||||
tag: this.tag,
|
||||
originalTag: this.originalTag,
|
||||
commit: this.commit,
|
||||
assetUrl: this.assetUrl,
|
||||
assetType: this.assetType,
|
||||
lookedUp: this.lookedUp,
|
||||
json: this.json,
|
||||
gitUrl: this.gitUrl,
|
||||
gitPath: this.gitPath,
|
||||
dependencies: this.dependencies,
|
||||
localConfig: this.localConfig
|
||||
};
|
||||
};
|
||||
|
||||
Package.prototype.unserialize = function (obj) {
|
||||
for (var key in obj) {
|
||||
this[key] = obj[key];
|
||||
}
|
||||
|
||||
this.version = this.tag;
|
||||
};
|
||||
|
||||
Package.prototype.generateResourceId = function () {
|
||||
this.resourceId = crypto.createHash('md5').update(this.name + '%' + this.gitUrl).digest('hex');
|
||||
this.gitPath = path.join(config.cache, this.name, this.resourceId);
|
||||
};
|
||||
|
||||
Package.prototype.__defineGetter__('localPath', function () {
|
||||
return path.join(process.cwd(), config.directory, this.name);
|
||||
});
|
||||
|
||||
module.exports = Package;
|
||||
236
lib/core/resolverFactory.js
Normal file
236
lib/core/resolverFactory.js
Normal file
@@ -0,0 +1,236 @@
|
||||
var Q = require('q');
|
||||
var fs = require('../util/fs');
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var resolvers = require('./resolvers');
|
||||
var createError = require('../util/createError');
|
||||
var resolve = require('../util/resolve');
|
||||
|
||||
var pluginResolverFactory = require('./resolvers/pluginResolverFactory');
|
||||
|
||||
function createInstance(decEndpoint, options, registryClient) {
|
||||
decEndpoint = mout.object.pick(decEndpoint, ['name', 'target', 'source']);
|
||||
|
||||
options.version = require('../version');
|
||||
|
||||
return getConstructor(decEndpoint, options, registryClient)
|
||||
.spread(function (ConcreteResolver, decEndpoint) {
|
||||
return new ConcreteResolver(decEndpoint, options.config, options.logger);
|
||||
});
|
||||
}
|
||||
|
||||
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 constructor 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];
|
||||
|
||||
if (resolver === undefined) {
|
||||
var resolverPath = resolve(resolverName, { cwd: config.cwd });
|
||||
|
||||
if (resolverPath === undefined) {
|
||||
throw createError('Bower resolver not found: ' + resolverName, 'ENORESOLVER')
|
||||
}
|
||||
|
||||
resolver = pluginResolverFactory(require(resolverPath), 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
|
||||
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, decEndpoint];
|
||||
}
|
||||
|
||||
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
|
||||
addResolver(function () {
|
||||
if (/^https?:\/\//i.exec(source)) {
|
||||
return [resolvers.Url, decEndpoint];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// If source is ./ or ../ or an absolute path
|
||||
|
||||
addResolver(function () {
|
||||
var absolutePath = path.resolve(config.cwd, source);
|
||||
|
||||
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]);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Check if is a shorthand and expand it
|
||||
addResolver(function () {
|
||||
// Skip ssh and/or URL with auth
|
||||
if (/[:@]/.test(source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure exactly only one "/"
|
||||
var parts = source.split('/');
|
||||
if (parts.length === 2) {
|
||||
decEndpoint.source = mout.string.interpolate(config.shorthandResolver, {
|
||||
shorthand: source,
|
||||
owner: parts[0],
|
||||
package: parts[1]
|
||||
});
|
||||
|
||||
return getConstructor(decEndpoint, options, registryClient);
|
||||
}
|
||||
});
|
||||
|
||||
// As last resort, we try the registry
|
||||
addResolver(function () {
|
||||
if (!registryClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Q.nfcall(registryClient.lookup.bind(registryClient), source)
|
||||
.then(function (entry) {
|
||||
if (!entry) {
|
||||
throw createError('Package ' + source + ' not found', 'ENOTFOUND');
|
||||
}
|
||||
|
||||
decEndpoint.registry = true;
|
||||
|
||||
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() {
|
||||
mout.object.values(resolvers).forEach(function (ConcreteResolver) {
|
||||
ConcreteResolver.clearRuntimeCache();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = createInstance;
|
||||
module.exports.getConstructor = getConstructor;
|
||||
module.exports.clearRuntimeCache = clearRuntimeCache;
|
||||
143
lib/core/resolvers/FsResolver.js
Normal file
143
lib/core/resolvers/FsResolver.js
Normal file
@@ -0,0 +1,143 @@
|
||||
var util = require('util');
|
||||
var fs = require('../../util/fs');
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var Q = require('q');
|
||||
var junk = require('junk');
|
||||
var Resolver = require('./Resolver');
|
||||
var copy = require('../../util/copy');
|
||||
var extract = require('../../util/extract');
|
||||
var createError = require('../../util/createError');
|
||||
|
||||
function FsResolver(decEndpoint, config, logger) {
|
||||
Resolver.call(this, decEndpoint, config, logger);
|
||||
|
||||
// Ensure absolute path
|
||||
this._source = path.resolve(this._config.cwd, this._source);
|
||||
|
||||
// If target was specified, simply reject the promise
|
||||
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);
|
||||
mout.object.mixIn(FsResolver, Resolver);
|
||||
|
||||
// -----------------
|
||||
|
||||
FsResolver.isTargetable = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
// TODO: Should we store latest mtimes in the resolution and compare?
|
||||
// This would be beneficial when copying big files/folders
|
||||
|
||||
// TODO: There's room for improvement by using streams if the source
|
||||
// is an archive file, by piping read stream to the zip extractor
|
||||
// This will likely increase the complexity of code but might worth it
|
||||
FsResolver.prototype._resolve = function () {
|
||||
return this._copy()
|
||||
.then(this._extract.bind(this))
|
||||
.then(this._rename.bind(this));
|
||||
};
|
||||
|
||||
// -----------------
|
||||
|
||||
FsResolver.prototype._copy = function () {
|
||||
var that = this;
|
||||
|
||||
return Q.nfcall(fs.stat, this._source)
|
||||
.then(function (stat) {
|
||||
var dst;
|
||||
var copyOpts;
|
||||
var promise;
|
||||
|
||||
that._sourceStat = stat;
|
||||
copyOpts = { mode: stat.mode };
|
||||
|
||||
// If it's a folder
|
||||
if (stat.isDirectory()) {
|
||||
dst = that._tempDir;
|
||||
|
||||
// Read the bower.json inside the folder, so that we
|
||||
// copy only the necessary files if it has ignore specified
|
||||
promise = that._readJson(that._source)
|
||||
.then(function (json) {
|
||||
copyOpts.ignore = json.ignore;
|
||||
return copy.copyDir(that._source, dst, copyOpts);
|
||||
})
|
||||
.then(function () {
|
||||
// Resolve to null because it's a dir
|
||||
return;
|
||||
});
|
||||
// Else it's a file
|
||||
} else {
|
||||
dst = path.join(that._tempDir, path.basename(that._source));
|
||||
promise = copy.copyFile(that._source, dst, copyOpts)
|
||||
.then(function () {
|
||||
return dst;
|
||||
});
|
||||
}
|
||||
|
||||
that._logger.action('copy', that._source, {
|
||||
src: that._source,
|
||||
dst: dst
|
||||
});
|
||||
|
||||
return promise;
|
||||
});
|
||||
};
|
||||
|
||||
FsResolver.prototype._extract = function (file) {
|
||||
if (!file || !extract.canExtract(file)) {
|
||||
return Q.resolve();
|
||||
}
|
||||
|
||||
this._logger.action('extract', path.basename(this._source), {
|
||||
archive: file,
|
||||
to: this._tempDir
|
||||
});
|
||||
|
||||
return extract(file, this._tempDir);
|
||||
};
|
||||
|
||||
FsResolver.prototype._rename = function () {
|
||||
return Q.nfcall(fs.readdir, this._tempDir)
|
||||
.then(function (files) {
|
||||
var file;
|
||||
var oldPath;
|
||||
var newPath;
|
||||
|
||||
// Remove any OS specific files from the files array
|
||||
// before checking its length
|
||||
files = files.filter(junk.isnt);
|
||||
|
||||
// Only rename if there's only one file and it's not the json
|
||||
if (files.length === 1 && !/^(bower|component)\.json$/.test(files[0])) {
|
||||
file = files[0];
|
||||
this._singleFile = 'index' + path.extname(file);
|
||||
oldPath = path.join(this._tempDir, file);
|
||||
newPath = path.join(this._tempDir, this._singleFile);
|
||||
|
||||
return Q.nfcall(fs.rename, oldPath, newPath);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
FsResolver.prototype._savePkgMeta = function (meta) {
|
||||
// Store main if is a single file
|
||||
if (this._singleFile) {
|
||||
meta.main = this._singleFile;
|
||||
}
|
||||
|
||||
return Resolver.prototype._savePkgMeta.call(this, meta);
|
||||
};
|
||||
|
||||
module.exports = FsResolver;
|
||||
77
lib/core/resolvers/GitFsResolver.js
Normal file
77
lib/core/resolvers/GitFsResolver.js
Normal file
@@ -0,0 +1,77 @@
|
||||
var util = require('util');
|
||||
var Q = require('q');
|
||||
var mout = require('mout');
|
||||
var path = require('path');
|
||||
var GitResolver = require('./GitResolver');
|
||||
var copy = require('../../util/copy');
|
||||
var cmd = require('../../util/cmd');
|
||||
|
||||
function GitFsResolver(decEndpoint, config, logger) {
|
||||
GitResolver.call(this, decEndpoint, config, logger);
|
||||
|
||||
// Ensure absolute path
|
||||
this._source = path.resolve(this._config.cwd, this._source);
|
||||
}
|
||||
|
||||
util.inherits(GitFsResolver, GitResolver);
|
||||
mout.object.mixIn(GitFsResolver, GitResolver);
|
||||
|
||||
// -----------------
|
||||
|
||||
// Override the checkout function to work with the local copy
|
||||
GitFsResolver.prototype._checkout = function () {
|
||||
var resolution = this._resolution;
|
||||
|
||||
// The checkout process could be similar to the GitRemoteResolver by prepending file:// to the source
|
||||
// But from my performance measures, it's faster to copy the folder and just checkout in there
|
||||
this._logger.action('checkout', resolution.tag || resolution.branch || resolution.commit, {
|
||||
resolution: resolution,
|
||||
to: this._tempDir
|
||||
});
|
||||
|
||||
// Copy files to the temporary directory first
|
||||
return this._copy()
|
||||
.then(cmd.bind(cmd, 'git', ['checkout', '-f', resolution.tag || resolution.branch || resolution.commit], { cwd: this._tempDir }))
|
||||
// Cleanup unstaged files
|
||||
.then(cmd.bind(cmd, 'git', ['clean', '-f', '-d'], { cwd: this._tempDir }));
|
||||
};
|
||||
|
||||
GitFsResolver.prototype._copy = function () {
|
||||
return copy.copyDir(this._source, this._tempDir);
|
||||
};
|
||||
|
||||
// -----------------
|
||||
|
||||
// Grab refs locally
|
||||
GitFsResolver.refs = function (source) {
|
||||
var value;
|
||||
|
||||
// TODO: Normalize source because of the various available protocols?
|
||||
value = this._cache.refs.get(source);
|
||||
if (value) {
|
||||
return Q.resolve(value);
|
||||
}
|
||||
|
||||
value = cmd('git', ['show-ref', '--tags', '--heads'], { cwd : source })
|
||||
.spread(function (stdout) {
|
||||
var refs;
|
||||
|
||||
refs = stdout.toString()
|
||||
.trim() // Trim trailing and leading spaces
|
||||
.replace(/[\t ]+/g, ' ') // Standardize spaces (some git versions make tabs, other spaces)
|
||||
.split(/[\r\n]+/); // Split lines into an array
|
||||
|
||||
// Update the refs with the actual refs
|
||||
this._cache.refs.set(source, refs);
|
||||
|
||||
return refs;
|
||||
}.bind(this));
|
||||
|
||||
// Store the promise to be reused until it resolves
|
||||
// to a specific value
|
||||
this._cache.refs.set(source, value);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
module.exports = GitFsResolver;
|
||||
145
lib/core/resolvers/GitHubResolver.js
Normal file
145
lib/core/resolvers/GitHubResolver.js
Normal file
@@ -0,0 +1,145 @@
|
||||
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');
|
||||
var createError = require('../../util/createError');
|
||||
|
||||
function GitHubResolver(decEndpoint, config, logger) {
|
||||
var pair;
|
||||
|
||||
GitRemoteResolver.call(this, decEndpoint, config, logger);
|
||||
|
||||
// Grab the org/repo
|
||||
// /xxxxx/yyyyy.git or :xxxxx/yyyyy.git (.git is optional)
|
||||
pair = GitHubResolver.getOrgRepoPair(this._source);
|
||||
if (!pair) {
|
||||
throw createError('Invalid GitHub URL', 'EINVEND', {
|
||||
details: this._source + ' does not seem to be a valid GitHub URL'
|
||||
});
|
||||
}
|
||||
|
||||
this._org = pair.org;
|
||||
this._repo = pair.repo;
|
||||
|
||||
// Ensure trailing for all protocols
|
||||
if (!mout.string.endsWith(this._source, '.git')) {
|
||||
this._source += '.git';
|
||||
}
|
||||
|
||||
// Use https:// rather than git:// if on a proxy
|
||||
if (this._config.proxy || this._config.httpsProxy) {
|
||||
this._source = this._source.replace('git://', 'https://');
|
||||
}
|
||||
|
||||
// Enable shallow clones for GitHub repos
|
||||
this._shallowClone = function () {
|
||||
return Q.resolve(true);
|
||||
};
|
||||
}
|
||||
|
||||
util.inherits(GitHubResolver, GitRemoteResolver);
|
||||
mout.object.mixIn(GitHubResolver, GitRemoteResolver);
|
||||
|
||||
// -----------------
|
||||
|
||||
GitHubResolver.prototype._checkout = function () {
|
||||
var msg;
|
||||
var name = this._resolution.tag || this._resolution.branch || this._resolution.commit;
|
||||
var tarballUrl = 'https://github.com/' + this._org + '/' + this._repo + '/archive/' + name + '.tar.gz';
|
||||
|
||||
var file = path.join(this._tempDir, 'archive.tar.gz');
|
||||
var reqHeaders = {};
|
||||
var that = this;
|
||||
|
||||
if (this._config.userAgent) {
|
||||
reqHeaders['User-Agent'] = this._config.userAgent;
|
||||
}
|
||||
|
||||
this._logger.action('download', tarballUrl, {
|
||||
url: that._source,
|
||||
to: file
|
||||
});
|
||||
|
||||
// Download tarball
|
||||
return download(tarballUrl, file, {
|
||||
ca: this._config.ca.default,
|
||||
strictSSL: this._config.strictSsl,
|
||||
timeout: this._config.timeout,
|
||||
headers: reqHeaders
|
||||
})
|
||||
.progress(function (state) {
|
||||
// Retry?
|
||||
if (state.retry) {
|
||||
msg = 'Download of ' + tarballUrl + ' failed with ' + state.error.code + ', ';
|
||||
msg += 'retrying in ' + (state.delay / 1000).toFixed(1) + 's';
|
||||
that._logger.debug('error', state.error.message, { error: state.error });
|
||||
return that._logger.warn('retry', msg);
|
||||
}
|
||||
|
||||
// Progress
|
||||
msg = 'received ' + (state.received / 1024 / 1024).toFixed(1) + 'MB';
|
||||
if (state.total) {
|
||||
msg += ' of ' + (state.total / 1024 / 1024).toFixed(1) + 'MB downloaded, ';
|
||||
msg += state.percent + '%';
|
||||
}
|
||||
that._logger.info('progress', msg);
|
||||
})
|
||||
.then(function () {
|
||||
// Extract archive
|
||||
that._logger.action('extract', path.basename(file), {
|
||||
archive: file,
|
||||
to: that._tempDir
|
||||
});
|
||||
|
||||
return extract(file, that._tempDir)
|
||||
// Fallback to standard git clone if extraction failed
|
||||
.fail(function (err) {
|
||||
msg = 'Decompression of ' + path.basename(file) + ' failed' + (err.code ? ' with ' + err.code : '') + ', ';
|
||||
msg += 'trying with git..';
|
||||
that._logger.debug('error', err.message, { error: err });
|
||||
that._logger.warn('retry', msg);
|
||||
|
||||
return that._cleanTempDir()
|
||||
.then(GitRemoteResolver.prototype._checkout.bind(that));
|
||||
});
|
||||
// Fallback to standard git clone if download failed
|
||||
}, function (err) {
|
||||
msg = 'Download of ' + tarballUrl + ' failed' + (err.code ? ' with ' + err.code : '') + ', ';
|
||||
msg += 'trying with git..';
|
||||
that._logger.debug('error', err.message, { error: err });
|
||||
that._logger.warn('retry', msg);
|
||||
|
||||
return that._cleanTempDir()
|
||||
.then(GitRemoteResolver.prototype._checkout.bind(that));
|
||||
});
|
||||
};
|
||||
|
||||
GitHubResolver.prototype._savePkgMeta = function (meta) {
|
||||
// Set homepage if not defined
|
||||
if (!meta.homepage) {
|
||||
meta.homepage = 'https://github.com/' + this._org + '/' + this._repo;
|
||||
}
|
||||
|
||||
return GitRemoteResolver.prototype._savePkgMeta.call(this, meta);
|
||||
};
|
||||
|
||||
// ----------------
|
||||
|
||||
GitHubResolver.getOrgRepoPair = function (url) {
|
||||
var match;
|
||||
|
||||
match = url.match(/(?:@|:\/\/)github.com[:\/]([^\/\s]+?)\/([^\/\s]+?)(?:\.git)?\/?$/i);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
org: match[1],
|
||||
repo: match[2]
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = GitHubResolver;
|
||||
281
lib/core/resolvers/GitRemoteResolver.js
Normal file
281
lib/core/resolvers/GitRemoteResolver.js
Normal file
@@ -0,0 +1,281 @@
|
||||
var util = require('util');
|
||||
var url = require('url');
|
||||
var Q = require('q');
|
||||
var mout = require('mout');
|
||||
var LRU = require('lru-cache');
|
||||
var GitResolver = require('./GitResolver');
|
||||
var cmd = require('../../util/cmd');
|
||||
|
||||
function GitRemoteResolver(decEndpoint, config, logger) {
|
||||
GitResolver.call(this, decEndpoint, config, logger);
|
||||
|
||||
if (!mout.string.startsWith(this._source, 'file://')) {
|
||||
// Trim trailing slashes
|
||||
this._source = this._source.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
// If the name was guessed, remove the trailing .git
|
||||
if (this._guessedName && mout.string.endsWith(this._name, '.git')) {
|
||||
this._name = this._name.slice(0, -4);
|
||||
}
|
||||
|
||||
// Get the remote of this source
|
||||
if (!/:\/\//.test(this._source)) {
|
||||
this._remote = url.parse('ssh://' + this._source);
|
||||
} else {
|
||||
this._remote = url.parse(this._source);
|
||||
}
|
||||
|
||||
this._host = this._remote.host;
|
||||
|
||||
// Verify whether the server supports shallow cloning
|
||||
this._shallowClone = this._supportsShallowCloning;
|
||||
}
|
||||
|
||||
util.inherits(GitRemoteResolver, GitResolver);
|
||||
mout.object.mixIn(GitRemoteResolver, GitResolver);
|
||||
|
||||
// -----------------
|
||||
|
||||
GitRemoteResolver.prototype._checkout = function () {
|
||||
var promise;
|
||||
var timer;
|
||||
var reporter;
|
||||
var that = this;
|
||||
var resolution = this._resolution;
|
||||
|
||||
this._logger.action('checkout', resolution.tag || resolution.branch || resolution.commit, {
|
||||
resolution: resolution,
|
||||
to: this._tempDir
|
||||
});
|
||||
|
||||
// If resolution is a commit, we need to clone the entire repo and check it out
|
||||
// Because a commit is not a named ref, there's no better solution
|
||||
if (resolution.type === 'commit') {
|
||||
promise = this._slowClone(resolution);
|
||||
// Otherwise we are checking out a named ref so we can optimize it
|
||||
} else {
|
||||
promise = this._fastClone(resolution);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
that._suggestProxyWorkaround(err);
|
||||
throw err;
|
||||
})
|
||||
// Clear timer at the end
|
||||
.fin(function () {
|
||||
clearTimeout(timer);
|
||||
reporter.cancel();
|
||||
});
|
||||
};
|
||||
|
||||
GitRemoteResolver.prototype._findResolution = function (target) {
|
||||
var that = this;
|
||||
|
||||
// Override this function to include a meaningful message related to proxies
|
||||
// if necessary
|
||||
return GitResolver.prototype._findResolution.call(this, target)
|
||||
.fail(function (err) {
|
||||
that._suggestProxyWorkaround(err);
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
|
||||
// ------------------------------
|
||||
|
||||
GitRemoteResolver.prototype._slowClone = function (resolution) {
|
||||
return cmd('git', ['clone', this._source, this._tempDir, '--progress'])
|
||||
.then(cmd.bind(cmd, 'git', ['checkout', resolution.commit], { cwd: this._tempDir }));
|
||||
};
|
||||
|
||||
GitRemoteResolver.prototype._fastClone = function (resolution) {
|
||||
var branch,
|
||||
args,
|
||||
that = this;
|
||||
|
||||
branch = resolution.tag || resolution.branch;
|
||||
args = ['clone', this._source, '-b', branch, '--progress', '.'];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
GitRemoteResolver.prototype._suggestProxyWorkaround = function (err) {
|
||||
if ((this._config.proxy || this._config.httpsProxy) &&
|
||||
mout.string.startsWith(this._source, 'git://') &&
|
||||
err.code === 'ECMDERR' && err.details
|
||||
) {
|
||||
err.details = err.details.trim();
|
||||
err.details += '\n\nWhen under a proxy, you must configure git to use https:// instead of git://.';
|
||||
err.details += '\nYou can configure it for every endpoint or for this specific host as follows:';
|
||||
err.details += '\ngit config --global url."https://".insteadOf git://';
|
||||
err.details += '\ngit config --global url."https://' + this._host + '".insteadOf git://' + this._host;
|
||||
err.details += 'Ignore this suggestion if you already have this configured.';
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
GitRemoteResolver.refs = function (source) {
|
||||
var value;
|
||||
|
||||
// TODO: Normalize source because of the various available protocols?
|
||||
value = this._cache.refs.get(source);
|
||||
if (value) {
|
||||
return Q.resolve(value);
|
||||
}
|
||||
|
||||
// Store the promise in the refs object
|
||||
value = cmd('git', ['ls-remote', '--tags', '--heads', source])
|
||||
.spread(function (stdout) {
|
||||
var refs;
|
||||
|
||||
refs = stdout.toString()
|
||||
.trim() // Trim trailing and leading spaces
|
||||
.replace(/[\t ]+/g, ' ') // Standardize spaces (some git versions make tabs, other spaces)
|
||||
.split(/[\r\n]+/); // Split lines into an array
|
||||
|
||||
// Update the refs with the actual refs
|
||||
this._cache.refs.set(source, refs);
|
||||
|
||||
return refs;
|
||||
}.bind(this));
|
||||
|
||||
// Store the promise to be reused until it resolves
|
||||
// to a specific value
|
||||
this._cache.refs.set(source, value);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
// 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;
|
||||
380
lib/core/resolvers/GitResolver.js
Normal file
380
lib/core/resolvers/GitResolver.js
Normal file
@@ -0,0 +1,380 @@
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
var Q = require('q');
|
||||
var rimraf = require('../../util/rimraf');
|
||||
var mkdirp = require('mkdirp');
|
||||
var which = require('which');
|
||||
var LRU = require('lru-cache');
|
||||
var mout = require('mout');
|
||||
var Resolver = require('./Resolver');
|
||||
var semver = require('../../util/semver');
|
||||
var createError = require('../../util/createError');
|
||||
|
||||
var hasGit;
|
||||
|
||||
// Check if git is installed
|
||||
try {
|
||||
which.sync('git');
|
||||
hasGit = true;
|
||||
} catch (ex) {
|
||||
hasGit = false;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!config.strictSsl) {
|
||||
process.env.GIT_SSL_NO_VERIFY = 'true';
|
||||
}
|
||||
|
||||
if (!config.interactive) {
|
||||
process.env.GIT_TERMINAL_PROMPT = '0';
|
||||
|
||||
if (!process.env.SSH_ASKPASS) {
|
||||
process.env.SSH_ASKPASS = 'echo';
|
||||
}
|
||||
}
|
||||
|
||||
Resolver.call(this, decEndpoint, config, logger);
|
||||
|
||||
if (!hasGit) {
|
||||
throw createError('git is not installed or not in the PATH', 'ENOGIT');
|
||||
}
|
||||
}
|
||||
|
||||
util.inherits(GitResolver, Resolver);
|
||||
mout.object.mixIn(GitResolver, Resolver);
|
||||
|
||||
// -----------------
|
||||
|
||||
GitResolver.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;
|
||||
});
|
||||
};
|
||||
|
||||
GitResolver.prototype._resolve = function () {
|
||||
var that = this;
|
||||
|
||||
return this._findResolution()
|
||||
.then(function () {
|
||||
return that._checkout()
|
||||
// Always run cleanup after checkout to ensure that .git is removed!
|
||||
// If it's not removed, problems might arise when the "tmp" module attempts
|
||||
// to delete the temporary folder
|
||||
.fin(function () {
|
||||
return that._cleanup();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// -----------------
|
||||
|
||||
// Abstract functions that should be implemented by concrete git resolvers
|
||||
GitResolver.prototype._checkout = function () {
|
||||
throw new Error('_checkout not implemented');
|
||||
};
|
||||
|
||||
GitResolver.refs = function (source) {
|
||||
throw new Error('refs not implemented');
|
||||
};
|
||||
|
||||
// -----------------
|
||||
|
||||
GitResolver.prototype._findResolution = function (target) {
|
||||
var err;
|
||||
var self = this.constructor;
|
||||
var that = this;
|
||||
|
||||
target = target || this._target || '*';
|
||||
|
||||
// Target is a commit, so it's a stale target (not a moving target)
|
||||
// There's nothing to do in this case
|
||||
if ((/^[a-f0-9]{40}$/).test(target)) {
|
||||
this._resolution = { type: 'commit', commit: target };
|
||||
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;
|
||||
|
||||
// If there are no tags and target is *,
|
||||
// fallback to the latest commit on master
|
||||
if (!versions.length && target === '*') {
|
||||
return that._findResolution('master');
|
||||
}
|
||||
|
||||
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] };
|
||||
}
|
||||
|
||||
if ((/^[a-f0-9]{4,40}$/).test(target)) {
|
||||
if (target.length < 12) {
|
||||
that._logger.warn(
|
||||
'short-sha',
|
||||
'Consider using longer commit SHA to avoid conflicts'
|
||||
);
|
||||
}
|
||||
|
||||
that._resolution = { type: 'commit', commit: target };
|
||||
return that._resolution;
|
||||
}
|
||||
|
||||
branches = Object.keys(branches);
|
||||
tags = Object.keys(tags);
|
||||
|
||||
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');
|
||||
|
||||
return Q.nfcall(rimraf, gitFolder);
|
||||
};
|
||||
|
||||
GitResolver.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.valid(meta.version) && 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.substr(0, 10);
|
||||
|
||||
// Save resolution to be used in hasNew later
|
||||
meta._resolution = this._resolution;
|
||||
|
||||
return Resolver.prototype._savePkgMeta.call(this, meta);
|
||||
};
|
||||
|
||||
// ------------------------------
|
||||
|
||||
GitResolver.versions = function (source, extra) {
|
||||
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;
|
||||
};
|
||||
|
||||
GitResolver.tags = function (source) {
|
||||
var value = this._cache.tags.get(source);
|
||||
|
||||
if (value) {
|
||||
return Q.resolve(value);
|
||||
}
|
||||
|
||||
value = this.refs(source)
|
||||
.then(function (refs) {
|
||||
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+)/);
|
||||
|
||||
if (match && !mout.string.endsWith(match[2], '^{}')) {
|
||||
tags[match[2]] = match[1];
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
GitResolver.branches = function (source) {
|
||||
var value = this._cache.branches.get(source);
|
||||
|
||||
if (value) {
|
||||
return Q.resolve(value);
|
||||
}
|
||||
|
||||
value = this.refs(source)
|
||||
.then(function (refs) {
|
||||
var branches = {};
|
||||
|
||||
// For each line in the refs, extract only the heads
|
||||
// Organize them in an object where keys are branches and values
|
||||
// the commit hashes
|
||||
refs.forEach(function (line) {
|
||||
var match = line.match(/^([a-f0-9]{40})\s+refs\/heads\/(\S+)/);
|
||||
|
||||
if (match) {
|
||||
branches[match[2]] = match[1];
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
GitResolver.clearRuntimeCache = function () {
|
||||
// Reset cache for branches, tags, etc
|
||||
mout.object.forOwn(GitResolver._cache, function (lru) {
|
||||
lru.reset();
|
||||
});
|
||||
};
|
||||
|
||||
GitResolver._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 }),
|
||||
refs: new LRU({ max: 50, maxAge: 5 * 60 * 1000 })
|
||||
};
|
||||
|
||||
module.exports = GitResolver;
|
||||
230
lib/core/resolvers/Resolver.js
Normal file
230
lib/core/resolvers/Resolver.js
Normal file
@@ -0,0 +1,230 @@
|
||||
var fs = require('../../util/fs');
|
||||
var path = require('path');
|
||||
var Q = require('q');
|
||||
var tmp = require('tmp');
|
||||
var mkdirp = require('mkdirp');
|
||||
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();
|
||||
|
||||
function Resolver(decEndpoint, config, logger) {
|
||||
this._source = decEndpoint.source;
|
||||
this._target = decEndpoint.target || '*';
|
||||
this._name = decEndpoint.name || path.basename(this._source);
|
||||
|
||||
this._config = config;
|
||||
this._logger = logger;
|
||||
|
||||
this._guessedName = !decEndpoint.name;
|
||||
}
|
||||
|
||||
// -----------------
|
||||
|
||||
Resolver.prototype.getSource = function () {
|
||||
return this._source;
|
||||
};
|
||||
|
||||
Resolver.prototype.getName = function () {
|
||||
return this._name;
|
||||
};
|
||||
|
||||
Resolver.prototype.getTarget = function () {
|
||||
return this._target;
|
||||
};
|
||||
|
||||
Resolver.prototype.getTempDir = function () {
|
||||
return this._tempDir;
|
||||
};
|
||||
|
||||
Resolver.prototype.getPkgMeta = function () {
|
||||
return this._pkgMeta;
|
||||
};
|
||||
|
||||
Resolver.prototype.hasNew = function (pkgMeta) {
|
||||
var promise;
|
||||
var that = this;
|
||||
|
||||
// If already working, error out
|
||||
if (this._working) {
|
||||
return Q.reject(createError('Already working', 'EWORKING'));
|
||||
}
|
||||
|
||||
this._working = true;
|
||||
|
||||
// Avoid reading the package meta if already given
|
||||
promise = this._hasNew(pkgMeta);
|
||||
|
||||
return promise.fin(function () {
|
||||
that._working = false;
|
||||
});
|
||||
};
|
||||
|
||||
Resolver.prototype.resolve = function () {
|
||||
var that = this;
|
||||
|
||||
// If already working, error out
|
||||
if (this._working) {
|
||||
return Q.reject(createError('Already working', 'EWORKING'));
|
||||
}
|
||||
|
||||
this._working = true;
|
||||
|
||||
// Create temporary dir
|
||||
return this._createTempDir()
|
||||
// Resolve self
|
||||
.then(this._resolve.bind(this))
|
||||
// Read json, generating the package meta
|
||||
.then(this._readJson.bind(this, null))
|
||||
// Apply and save package meta
|
||||
.then(function (meta) {
|
||||
return that._applyPkgMeta(meta)
|
||||
.then(that._savePkgMeta.bind(that, meta));
|
||||
})
|
||||
.then(function () {
|
||||
// Resolve with the folder
|
||||
return that._tempDir;
|
||||
}, function (err) {
|
||||
// If something went wrong, unset the temporary dir
|
||||
that._tempDir = null;
|
||||
throw err;
|
||||
})
|
||||
.fin(function () {
|
||||
that._working = false;
|
||||
});
|
||||
};
|
||||
|
||||
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
|
||||
Resolver.prototype._resolve = function () {
|
||||
throw new Error('_resolve not implemented');
|
||||
};
|
||||
|
||||
// Abstract functions that can be re-implemented by concrete resolvers
|
||||
// as necessary
|
||||
Resolver.prototype._hasNew = function (pkgMeta) {
|
||||
return Q.resolve(true);
|
||||
};
|
||||
|
||||
Resolver.isTargetable = function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
Resolver.versions = function (source) {
|
||||
return Q.resolve([]);
|
||||
};
|
||||
|
||||
Resolver.clearRuntimeCache = function () {};
|
||||
|
||||
// -----------------
|
||||
|
||||
Resolver.prototype._createTempDir = function () {
|
||||
return Q.nfcall(mkdirp, this._config.tmp)
|
||||
.then(function () {
|
||||
return Q.nfcall(tmp.dir, {
|
||||
template: path.join(this._config.tmp, md5(this._name) + '-' + process.pid + '-XXXXXX'),
|
||||
mode: 0777 & ~process.umask(),
|
||||
unsafeCleanup: true
|
||||
});
|
||||
}.bind(this))
|
||||
.then(function (dir) {
|
||||
// nfcall may return multiple callback arguments as an array
|
||||
return this._tempDir = Array.isArray(dir) ? dir[0] : dir;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Resolver.prototype._cleanTempDir = function () {
|
||||
var tempDir = this._tempDir;
|
||||
|
||||
if (!tempDir) {
|
||||
return Q.resolve();
|
||||
}
|
||||
|
||||
// Delete and create folder
|
||||
return Q.nfcall(rimraf, tempDir)
|
||||
.then(function () {
|
||||
return Q.nfcall(mkdirp, tempDir, 0777 & ~process.umask());
|
||||
})
|
||||
.then(function () {
|
||||
return tempDir;
|
||||
});
|
||||
};
|
||||
|
||||
Resolver.prototype._readJson = function (dir) {
|
||||
var that = this;
|
||||
|
||||
dir = dir || this._tempDir;
|
||||
return readJson(dir, {
|
||||
assume: { name: this._name },
|
||||
logger: that._logger
|
||||
})
|
||||
.spread(function (json, deprecated) {
|
||||
if (deprecated) {
|
||||
that._logger.warn('deprecated', 'Package ' + that._name + ' is using the deprecated ' + deprecated);
|
||||
}
|
||||
|
||||
return json;
|
||||
});
|
||||
};
|
||||
|
||||
Resolver.prototype._applyPkgMeta = function (meta) {
|
||||
// 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._guessedName) {
|
||||
this._name = meta.name;
|
||||
}
|
||||
|
||||
// Handle ignore property, deleting all files from the temporary directory
|
||||
// If no ignores were specified, simply resolve
|
||||
if (!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;
|
||||
});
|
||||
};
|
||||
|
||||
Resolver.prototype._savePkgMeta = function (meta) {
|
||||
var that = this;
|
||||
var contents;
|
||||
|
||||
// Store original source & target
|
||||
meta._source = this._source;
|
||||
meta._target = this._target;
|
||||
|
||||
// Stringify contents
|
||||
contents = JSON.stringify(meta, null, 2);
|
||||
|
||||
return Q.nfcall(fs.writeFile, path.join(this._tempDir, '.bower.json'), contents)
|
||||
.then(function () {
|
||||
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;
|
||||
294
lib/core/resolvers/UrlResolver.js
Normal file
294
lib/core/resolvers/UrlResolver.js
Normal file
@@ -0,0 +1,294 @@
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
var fs = require('../../util/fs');
|
||||
var url = require('url');
|
||||
var request = require('request');
|
||||
var Q = require('q');
|
||||
var mout = require('mout');
|
||||
var junk = require('junk');
|
||||
var Resolver = require('./Resolver');
|
||||
var download = require('../../util/download');
|
||||
var extract = require('../../util/extract');
|
||||
var createError = require('../../util/createError');
|
||||
|
||||
function UrlResolver(decEndpoint, config, logger) {
|
||||
Resolver.call(this, decEndpoint, config, logger);
|
||||
|
||||
// If target was specified, error out
|
||||
if (this._target !== '*') {
|
||||
throw createError('URL sources can\'t resolve targets', 'ENORESTARGET');
|
||||
}
|
||||
|
||||
// If the name was guessed
|
||||
if (this._guessedName) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
util.inherits(UrlResolver, Resolver);
|
||||
mout.object.mixIn(UrlResolver, Resolver);
|
||||
|
||||
// -----------------
|
||||
|
||||
UrlResolver.isTargetable = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
UrlResolver.prototype._hasNew = function (pkgMeta) {
|
||||
var oldCacheHeaders = pkgMeta._cacheHeaders || {};
|
||||
var reqHeaders = {};
|
||||
|
||||
// If the previous cache headers contain an ETag,
|
||||
// send the "If-None-Match" header with it
|
||||
if (oldCacheHeaders.ETag) {
|
||||
reqHeaders['If-None-Match'] = oldCacheHeaders.ETag;
|
||||
}
|
||||
|
||||
if (this._config.userAgent) {
|
||||
reqHeaders['User-Agent'] = this._config.userAgent;
|
||||
}
|
||||
|
||||
// Make an HEAD request to the source
|
||||
return Q.nfcall(request.head, this._source, {
|
||||
ca: this._config.ca.default,
|
||||
strictSSL: this._config.strictSsl,
|
||||
timeout: this._config.timeout,
|
||||
headers: reqHeaders
|
||||
})
|
||||
// Compare new headers with the old ones
|
||||
.spread(function (response) {
|
||||
var cacheHeaders;
|
||||
|
||||
// If the server responded with 303 then the resource
|
||||
// still has the same ETag
|
||||
if (response.statusCode === 304) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If status code is not in the 2xx range,
|
||||
// then just resolve to true
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback to comparing cache headers
|
||||
cacheHeaders = this._collectCacheHeaders(response);
|
||||
return !mout.object.equals(oldCacheHeaders, cacheHeaders);
|
||||
}.bind(this), function () {
|
||||
// Assume new contents if the request failed
|
||||
// Note that we do not retry the request using the "request-replay" module
|
||||
// because it would take too long
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: There's room for improvement by using streams if the URL
|
||||
// is an archive file, by piping read stream to the zip extractor
|
||||
// This will likely increase the complexity of code but might worth it
|
||||
|
||||
UrlResolver.prototype._resolve = function () {
|
||||
// Download
|
||||
return this._download()
|
||||
// Parse headers
|
||||
.spread(this._parseHeaders.bind(this))
|
||||
// Extract file
|
||||
.spread(this._extract.bind(this))
|
||||
// Rename file to index
|
||||
.then(this._rename.bind(this));
|
||||
};
|
||||
|
||||
// -----------------
|
||||
|
||||
UrlResolver.prototype._parseSourceURL = function (_url) {
|
||||
return url.parse(path.basename(_url)).pathname;
|
||||
};
|
||||
|
||||
UrlResolver.prototype._download = function () {
|
||||
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;
|
||||
|
||||
if (this._config.userAgent) {
|
||||
reqHeaders['User-Agent'] = this._config.userAgent;
|
||||
}
|
||||
|
||||
this._logger.action('download', that._source, {
|
||||
url: that._source,
|
||||
to: file
|
||||
});
|
||||
|
||||
// Download the file
|
||||
return download(this._source, file, {
|
||||
ca: this._config.ca.default,
|
||||
strictSSL: this._config.strictSsl,
|
||||
timeout: this._config.timeout,
|
||||
headers: reqHeaders
|
||||
})
|
||||
.progress(function (state) {
|
||||
var msg;
|
||||
|
||||
// Retry?
|
||||
if (state.retry) {
|
||||
msg = 'Download of ' + that._source + ' failed' + (state.error.code ? ' with ' + state.error.code : '') + ', ';
|
||||
msg += 'retrying in ' + (state.delay / 1000).toFixed(1) + 's';
|
||||
that._logger.debug('error', state.error.message, { error: state.error });
|
||||
return that._logger.warn('retry', msg);
|
||||
}
|
||||
|
||||
// Progress
|
||||
msg = 'received ' + (state.received / 1024 / 1024).toFixed(1) + 'MB';
|
||||
if (state.total) {
|
||||
msg += ' of ' + (state.total / 1024 / 1024).toFixed(1) + 'MB downloaded, ';
|
||||
msg += state.percent + '%';
|
||||
}
|
||||
that._logger.info('progress', msg);
|
||||
})
|
||||
.then(function (response) {
|
||||
that._response = response;
|
||||
return [file, response];
|
||||
});
|
||||
};
|
||||
|
||||
UrlResolver.prototype._parseHeaders = function (file, response) {
|
||||
var disposition;
|
||||
var newFile;
|
||||
var match;
|
||||
|
||||
// Check if we got a Content-Disposition header
|
||||
disposition = response.headers['content-disposition'];
|
||||
if (!disposition) {
|
||||
return Q.resolve([file, response]);
|
||||
}
|
||||
|
||||
// Since there's various security issues with parsing this header, we only
|
||||
// interpret word chars plus dots, dashes and spaces
|
||||
match = disposition.match(/filename=(?:"([\w\-\. ]+)")/i);
|
||||
if (!match) {
|
||||
// The spec defines that the filename must be in quotes,
|
||||
// though a wide range of servers do not follow the rule
|
||||
match = disposition.match(/filename=([\w\-\.]+)/i);
|
||||
if (!match) {
|
||||
return Q.resolve([file, response]);
|
||||
}
|
||||
}
|
||||
|
||||
// Trim spaces
|
||||
newFile = match[1].trim();
|
||||
|
||||
// The filename can't end with a dot because this is known
|
||||
// to cause issues in Windows
|
||||
// See: http://superuser.com/questions/230385/dots-at-end-of-file-name
|
||||
if (mout.string.endsWith(newFile, '.')) {
|
||||
return Q.resolve([file, response]);
|
||||
}
|
||||
|
||||
newFile = path.join(this._tempDir, newFile);
|
||||
|
||||
return Q.nfcall(fs.rename, file, newFile)
|
||||
.then(function () {
|
||||
return [newFile, response];
|
||||
});
|
||||
};
|
||||
|
||||
UrlResolver.prototype._extract = function (file, response) {
|
||||
var mimeType = response.headers['content-type'];
|
||||
|
||||
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)) {
|
||||
return Q.resolve();
|
||||
}
|
||||
|
||||
this._logger.action('extract', path.basename(this._source), {
|
||||
archive: file,
|
||||
to: this._tempDir
|
||||
});
|
||||
|
||||
return extract(file, this._tempDir, {
|
||||
mimeType: mimeType
|
||||
});
|
||||
};
|
||||
|
||||
UrlResolver.prototype._rename = function () {
|
||||
return Q.nfcall(fs.readdir, this._tempDir)
|
||||
.then(function (files) {
|
||||
var file;
|
||||
var oldPath;
|
||||
var newPath;
|
||||
|
||||
// Remove any OS specific files from the files array
|
||||
// before checking its length
|
||||
files = files.filter(junk.isnt);
|
||||
|
||||
// Only rename if there's only one file and it's not the json
|
||||
if (files.length === 1 && !/^(component|bower)\.json$/.test(files[0])) {
|
||||
file = files[0];
|
||||
this._singleFile = 'index' + path.extname(file);
|
||||
oldPath = path.join(this._tempDir, file);
|
||||
newPath = path.join(this._tempDir, this._singleFile);
|
||||
|
||||
return Q.nfcall(fs.rename, oldPath, newPath);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
UrlResolver.prototype._savePkgMeta = function (meta) {
|
||||
// Store collected headers in the package meta
|
||||
meta._cacheHeaders = this._collectCacheHeaders(this._response);
|
||||
|
||||
// Store ETAG under _release
|
||||
if (meta._cacheHeaders.ETag) {
|
||||
meta._release = 'e-tag:' + mout.string.trim(meta._cacheHeaders.ETag.substr(0, 10), '"');
|
||||
}
|
||||
|
||||
// Store main if is a single file
|
||||
if (this._singleFile) {
|
||||
meta.main = this._singleFile;
|
||||
}
|
||||
|
||||
return Resolver.prototype._savePkgMeta.call(this, meta);
|
||||
};
|
||||
|
||||
UrlResolver.prototype._collectCacheHeaders = function (res) {
|
||||
var headers = {};
|
||||
|
||||
// Collect cache headers
|
||||
this.constructor._cacheHeaders.forEach(function (name) {
|
||||
var value = res.headers[name.toLowerCase()];
|
||||
|
||||
if (value != null) {
|
||||
headers[name] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return headers;
|
||||
};
|
||||
|
||||
UrlResolver._cacheHeaders = [
|
||||
'Content-MD5',
|
||||
'ETag',
|
||||
'Last-Modified',
|
||||
'Content-Language',
|
||||
'Content-Length',
|
||||
'Content-Type',
|
||||
'Content-Disposition'
|
||||
];
|
||||
|
||||
module.exports = UrlResolver;
|
||||
8
lib/core/resolvers/index.js
Normal file
8
lib/core/resolvers/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
GitFs: require('./GitFsResolver'),
|
||||
GitRemote: require('./GitRemoteResolver'),
|
||||
GitHub: require('./GitHubResolver'),
|
||||
Svn: require('./SvnResolver'),
|
||||
Fs: require('./FsResolver'),
|
||||
Url: require('./UrlResolver')
|
||||
};
|
||||
323
lib/core/resolvers/pluginResolverFactory.js
Normal file
323
lib/core/resolvers/pluginResolverFactory.js
Normal file
@@ -0,0 +1,323 @@
|
||||
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 = that._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() },
|
||||
logger: bower.logger
|
||||
})
|
||||
.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;
|
||||
|
||||
97
lib/core/scripts.js
Normal file
97
lib/core/scripts.js
Normal file
@@ -0,0 +1,97 @@
|
||||
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]) {
|
||||
return Q();
|
||||
}
|
||||
|
||||
var orderedPackages = ordered ? orderByDependencies(packages, installed, json) : mout.object.keys(packages);
|
||||
var placeholder = new RegExp('%', 'g');
|
||||
var cmdString = mout.string.replace(config.scripts[action], placeholder, orderedPackages.join(' '));
|
||||
return run(cmdString, action, logger, config);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
preuninstall: mout.function.partial(hook, 'preuninstall', false),
|
||||
postuninstall: mout.function.partial(hook, 'postuninstall', false),
|
||||
preinstall: mout.function.partial(hook, 'preinstall', true),
|
||||
postinstall: mout.function.partial(hook, 'postinstall', true),
|
||||
//only exposed for test
|
||||
_orderByDependencies: orderByDependencies
|
||||
};
|
||||
@@ -1,146 +0,0 @@
|
||||
// ==========================================
|
||||
// BOWER: Source Api
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
var request = require('request');
|
||||
var config = require('./config');
|
||||
|
||||
var endpoint = config.endpoint + '/packages';
|
||||
|
||||
if (config.proxy) {
|
||||
request = request.defaults({ proxy: config.proxy, timeout: 5000 });
|
||||
}
|
||||
|
||||
|
||||
// allow for searchpath endpoints to be used for search and lookup
|
||||
var endpoints = [];
|
||||
endpoints.push(endpoint);
|
||||
if (config.searchpath) {
|
||||
for (var i = 0; i < config.searchpath.length; i += 1) {
|
||||
endpoints.push(config.searchpath[i] + '/packages');
|
||||
}
|
||||
}
|
||||
|
||||
exports.lookup = function (name, callback) {
|
||||
// walk all endpoints to find the first matching component
|
||||
var f = function (i) {
|
||||
var endpoint = endpoints[i];
|
||||
request.get(endpoint + '/' + encodeURIComponent(name), function (err, response, body) {
|
||||
if (err || (response.statusCode !== 200 && response.statusCode !== 404)) {
|
||||
return callback(err || new Error(name + ' failed to look up for endpoint: ' + endpoint));
|
||||
}
|
||||
|
||||
if (response && response.statusCode !== 404) {
|
||||
callback(err, body && JSON.parse(body).url);
|
||||
} else {
|
||||
if (i + 1 < endpoints.length) f(i + 1);
|
||||
else return callback(new Error(name + ' not found'));
|
||||
}
|
||||
});
|
||||
};
|
||||
f(0);
|
||||
};
|
||||
|
||||
exports.register = function (name, url, callback) {
|
||||
var body = {name: name, url: url};
|
||||
|
||||
request.post({url: endpoint, form: body}, function (err, response) {
|
||||
if (err) return callback(err);
|
||||
|
||||
if (response.statusCode === 406) {
|
||||
return callback(new Error('Duplicate package'));
|
||||
}
|
||||
|
||||
if (response.statusCode === 400) {
|
||||
return callback(new Error('Incorrect format'));
|
||||
}
|
||||
|
||||
if (response.statusCode !== 201) {
|
||||
return callback(new Error('Unknown error: ' + response.statusCode));
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
exports.search = function (name, callback) {
|
||||
// walk all endpoints to produced federated search results
|
||||
var f = function (i, map, results) {
|
||||
var endpoint = endpoints[i];
|
||||
|
||||
request.get(endpoint + '/search/' + encodeURIComponent(name), function (err, response, body) {
|
||||
if (err || (response.statusCode !== 200 && response.statusCode !== 404)) {
|
||||
return callback(err || new Error(name + ' failed to look up for endpoint: ' + endpoint));
|
||||
}
|
||||
|
||||
if (response && response.statusCode !== 404) {
|
||||
var array = body && JSON.parse(body);
|
||||
for (var x = 0; x < array.length; x += 1) {
|
||||
var pkgName = array[x].name;
|
||||
if (!map[pkgName]) {
|
||||
map[pkgName] = pkgName;
|
||||
results.push({ name: pkgName, url: array[x].url, endpoint: array[x].endpoint });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i + 1 < endpoints.length) f(i + 1, map, results);
|
||||
else return callback(null, results);
|
||||
});
|
||||
};
|
||||
|
||||
f(0, {}, []);
|
||||
};
|
||||
|
||||
exports.info = function (name, callback) {
|
||||
exports.lookup(name, function (err, url) {
|
||||
if (err) return callback(err);
|
||||
|
||||
var Package = require('./package');
|
||||
var pkg = new Package(name, url);
|
||||
|
||||
pkg.once('error', function (err) {
|
||||
pkg.removeAllListeners();
|
||||
callback(err);
|
||||
});
|
||||
pkg.once('resolve', function () {
|
||||
pkg.once('versions', function (versions) {
|
||||
pkg.removeAllListeners();
|
||||
callback(null, { pkg: pkg, versions: versions });
|
||||
}).versions();
|
||||
}).resolve();
|
||||
});
|
||||
};
|
||||
|
||||
exports.all = function (callback) {
|
||||
// walk all endpoints to produced federated search results
|
||||
var f = function (i, map, results) {
|
||||
var endpoint = endpoints[i];
|
||||
|
||||
request.get(endpoint, function (err, response, body) {
|
||||
if (err || (response.statusCode !== 200 && response.statusCode !== 404)) {
|
||||
return callback(err || new Error('Failed to look up endpoint: ' + endpoint));
|
||||
}
|
||||
|
||||
if (response && response.statusCode !== 404) {
|
||||
var array = body && JSON.parse(body);
|
||||
for (var x = 0; x < array.length; x += 1) {
|
||||
var pkgName = array[x].name;
|
||||
if (!map[pkgName]) {
|
||||
map[pkgName] = pkgName;
|
||||
results.push({ name: pkgName, url: array[x].url, endpoint: array[x].endpoint });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i + 1 < endpoints.length) f(i + 1, map, results);
|
||||
else return callback(null, results);
|
||||
});
|
||||
};
|
||||
|
||||
f(0, {}, []);
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
// ==========================================
|
||||
// BOWER: Package Object Definition
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
// Events:
|
||||
// - lock: fired when a lock write over a key is acquired
|
||||
// - unlock: fired when an unlock write over a key is acquired
|
||||
// ==========================================
|
||||
|
||||
var events = require('events');
|
||||
|
||||
var UnitWork = function () {
|
||||
this.locks = [];
|
||||
this.data = [];
|
||||
|
||||
this.setMaxListeners(100); // Increase the number of listeners because this is a central storage
|
||||
};
|
||||
|
||||
UnitWork.prototype = Object.create(events.EventEmitter.prototype);
|
||||
|
||||
UnitWork.prototype.constructor = UnitWork;
|
||||
|
||||
UnitWork.prototype.lock = function (key, owner) {
|
||||
if (this.locks[key]) throw new Error('A write lock for "' + key + '" was already acquired.');
|
||||
if (!owner) throw new Error('A lock requires an owner.');
|
||||
this.locks[key] = owner;
|
||||
|
||||
return this.emit('lock', key);
|
||||
};
|
||||
|
||||
UnitWork.prototype.unlock = function (key, owner) {
|
||||
if (!owner) throw new Error('A write lock requires an owner.');
|
||||
if (this.locks[key]) {
|
||||
if (this.locks[key] !== owner) throw new Error('Lock owner for "' + key + '" mismatch.');
|
||||
delete this.locks[key];
|
||||
this.emit('unlock', key);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UnitWork.prototype.isLocked = function (key) {
|
||||
return !!this.locks[key];
|
||||
};
|
||||
|
||||
UnitWork.prototype.store = function (key, data, owner) {
|
||||
if (this.locks[key] && owner !== this.locks[key]) throw new Error('A write lock for "' + key + '" is acquired therefore only its owner can write to it.');
|
||||
this.data[key] = data;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UnitWork.prototype.retrieve = function (key) {
|
||||
return this.data[key];
|
||||
};
|
||||
|
||||
UnitWork.prototype.keys = function () {
|
||||
return Object.keys(this.data);
|
||||
};
|
||||
|
||||
module.exports = UnitWork;
|
||||
27
lib/index.js
27
lib/index.js
@@ -1,12 +1,19 @@
|
||||
// ==========================================
|
||||
// BOWER: Public API Defintion
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var commands = require('./commands');
|
||||
var version = require('./version');
|
||||
var abbreviations = require('./util/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 = {
|
||||
commands: require('./commands'),
|
||||
config: require('./core/config')
|
||||
};
|
||||
version: version,
|
||||
commands: commands,
|
||||
config: require('./config')(),
|
||||
abbreviations: abbreviations,
|
||||
reset: clearRuntimeCache
|
||||
};
|
||||
|
||||
133
lib/renderers/JsonRenderer.js
Normal file
133
lib/renderers/JsonRenderer.js
Normal file
@@ -0,0 +1,133 @@
|
||||
var chalk = require('chalk');
|
||||
var Q = require('q');
|
||||
var promptly = require('promptly');
|
||||
var createError = require('../util/createError');
|
||||
|
||||
function JsonRenderer() {
|
||||
this._nrLogs = 0;
|
||||
}
|
||||
|
||||
JsonRenderer.prototype.end = function (data) {
|
||||
if (this._nrLogs) {
|
||||
process.stderr.write(']\n');
|
||||
}
|
||||
|
||||
if (data) {
|
||||
process.stdout.write(this._stringify(data) + '\n');
|
||||
}
|
||||
};
|
||||
|
||||
JsonRenderer.prototype.error = function (err) {
|
||||
var message = err.message;
|
||||
var stack;
|
||||
|
||||
err.id = err.code || 'error';
|
||||
err.level = 'error';
|
||||
err.data = err.data || {};
|
||||
|
||||
// Need to set message again because it is
|
||||
// not enumerable in some cases
|
||||
delete err.message;
|
||||
err.message = message;
|
||||
|
||||
// Stack
|
||||
stack = err.fstream_stack || err.stack || 'N/A';
|
||||
err.stacktrace = (Array.isArray(stack) ? stack.join('\n') : stack);
|
||||
|
||||
this.log(err);
|
||||
this.end();
|
||||
};
|
||||
|
||||
JsonRenderer.prototype.log = function (log) {
|
||||
if (!this._nrLogs) {
|
||||
process.stderr.write('[');
|
||||
} else {
|
||||
process.stderr.write(', ');
|
||||
}
|
||||
|
||||
process.stderr.write(this._stringify(log));
|
||||
this._nrLogs++;
|
||||
};
|
||||
|
||||
JsonRenderer.prototype.prompt = function (prompts) {
|
||||
var promise = Q.resolve();
|
||||
var answers = {};
|
||||
var that = this;
|
||||
|
||||
prompts.forEach(function (prompt) {
|
||||
var opts;
|
||||
var funcName;
|
||||
|
||||
// Strip colors
|
||||
prompt.message = chalk.stripColor(prompt.message);
|
||||
|
||||
// Prompt
|
||||
opts = {
|
||||
silent: true, // To not mess with JSON output
|
||||
trim: false, // To allow " " to not assume the default value
|
||||
default: prompt.default == null ? '' : prompt.default, // If default is null, make it '' so that it does not retry
|
||||
validator: !prompt.validate ? null : function (value) {
|
||||
var ret = prompt.validate(value);
|
||||
|
||||
if (typeof ret === 'string') {
|
||||
throw ret;
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
};
|
||||
|
||||
// For now only "input", "confirm" and "password" are supported
|
||||
switch (prompt.type) {
|
||||
case 'input':
|
||||
funcName = 'prompt';
|
||||
break;
|
||||
case 'confirm':
|
||||
case 'password':
|
||||
funcName = prompt.type;
|
||||
break;
|
||||
case 'checkbox':
|
||||
funcName = 'prompt';
|
||||
break;
|
||||
default:
|
||||
promise = promise.then(function () {
|
||||
throw createError('Unknown prompt type', 'ENOTSUP');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
promise = promise.then(function () {
|
||||
// Log
|
||||
prompt.level = 'prompt';
|
||||
that.log(prompt);
|
||||
|
||||
return Q.nfcall(promptly[funcName], '', opts)
|
||||
.then(function (answer) {
|
||||
answers[prompt.name] = answer;
|
||||
});
|
||||
});
|
||||
|
||||
if (prompt.type === 'checkbox') {
|
||||
promise = promise.then(function () {
|
||||
answers[prompt.name] = answers[prompt.name].split(',');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return promise.then(function () {
|
||||
return answers;
|
||||
});
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
|
||||
JsonRenderer.prototype._stringify = function (log) {
|
||||
// To json
|
||||
var str = JSON.stringify(log, null, ' ');
|
||||
// Remove colors in case some log has colors..
|
||||
str = chalk.stripColor(str);
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
module.exports = JsonRenderer;
|
||||
507
lib/renderers/StandardRenderer.js
Normal file
507
lib/renderers/StandardRenderer.js
Normal file
@@ -0,0 +1,507 @@
|
||||
var chalk = require('chalk');
|
||||
var path = require('path');
|
||||
var mout = require('mout');
|
||||
var archy = require('archy');
|
||||
var Q = require('q');
|
||||
var stringifyObject = require('stringify-object');
|
||||
var os = require('os');
|
||||
var semverUtils = require('semver-utils');
|
||||
var version = require('../version');
|
||||
var template = require('../util/template');
|
||||
|
||||
function StandardRenderer(command, config) {
|
||||
this._sizes = {
|
||||
id: 13, // Id max chars
|
||||
label: 20, // Label max chars
|
||||
sumup: 5 // Amount to sum when the label exceeds
|
||||
};
|
||||
this._colors = {
|
||||
warn: chalk.yellow,
|
||||
error: chalk.red,
|
||||
conflict: chalk.magenta,
|
||||
debug: chalk.gray,
|
||||
default: chalk.cyan
|
||||
};
|
||||
|
||||
this._command = command;
|
||||
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) {
|
||||
var method = '_' + mout.string.camelCase(this._command);
|
||||
|
||||
if (this[method]) {
|
||||
this[method](data);
|
||||
}
|
||||
};
|
||||
|
||||
StandardRenderer.prototype.error = function (err) {
|
||||
var str;
|
||||
var stack;
|
||||
|
||||
this._guessOrigin(err);
|
||||
|
||||
err.id = err.code || 'error';
|
||||
err.level = 'error';
|
||||
|
||||
str = this._prefix(err) + ' ' + err.message.replace(/\r?\n/g, ' ').trim() + '\n';
|
||||
this._write(process.stderr, 'bower ' + str);
|
||||
|
||||
// Check if additional details were provided
|
||||
if (err.details) {
|
||||
str = chalk.yellow('\nAdditional error details:\n') + err.details.trim() + '\n';
|
||||
this._write(process.stderr, str);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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');
|
||||
|
||||
this._write(process.stderr, str);
|
||||
this._write(process.stderr, new Error().stack);
|
||||
|
||||
// Print bower version, node version and system info.
|
||||
this._write(process.stderr, chalk.yellow('\nSystem info:\n'));
|
||||
this._write(process.stderr, 'Bower version: ' + version + '\n');
|
||||
this._write(process.stderr, 'Node version: ' + process.versions.node + '\n');
|
||||
this._write(process.stderr, 'OS: ' + os.type() + ' ' + os.release() + ' ' + os.arch() + '\n');
|
||||
}
|
||||
};
|
||||
|
||||
StandardRenderer.prototype.log = function (log) {
|
||||
var method = '_' + mout.string.camelCase(log.id) + 'Log';
|
||||
|
||||
this._guessOrigin(log);
|
||||
|
||||
// Call render method for this log entry or the generic one
|
||||
if (this[method]) {
|
||||
this[method](log);
|
||||
} else {
|
||||
this._genericLog(log);
|
||||
}
|
||||
};
|
||||
|
||||
StandardRenderer.prototype.prompt = function (prompts) {
|
||||
var deferred;
|
||||
|
||||
// Strip colors from the prompt if color is disabled
|
||||
if (!this._config.color) {
|
||||
prompts.forEach(function (prompt) {
|
||||
prompt.message = chalk.stripColor(prompt.message);
|
||||
});
|
||||
}
|
||||
|
||||
// Prompt
|
||||
deferred = Q.defer();
|
||||
var inquirer = require('inquirer');
|
||||
inquirer.prompt(prompts, deferred.resolve);
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
|
||||
StandardRenderer.prototype._help = function (data) {
|
||||
var str;
|
||||
var that = this;
|
||||
var specific;
|
||||
|
||||
if (!data.command) {
|
||||
str = template.render('std/help.std', data);
|
||||
that._write(process.stdout, str);
|
||||
} else {
|
||||
// Check if a specific template exists for the command
|
||||
specific = 'std/help-' + data.command.replace(/\s+/g, '/') + '.std';
|
||||
|
||||
if (template.exists(specific)) {
|
||||
str = template.render(specific, data);
|
||||
} else {
|
||||
str = template.render('std/help-generic.std', data);
|
||||
}
|
||||
|
||||
that._write(process.stdout, str);
|
||||
}
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._install = function (packages) {
|
||||
var str = '';
|
||||
|
||||
mout.object.forOwn(packages, function (pkg) {
|
||||
var cliTree;
|
||||
|
||||
// List only 1 level deep dependencies
|
||||
mout.object.forOwn(pkg.dependencies, function (dependency) {
|
||||
dependency.dependencies = {};
|
||||
});
|
||||
// Make canonical dir relative
|
||||
pkg.canonicalDir = path.relative(this._config.cwd, pkg.canonicalDir);
|
||||
// Signal as root
|
||||
pkg.root = true;
|
||||
|
||||
cliTree = this._tree2archy(pkg);
|
||||
str += '\n' + archy(cliTree);
|
||||
}, this);
|
||||
|
||||
if (str) {
|
||||
this._write(process.stdout, str);
|
||||
}
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._update = function (packages) {
|
||||
this._install(packages);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._list = function (tree) {
|
||||
var cliTree;
|
||||
|
||||
if (tree.pkgMeta) {
|
||||
tree.root = true;
|
||||
cliTree = archy(this._tree2archy(tree));
|
||||
} else {
|
||||
cliTree = stringifyObject(tree, { indent: ' ' }).replace(/[{}]/g, '') + '\n';
|
||||
}
|
||||
|
||||
this._write(process.stdout, cliTree);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._search = function (results) {
|
||||
var str = template.render('std/search-results.std', results);
|
||||
this._write(process.stdout, str);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._info = function (data) {
|
||||
var str = '';
|
||||
var pkgMeta = data;
|
||||
var includeVersions = false;
|
||||
|
||||
// If the response is the whole package info, the package meta
|
||||
// is under the "latest" property
|
||||
if (typeof data === 'object' && data.versions) {
|
||||
pkgMeta = data.latest;
|
||||
includeVersions = true;
|
||||
}
|
||||
|
||||
// Render package meta
|
||||
if (pkgMeta != null) {
|
||||
str += '\n' + this._highlightJson(pkgMeta) + '\n';
|
||||
}
|
||||
|
||||
// Render the versions at the end
|
||||
if (includeVersions) {
|
||||
data.hidePreReleases = false;
|
||||
data.numPreReleases = 0;
|
||||
// If output isn't verbose, hide prereleases
|
||||
if (!this._config.verbose) {
|
||||
data.versions = mout.array.filter(data.versions, function (version) {
|
||||
version = semverUtils.parse(version);
|
||||
if (!version.release && !version.build) {
|
||||
return true;
|
||||
}
|
||||
data.numPreReleases++;
|
||||
});
|
||||
data.hidePreReleases = !!data.numPreReleases;
|
||||
}
|
||||
str += '\n' + template.render('std/info.std', data);
|
||||
}
|
||||
|
||||
this._write(process.stdout, str);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._lookup = function (data) {
|
||||
var str = template.render('std/lookup.std', data);
|
||||
|
||||
this._write(process.stdout, str);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._link = function (data) {
|
||||
this._sizes.id = 4;
|
||||
|
||||
this.log({
|
||||
id: 'link',
|
||||
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) {
|
||||
var str;
|
||||
|
||||
// If no data passed, it means the user aborted
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
str = '\n' + template.render('std/register.std', data);
|
||||
this._write(process.stdout, str);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._cacheList = function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
var pkgMeta = entry.pkgMeta;
|
||||
var version = pkgMeta.version || pkgMeta._target;
|
||||
this._write(process.stdout, pkgMeta.name + '=' + pkgMeta._source + '#' + version + '\n');
|
||||
}, this);
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
|
||||
StandardRenderer.prototype._genericLog = function (log) {
|
||||
var stream;
|
||||
var str;
|
||||
|
||||
if (log.level === 'warn') {
|
||||
stream = process.stderr;
|
||||
} else {
|
||||
stream = process.stdout;
|
||||
}
|
||||
|
||||
str = this._prefix(log) + ' ' + log.message + '\n';
|
||||
this._write(stream, 'bower ' + str);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._checkoutLog = function (log) {
|
||||
if (this._compact) {
|
||||
log.message = log.origin.split('#')[0] + '#' + log.message;
|
||||
}
|
||||
|
||||
this._genericLog(log);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._progressLog = function (log) {
|
||||
if (this._compact) {
|
||||
log.message = log.origin + ' ' + log.message;
|
||||
}
|
||||
|
||||
this._genericLog(log);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._extractLog = function (log) {
|
||||
if (this._compact) {
|
||||
log.message = log.origin + ' ' + log.message;
|
||||
}
|
||||
|
||||
this._genericLog(log);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._incompatibleLog = function (log) {
|
||||
var str;
|
||||
var templatePath;
|
||||
|
||||
// Generate dependants string for each pick
|
||||
log.data.picks.forEach(function (pick) {
|
||||
pick.dependants = pick.dependants.map(function (dependant) {
|
||||
var release = dependant.pkgMeta._release;
|
||||
return dependant.endpoint.name + (release ? '#' + release : '');
|
||||
}).join(', ');
|
||||
});
|
||||
|
||||
templatePath = log.data.suitable ? 'std/conflict-resolved.std' : 'std/conflict.std';
|
||||
str = template.render(templatePath, log.data);
|
||||
|
||||
this._write(process.stdout, '\n');
|
||||
this._write(process.stdout, str);
|
||||
this._write(process.stdout, '\n');
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._solvedLog = function (log) {
|
||||
this._incompatibleLog(log);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._jsonLog = function (log) {
|
||||
this._write(process.stdout, '\n' + this._highlightJson(log.data.json) + '\n\n');
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._cachedEntryLog = function (log) {
|
||||
if (this._compact) {
|
||||
log.message = log.origin;
|
||||
}
|
||||
|
||||
this._genericLog(log);
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
|
||||
StandardRenderer.prototype._guessOrigin = function (log) {
|
||||
var data = log.data;
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.endpoint) {
|
||||
log.origin = data.endpoint.name || (data.registry && data.endpoint.source);
|
||||
|
||||
// Resort to using the resolver name for unnamed endpoints
|
||||
if (!log.origin && data.resolver) {
|
||||
log.origin = data.resolver.name;
|
||||
}
|
||||
|
||||
if (log.origin && data.endpoint.target) {
|
||||
log.origin += '#' + data.endpoint.target;
|
||||
}
|
||||
} else if (data.name) {
|
||||
log.origin = data.name;
|
||||
|
||||
if (data.version) {
|
||||
log.origin += '#' + data.version;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._prefix = function (log) {
|
||||
var label;
|
||||
var length;
|
||||
var nrSpaces;
|
||||
var id = this.constructor._idMappings[log.id] || log.id;
|
||||
var idColor = this._colors[log.level] || this._colors.default;
|
||||
|
||||
if (this._compact) {
|
||||
// If there's not enough space for the id, adjust it
|
||||
// for subsequent logs
|
||||
if (id.length > this._sizes.id) {
|
||||
this._sizes.id = id.length += this._sizes.sumup;
|
||||
}
|
||||
|
||||
return idColor(mout.string.rpad(id, this._sizes.id));
|
||||
}
|
||||
|
||||
// Construct the label
|
||||
label = log.origin || '';
|
||||
length = id.length + label.length + 1;
|
||||
nrSpaces = this._sizes.id + this._sizes.label - length;
|
||||
|
||||
// Ensure at least one space between the label and the id
|
||||
if (nrSpaces < 1) {
|
||||
// Also adjust the label size for subsequent logs
|
||||
this._sizes.label = label.length + this._sizes.sumup;
|
||||
nrSpaces = this._sizes.id + this._sizes.label - length;
|
||||
}
|
||||
|
||||
return chalk.green(label) + mout.string.repeat(' ', nrSpaces) + idColor(id);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._write = function (stream, str) {
|
||||
if (!this._config.color) {
|
||||
str = chalk.stripColor(str);
|
||||
}
|
||||
|
||||
stream.write(str);
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._highlightJson = function (json) {
|
||||
var cardinal = require('cardinal');
|
||||
|
||||
return cardinal.highlight(stringifyObject(json, { indent: ' ' }), {
|
||||
theme: {
|
||||
String: {
|
||||
_default: function (str) {
|
||||
return chalk.cyan(str);
|
||||
}
|
||||
},
|
||||
Identifier: {
|
||||
_default: function (str) {
|
||||
return chalk.green(str);
|
||||
}
|
||||
}
|
||||
},
|
||||
json: true
|
||||
});
|
||||
};
|
||||
|
||||
StandardRenderer.prototype._tree2archy = function (node) {
|
||||
var dependencies = mout.object.values(node.dependencies);
|
||||
var version = !node.missing ? node.pkgMeta._release || node.pkgMeta.version : null;
|
||||
var label = node.endpoint.name + (version ? '#' + version : '');
|
||||
var update;
|
||||
|
||||
if (node.root) {
|
||||
label += ' ' + node.canonicalDir;
|
||||
}
|
||||
|
||||
// State labels
|
||||
if (node.missing) {
|
||||
label += chalk.red(' not installed');
|
||||
return label;
|
||||
}
|
||||
|
||||
if (node.different) {
|
||||
label += chalk.red(' different');
|
||||
}
|
||||
|
||||
if (node.linked) {
|
||||
label += chalk.magenta(' linked');
|
||||
}
|
||||
|
||||
if (node.incompatible) {
|
||||
label += chalk.yellow(' incompatible') + ' with ' + node.endpoint.target;
|
||||
} else if (node.extraneous) {
|
||||
label += chalk.green(' extraneous');
|
||||
}
|
||||
|
||||
// New versions
|
||||
if (node.update) {
|
||||
update = '';
|
||||
|
||||
if (node.update.target && node.pkgMeta.version !== node.update.target) {
|
||||
update += node.update.target + ' available';
|
||||
}
|
||||
|
||||
if (node.update.latest !== node.update.target) {
|
||||
update += (update ? ', ' : '');
|
||||
update += 'latest is ' + node.update.latest;
|
||||
}
|
||||
|
||||
if (update) {
|
||||
label += ' (' + chalk.cyan(update) + ')';
|
||||
}
|
||||
}
|
||||
|
||||
if (!dependencies.length) {
|
||||
return label;
|
||||
}
|
||||
|
||||
return {
|
||||
label: label,
|
||||
nodes: mout.object.values(dependencies).map(this._tree2archy, this)
|
||||
};
|
||||
};
|
||||
|
||||
StandardRenderer._wideCommands = [
|
||||
'install',
|
||||
'update',
|
||||
'link',
|
||||
'info',
|
||||
'home',
|
||||
'register'
|
||||
];
|
||||
StandardRenderer._idMappings = {
|
||||
'mutual': 'conflict',
|
||||
'cached-entry': 'cached'
|
||||
};
|
||||
|
||||
module.exports = StandardRenderer;
|
||||
4
lib/renderers/index.js
Normal file
4
lib/renderers/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
Json: require('./JsonRenderer'),
|
||||
Standard: require('./StandardRenderer')
|
||||
};
|
||||
20
lib/templates/helpers/colors.js
Normal file
20
lib/templates/helpers/colors.js
Normal file
@@ -0,0 +1,20 @@
|
||||
var chalk = require('chalk');
|
||||
|
||||
var templateColors = [
|
||||
'yellow',
|
||||
'green',
|
||||
'cyan',
|
||||
'red',
|
||||
'white',
|
||||
'magenta'
|
||||
];
|
||||
|
||||
function colors(Handlebars) {
|
||||
templateColors.forEach(function (color) {
|
||||
Handlebars.registerHelper(color, function (context) {
|
||||
return chalk[color](context.fn(this));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = colors;
|
||||
22
lib/templates/helpers/condense.js
Normal file
22
lib/templates/helpers/condense.js
Normal file
@@ -0,0 +1,22 @@
|
||||
var mout = require('mout');
|
||||
var leadLinesRegExp = /^\r?\n/;
|
||||
var multipleLinesRegExp = /\r?\n(\r?\n)+/mg;
|
||||
|
||||
function condense(Handlebars) {
|
||||
Handlebars.registerHelper('condense', function (context) {
|
||||
var str = context.fn(this);
|
||||
|
||||
// Remove multiple lines
|
||||
str = str.replace(multipleLinesRegExp, '$1');
|
||||
|
||||
// Remove leading new lines (while keeping indentation)
|
||||
str = str.replace(leadLinesRegExp, '');
|
||||
|
||||
// Remove trailing whitespaces (including new lines);
|
||||
str = mout.string.rtrim(str);
|
||||
|
||||
return str;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = condense;
|
||||
12
lib/templates/helpers/indent.js
Normal file
12
lib/templates/helpers/indent.js
Normal file
@@ -0,0 +1,12 @@
|
||||
var mout = require('mout');
|
||||
|
||||
function indent(Handlebars) {
|
||||
Handlebars.registerHelper('indent', function (context) {
|
||||
var hash = context.hash;
|
||||
var indentStr = mout.string.repeat(' ', parseInt(hash.level, 10));
|
||||
|
||||
return context.fn(this).replace(/\n/g, '\n' + indentStr);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = indent;
|
||||
7
lib/templates/helpers/index.js
Normal file
7
lib/templates/helpers/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
colors: require('./colors'),
|
||||
condense: require('./condense'),
|
||||
indent: require('./indent'),
|
||||
rpad: require('./rpad'),
|
||||
sum: require('./sum')
|
||||
};
|
||||
12
lib/templates/helpers/rpad.js
Normal file
12
lib/templates/helpers/rpad.js
Normal file
@@ -0,0 +1,12 @@
|
||||
var mout = require('mout');
|
||||
|
||||
function rpad(Handlebars) {
|
||||
Handlebars.registerHelper('rpad', function (context) {
|
||||
var hash = context.hash;
|
||||
var minLength = parseInt(hash.minLength, 10);
|
||||
var chr = hash.char;
|
||||
return mout.string.rpad(context.fn(this), minLength, chr);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = rpad;
|
||||
7
lib/templates/helpers/sum.js
Normal file
7
lib/templates/helpers/sum.js
Normal file
@@ -0,0 +1,7 @@
|
||||
function sum(Handlebars) {
|
||||
Handlebars.registerHelper('sum', function (val1, val2) {
|
||||
return val1 + val2;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = sum;
|
||||
11
lib/templates/json/help-cache.json
Normal file
11
lib/templates/json/help-cache.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"command": "cache",
|
||||
"usage": [
|
||||
"cache <command> [<args>] [<options>]"
|
||||
],
|
||||
"commands": {
|
||||
"clean": "Clean cached packages",
|
||||
"list": "List cached packages"
|
||||
},
|
||||
"options": []
|
||||
}
|
||||
16
lib/templates/json/help-cache/clean.json
Normal file
16
lib/templates/json/help-cache/clean.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"command": "cache clean",
|
||||
"description": "Cleans cached packages.",
|
||||
"usage": [
|
||||
"cache clean [<options>]",
|
||||
"cache clean <name> [<name> ...] [<options>]",
|
||||
"cache clean <name>#<version> [<name>#<version> ..] [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
lib/templates/json/help-cache/list.json
Normal file
15
lib/templates/json/help-cache/list.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"command": "cache list",
|
||||
"description": "Lists cached packages.",
|
||||
"usage": [
|
||||
"cache list [<options>]",
|
||||
"cache list <name> [<name> ...] [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
16
lib/templates/json/help-home.json
Normal file
16
lib/templates/json/help-home.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"command": "home",
|
||||
"description": "Opens a package homepage into your favorite browser.\n\nIf no <package> is passed, opens the homepage of the local package.",
|
||||
"usage": [
|
||||
"home [<options>]",
|
||||
"home <package> [<options>]",
|
||||
"home <package>#<version> [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
16
lib/templates/json/help-info.json
Normal file
16
lib/templates/json/help-info.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"command": "info",
|
||||
"description": "Displays overall information of a package or of a particular version.",
|
||||
"usage": [
|
||||
"info <package> [<options>]",
|
||||
"info <package> [<property>] [<options>]",
|
||||
"info <package>#<version> [<property>] [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
14
lib/templates/json/help-init.json
Normal file
14
lib/templates/json/help-init.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"command": "init",
|
||||
"description": "Creates a bower.json file based on answers to questions.",
|
||||
"usage": [
|
||||
"init [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
45
lib/templates/json/help-install.json
Normal file
45
lib/templates/json/help-install.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"command": "install",
|
||||
"description": "Installs the project dependencies or a specific set of endpoints.\nEndpoints can have multiple forms:\n- <source>\n- <source>#<target>\n- <name>=<source>#<target>\n\nWhere:\n- <source> is a package URL, physical location or registry name\n- <target> is a valid range, commit, branch, etc.\n- <name> is the name it should have locally.",
|
||||
"usage": [
|
||||
"install [<options>]",
|
||||
"install <endpoint> [<endpoint> ..] [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-F",
|
||||
"flag": "--force-latest",
|
||||
"description": "Force latest version on conflict"
|
||||
},
|
||||
{
|
||||
"shorthand": "-f",
|
||||
"flag": "--force",
|
||||
"description": "If dependencies are installed, it reinstalls all installed components. It also forces installation even when there are non-bower directories with the same name in the components directory. Also bypasses the cache and overwrites to the cache anyway."
|
||||
},
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
},
|
||||
{
|
||||
"shorthand": "-p",
|
||||
"flag": "--production",
|
||||
"description": "Do not install project devDependencies"
|
||||
},
|
||||
{
|
||||
"shorthand": "-S",
|
||||
"flag": "--save",
|
||||
"description": "Save installed packages into the project's bower.json dependencies"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
lib/templates/json/help-link.json
Normal file
15
lib/templates/json/help-link.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"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.\nWhen the link is no longer necessary, simply remove it with 'bower uninstall <name>'.",
|
||||
"usage": [
|
||||
"link [<options>]",
|
||||
"link <name> [<local name>] [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
24
lib/templates/json/help-list.json
Normal file
24
lib/templates/json/help-list.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"command": "list",
|
||||
"description": "List local packages - and possible updates.",
|
||||
"usage": [
|
||||
"list [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
},
|
||||
{
|
||||
"shorthand": "-p",
|
||||
"flag": "--paths",
|
||||
"description": "Generates a simple JSON source mapping"
|
||||
},
|
||||
{
|
||||
"shorthand": "-r",
|
||||
"flag": "--relative",
|
||||
"description": "Make paths relative to the directory config property, which defaults to bower_components"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
lib/templates/json/help-login.json
Normal file
19
lib/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."
|
||||
}
|
||||
]
|
||||
}
|
||||
14
lib/templates/json/help-lookup.json
Normal file
14
lib/templates/json/help-lookup.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"command": "lookup",
|
||||
"description": "Look up a single package URL by name.",
|
||||
"usage": [
|
||||
"lookup <name> [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
14
lib/templates/json/help-prune.json
Normal file
14
lib/templates/json/help-prune.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"command": "prune",
|
||||
"description": "Uninstalls local extraneous packages.",
|
||||
"usage": [
|
||||
"prune [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
lib/templates/json/help-register.json
Normal file
19
lib/templates/json/help-register.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"command": "register",
|
||||
"description": "Registers a package.",
|
||||
"usage": [
|
||||
"register <name> <url> [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-f",
|
||||
"flag": "--force",
|
||||
"description": "Bypasses confirmation. Bower login is still needed."
|
||||
},
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
14
lib/templates/json/help-search.json
Normal file
14
lib/templates/json/help-search.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"command": "search",
|
||||
"description": "Search for packages by name.",
|
||||
"usage": [
|
||||
"search <name> [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
29
lib/templates/json/help-uninstall.json
Normal file
29
lib/templates/json/help-uninstall.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"command": "uninstall",
|
||||
"description": "Uninstalls a package locally from your bower_components directory",
|
||||
"usage": [
|
||||
"uninstall <name> [<name> ..] [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-f",
|
||||
"flag": "--force",
|
||||
"description": "Continues uninstallation even after a dependency conflict"
|
||||
},
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
},
|
||||
{
|
||||
"shorthand": "-S",
|
||||
"flag": "--save",
|
||||
"description": "Remove uninstalled packages from the project's bower.json dependencies"
|
||||
},
|
||||
{
|
||||
"shorthand": "-D",
|
||||
"flag": "--save-dev",
|
||||
"description": "Remove uninstalled packages from the project's bower.json devDependencies"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
lib/templates/json/help-unregister.json
Normal file
19
lib/templates/json/help-unregister.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"command": "unregister",
|
||||
"description": "Unregisters a package.",
|
||||
"usage": [
|
||||
"unregister <name> [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-f",
|
||||
"flag": "--force",
|
||||
"description": "Bypasses confirmation. Bower login is still needed."
|
||||
},
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
}
|
||||
]
|
||||
}
|
||||
34
lib/templates/json/help-update.json
Normal file
34
lib/templates/json/help-update.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"command": "update",
|
||||
"description": "Updates installed packages to their newest version according to bower.json.",
|
||||
"usage": [
|
||||
"update <name> [<name> ..] [<options>]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-F",
|
||||
"flag": "--force-latest",
|
||||
"description": "Force latest version on conflict"
|
||||
},
|
||||
{
|
||||
"shorthand": "-h",
|
||||
"flag": "--help",
|
||||
"description": "Show this help message"
|
||||
},
|
||||
{
|
||||
"shorthand": "-p",
|
||||
"flag": "--production",
|
||||
"description": "Do not install project devDependencies"
|
||||
},
|
||||
{
|
||||
"shorthand": "-S",
|
||||
"flag": "--save",
|
||||
"description": "Update dependencies in bower.json"
|
||||
},
|
||||
{
|
||||
"shorthand": "-D",
|
||||
"flag": "--save-dev",
|
||||
"description": "Update devDependencies in bower.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
14
lib/templates/json/help-version.json
Normal file
14
lib/templates/json/help-version.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"command": "version",
|
||||
"description": "Creates an empty version commit and tag, and fail if the repo is not clean.\n\nThe <version> argument should be a valid semver string, or one of following:\nbuild, patch, minor, major.\n\nIf supplied with --message (shorthand: -m) config option, bower will use it\nas a commit message when creating a version commit. If the message config\ncontains %s then that will be replaced with the resulting version number.\n\nFor example:\n\n bower version patch -m \"Upgrade to %s for reasons\"",
|
||||
"usage": [
|
||||
"version [<version> | major | minor | patch]"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-m",
|
||||
"flag": "--message",
|
||||
"description": "Custom git commit and tag message"
|
||||
}
|
||||
]
|
||||
}
|
||||
78
lib/templates/json/help.json
Normal file
78
lib/templates/json/help.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"usage": [
|
||||
"<command> [<args>] [<options>]"
|
||||
],
|
||||
"commands": {
|
||||
"cache": "Manage bower cache",
|
||||
"help": "Display help information about Bower",
|
||||
"home": "Opens a package homepage into your favorite browser",
|
||||
"info": "Info of a particular package",
|
||||
"init": "Interactively create a bower.json file",
|
||||
"install": "Install a package locally",
|
||||
"link": "Symlink a package folder",
|
||||
"list": "List local packages - and possible updates",
|
||||
"login": "Authenticate with GitHub and store credentials",
|
||||
"lookup": "Look up a single package URL by name",
|
||||
"prune": "Removes local extraneous packages",
|
||||
"register": "Register a package",
|
||||
"search": "Search for packages by name",
|
||||
"update": "Update a local package",
|
||||
"uninstall": "Remove a local package",
|
||||
"unregister": "Remove a package from the registry",
|
||||
"version": "Bump a package version"
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"shorthand": "-f",
|
||||
"flag": "--force",
|
||||
"description": "Makes various commands more forceful"
|
||||
},
|
||||
{
|
||||
"shorthand": "-j",
|
||||
"flag": "--json",
|
||||
"description": "Output consumable JSON"
|
||||
},
|
||||
{
|
||||
"shorthand": "-l",
|
||||
"flag": "--loglevel",
|
||||
"description": "What level of logs to report"
|
||||
},
|
||||
{
|
||||
"shorthand": "-o",
|
||||
"flag": "--offline",
|
||||
"description": "Do not hit the network"
|
||||
},
|
||||
{
|
||||
"shorthand": "-q",
|
||||
"flag": "--quiet",
|
||||
"description": "Only output important information"
|
||||
},
|
||||
{
|
||||
"shorthand": "-s",
|
||||
"flag": "--silent",
|
||||
"description": "Do not output anything, besides errors"
|
||||
},
|
||||
{
|
||||
"shorthand": "-V",
|
||||
"flag": "--verbose",
|
||||
"description": "Makes output more verbose"
|
||||
},
|
||||
{
|
||||
"flag": "--allow-root",
|
||||
"description": "Allows running commands as root"
|
||||
},
|
||||
{
|
||||
"shorthand": "-v",
|
||||
"flag": "--version",
|
||||
"description": "Output Bower version"
|
||||
},
|
||||
{
|
||||
"flag": "--no-color",
|
||||
"description": "Disable colors"
|
||||
},
|
||||
{
|
||||
"flag": "--config.interactive=false",
|
||||
"description": "Disable prompts"
|
||||
}
|
||||
]
|
||||
}
|
||||
9
lib/templates/std/conflict-resolved.std
Normal file
9
lib/templates/std/conflict-resolved.std
Normal file
@@ -0,0 +1,9 @@
|
||||
{{#yellow}}Please note that,{{/yellow}}
|
||||
{{#condense}}
|
||||
{{#each picks}}
|
||||
{{#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 {{#green}}{{suitable.endpoint.name}}#{{suitable.pkgMeta._release}}{{/green}}
|
||||
Code incompatibilities may occur.
|
||||
11
lib/templates/std/conflict.std
Normal file
11
lib/templates/std/conflict.std
Normal file
@@ -0,0 +1,11 @@
|
||||
{{#yellow}}Unable to find a suitable version for {{name}}, please choose one by typing one of the numbers below:{{/yellow}}
|
||||
{{#condense}}
|
||||
{{#each picks}}
|
||||
{{#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}}
|
||||
17
lib/templates/std/help-cache.std
Normal file
17
lib/templates/std/help-cache.std
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
Usage:
|
||||
|
||||
{{#condense}}
|
||||
{{#each usage}}
|
||||
{{#cyan}}bower{{/cyan}} {{.}}
|
||||
{{/each}}
|
||||
{{/condense}}
|
||||
|
||||
Commands:
|
||||
|
||||
{{#condense}}
|
||||
{{#each commands}}
|
||||
{{#rpad minLength="23"}}{{@key}}{{/rpad}} {{.}}
|
||||
{{/each}}
|
||||
{{/condense}}
|
||||
{{#rpad minLength="23"}}{{/rpad}}
|
||||
23
lib/templates/std/help-generic.std
Normal file
23
lib/templates/std/help-generic.std
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
Usage:
|
||||
|
||||
{{#condense}}
|
||||
{{#each usage}}
|
||||
{{#cyan}}bower{{/cyan}} {{.}}
|
||||
{{/each}}
|
||||
{{/condense}}
|
||||
|
||||
|
||||
Options:
|
||||
|
||||
{{#condense}}
|
||||
{{#each options}}
|
||||
{{#yellow}}{{#rpad minLength="23"}}{{#if shorthand}}{{shorthand}}, {{/if}}{{flag}}{{/rpad}}{{/yellow}} {{description}}
|
||||
{{/each}}
|
||||
{{/condense}}
|
||||
|
||||
Additionally all global options listed in 'bower help' are available
|
||||
|
||||
Description:
|
||||
|
||||
{{#indent level="4"}}{{description}}{{/indent}}
|
||||
26
lib/templates/std/help.std
Normal file
26
lib/templates/std/help.std
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
Usage:
|
||||
|
||||
{{#condense}}
|
||||
{{#each usage}}
|
||||
{{#cyan}}bower{{/cyan}} {{.}}
|
||||
{{/each}}
|
||||
{{/condense}}
|
||||
|
||||
Commands:
|
||||
|
||||
{{#condense}}
|
||||
{{#each commands}}
|
||||
{{#rpad minLength="23"}}{{@key}}{{/rpad}} {{.}}
|
||||
{{/each}}
|
||||
{{/condense}}
|
||||
|
||||
Options:
|
||||
|
||||
{{#condense}}
|
||||
{{#each options}}
|
||||
{{#yellow}}{{#rpad minLength="23"}}{{#if shorthand}}{{shorthand}}, {{/if}}{{flag}}{{/rpad}}{{/yellow}} {{description}}
|
||||
{{/each}}
|
||||
{{/condense}}
|
||||
|
||||
See 'bower help <command>' for more information on a specific command.
|
||||
14
lib/templates/std/info.std
Normal file
14
lib/templates/std/info.std
Normal file
@@ -0,0 +1,14 @@
|
||||
{{#if versions}}{{#cyan}}Available versions:{{/cyan}}
|
||||
{{#condense}}
|
||||
{{#versions}}
|
||||
- {{.}}
|
||||
{{/versions}}
|
||||
{{/condense}}
|
||||
|
||||
|
||||
{{#if hidePreReleases}}
|
||||
Show {{numPreReleases}} additional prereleases with ‘bower info {{name}} --verbose’
|
||||
{{/if}}
|
||||
You can request info for a specific version with 'bower info {{name}}#<version>'
|
||||
{{else}}No versions available.
|
||||
{{/if}}
|
||||
8
lib/templates/std/lookup.std
Normal file
8
lib/templates/std/lookup.std
Normal file
@@ -0,0 +1,8 @@
|
||||
{{#condense}}
|
||||
{{#if url}}
|
||||
{{#cyan}}{{name}}{{/cyan}} {{url}}
|
||||
{{else}}
|
||||
Package not found.
|
||||
{{/if}}
|
||||
{{/condense}}
|
||||
|
||||
5
lib/templates/std/register.std
Normal file
5
lib/templates/std/register.std
Normal file
@@ -0,0 +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, just release a valid semver tag.
|
||||
|
||||
Run {{#cyan}}bower info {{name}}{{/cyan}} to list the available versions.
|
||||
10
lib/templates/std/search-results.std
Normal file
10
lib/templates/std/search-results.std
Normal file
@@ -0,0 +1,10 @@
|
||||
{{#if .}}Search results:
|
||||
|
||||
{{#condense}}
|
||||
{{#.}}
|
||||
{{#cyan}}{{{name}}}{{/cyan}} {{{url}}}
|
||||
{{/.}}
|
||||
{{/condense}}
|
||||
|
||||
{{else}}No results.
|
||||
{{/if}}
|
||||
30
lib/util/abbreviations.js
Normal file
30
lib/util/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;
|
||||
};
|
||||
49
lib/util/cli.js
Normal file
49
lib/util/cli.js
Normal file
@@ -0,0 +1,49 @@
|
||||
var mout = require('mout');
|
||||
var nopt = require('nopt');
|
||||
var renderers = require('../renderers');
|
||||
|
||||
function readOptions(options, argv) {
|
||||
var types;
|
||||
var noptOptions;
|
||||
var parsedOptions = {};
|
||||
var shorthands = {};
|
||||
|
||||
if (Array.isArray(options)) {
|
||||
argv = options;
|
||||
options = {};
|
||||
} else {
|
||||
options = options || {};
|
||||
}
|
||||
|
||||
types = mout.object.map(options, function (option) {
|
||||
return option.type;
|
||||
});
|
||||
mout.object.forOwn(options, function (option, name) {
|
||||
shorthands[option.shorthand] = '--' + name;
|
||||
});
|
||||
|
||||
noptOptions = nopt(types, shorthands, argv);
|
||||
|
||||
// Filter only the specified options because nopt parses every --
|
||||
// Also make them camel case
|
||||
mout.object.forOwn(noptOptions, function (value, key) {
|
||||
if (options[key]) {
|
||||
parsedOptions[mout.string.camelCase(key)] = value;
|
||||
}
|
||||
});
|
||||
|
||||
parsedOptions.argv = noptOptions.argv;
|
||||
|
||||
return parsedOptions;
|
||||
}
|
||||
|
||||
function getRenderer(command, json, config) {
|
||||
if (config.json || json) {
|
||||
return new renderers.Json(command, config);
|
||||
}
|
||||
|
||||
return new renderers.Standard(command, config);
|
||||
}
|
||||
|
||||
module.exports.readOptions = readOptions;
|
||||
module.exports.getRenderer = getRenderer;
|
||||
120
lib/util/cmd.js
Normal file
120
lib/util/cmd.js
Normal file
@@ -0,0 +1,120 @@
|
||||
var cp = require('child_process');
|
||||
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';
|
||||
|
||||
if (isWin) {
|
||||
winBatchExtensions = ['.bat', '.cmd'];
|
||||
winWhichCache = {};
|
||||
}
|
||||
|
||||
function getWindowsCommand(command) {
|
||||
var fullCommand;
|
||||
var extension;
|
||||
|
||||
// Do we got the value converted in the cache?
|
||||
if (mout.object.hasOwn(winWhichCache, command)) {
|
||||
return winWhichCache[command];
|
||||
}
|
||||
|
||||
// Use which to retrieve the full command, which puts the extension in the end
|
||||
try {
|
||||
fullCommand = which.sync(command);
|
||||
} catch (err) {
|
||||
return winWhichCache[command] = command;
|
||||
}
|
||||
|
||||
extension = path.extname(fullCommand).toLowerCase();
|
||||
|
||||
// Does it need to be converted?
|
||||
if (winBatchExtensions.indexOf(extension) === -1) {
|
||||
return winWhichCache[command] = command;
|
||||
}
|
||||
|
||||
return winWhichCache[command] = fullCommand;
|
||||
}
|
||||
|
||||
// Executes a shell command, buffering the stdout and stderr
|
||||
// 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 executeCmd(command, args, options) {
|
||||
var process;
|
||||
var stderr = '';
|
||||
var stdout = '';
|
||||
var deferred = Q.defer();
|
||||
|
||||
// Windows workaround for .bat and .cmd files, see #626
|
||||
if (isWin) {
|
||||
command = getWindowsCommand(command);
|
||||
}
|
||||
|
||||
// Buffer output, reporting progress
|
||||
process = cp.spawn(command, args, options);
|
||||
process.stdout.on('data', function (data) {
|
||||
data = data.toString();
|
||||
deferred.notify(data);
|
||||
stdout += data;
|
||||
});
|
||||
process.stderr.on('data', function (data) {
|
||||
data = data.toString();
|
||||
deferred.notify(data);
|
||||
stderr += data;
|
||||
});
|
||||
|
||||
// If there is an error spawning the command, reject the promise
|
||||
process.on('error', function (error) {
|
||||
return deferred.reject(error);
|
||||
});
|
||||
|
||||
// Listen to the close event instead of exit
|
||||
// They are similar but close ensures that streams are flushed
|
||||
process.on('close', function (code) {
|
||||
var fullCommand;
|
||||
var error;
|
||||
|
||||
if (code) {
|
||||
// Generate the full command to be presented in the error message
|
||||
if (!Array.isArray(args)) {
|
||||
args = [];
|
||||
}
|
||||
|
||||
fullCommand = command;
|
||||
fullCommand += args.length ? ' ' + args.join(' ') : '';
|
||||
|
||||
// Build the error instance
|
||||
error = createError('Failed to execute "' + fullCommand + '", exit code of #' + code + '\n' + stderr, 'ECMDERR', {
|
||||
details: stderr,
|
||||
exitCode: code
|
||||
});
|
||||
|
||||
return deferred.reject(error);
|
||||
}
|
||||
|
||||
return deferred.resolve([stdout, stderr]);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function cmd(command, args, options) {
|
||||
return throttler.enqueue(executeCmd.bind(null, command, args, options));
|
||||
}
|
||||
|
||||
module.exports = cmd;
|
||||
@@ -1,65 +0,0 @@
|
||||
// ==========================================
|
||||
// BOWER: completion
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
// This module exposes a simple helper to parse the environment variables in
|
||||
// case of a tab completion command. It parses the provided `argv` (nopt's
|
||||
// remain arguments after `--`) and `env` (should be process.env)
|
||||
//
|
||||
// It is inspired and based off Isaac's work on npm.
|
||||
|
||||
module.exports = function (argv, env) {
|
||||
var opts = {};
|
||||
|
||||
// w is the words number, based on the cursor position
|
||||
opts.w = +env.COMP_CWORD;
|
||||
|
||||
// words is the escaped sequence of words following `bower`
|
||||
opts.words = argv.map(function (word) {
|
||||
return word.charAt(0) === '"' ?
|
||||
word.replace(/^"|"$/g, '') :
|
||||
word.replace(/\\ /g, ' ');
|
||||
});
|
||||
|
||||
// word is a shortcut to the last word in the line
|
||||
opts.word = opts.words[opts.w - 1];
|
||||
|
||||
// line is the sequence of tab completed words.
|
||||
opts.line = env.COMP_LINE;
|
||||
|
||||
// point is the cursor position in the line
|
||||
opts.point = +env.COMP_POINT;
|
||||
|
||||
// length is the whole line's length.
|
||||
opts.length = opts.line.length;
|
||||
|
||||
// partialLine is the line ignoring the sequence of characters after
|
||||
// cursor position, ie. tabbing at: bower install j|qu
|
||||
// gives back a partialLine: bower install j
|
||||
opts.partialLine = opts.line.slice(0, opts.point);
|
||||
|
||||
// partialWords is only returning the words based on cursor position,
|
||||
// ie tabbing at: bower install ze|pto backbone
|
||||
// gives back a partialWords array: ['install', 'zepto']
|
||||
opts.partialWords = opts.words.slice(0, opts.w);
|
||||
|
||||
return opts;
|
||||
};
|
||||
|
||||
module.exports.log = function (arr, opts) {
|
||||
arr = Array.isArray(arr) ? arr : [arr];
|
||||
arr.filter(module.exports.abbrev(opts)).forEach(function (word) {
|
||||
console.log(word);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.abbrev = function abbrev(opts) {
|
||||
var word = opts.word.replace(/\./g, '\\.');
|
||||
return function (it) {
|
||||
return new RegExp('^' + word).test(it);
|
||||
};
|
||||
};
|
||||
114
lib/util/copy.js
Normal file
114
lib/util/copy.js
Normal file
@@ -0,0 +1,114 @@
|
||||
var fstream = require('fstream');
|
||||
var fstreamIgnore = require('fstream-ignore');
|
||||
var fs = require('./fs');
|
||||
var Q = require('q');
|
||||
|
||||
function copy(reader, writer) {
|
||||
var deferred;
|
||||
var ignore;
|
||||
|
||||
// Filter symlinks because they are not 100% portable, specially
|
||||
// when linking between different drives
|
||||
// Following can't be enabled either because symlinks that reference
|
||||
// another symlinks will get filtered
|
||||
// See: https://github.com/bower/bower/issues/699
|
||||
reader.filter = filterSymlinks;
|
||||
reader.follow = false;
|
||||
|
||||
if (reader.type === 'Directory' && reader.ignore) {
|
||||
ignore = reader.ignore;
|
||||
reader = fstreamIgnore(reader);
|
||||
reader.addIgnoreRules(ignore);
|
||||
} else {
|
||||
reader = fstream.Reader(reader);
|
||||
}
|
||||
|
||||
deferred = Q.defer();
|
||||
|
||||
reader
|
||||
.on('error', deferred.reject)
|
||||
// Pipe to writer
|
||||
.pipe(fstream.Writer(writer))
|
||||
.on('error', deferred.reject)
|
||||
.on('close', deferred.resolve);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function copyMode(src, dst) {
|
||||
return Q.nfcall(fs.stat, src)
|
||||
.then(function (stat) {
|
||||
return Q.nfcall(fs.chmod, dst, stat.mode);
|
||||
});
|
||||
}
|
||||
|
||||
function filterSymlinks(entry) {
|
||||
return entry.type !== 'SymbolicLink';
|
||||
}
|
||||
|
||||
function parseOptions(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
if (opts.mode != null) {
|
||||
opts.copyMode = false;
|
||||
} else if (opts.copyMode == null) {
|
||||
opts.copyMode = true;
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
|
||||
// Available options:
|
||||
// - mode: force final mode of dst (defaults to null)
|
||||
// - copyMode: copy mode of src to dst, only if mode is not specified (defaults to true)
|
||||
function copyFile(src, dst, opts) {
|
||||
var promise;
|
||||
|
||||
opts = parseOptions(opts);
|
||||
|
||||
promise = copy({
|
||||
path: src,
|
||||
type: 'File'
|
||||
}, {
|
||||
path: dst,
|
||||
mode: opts.mode,
|
||||
type: 'File'
|
||||
});
|
||||
|
||||
if (opts.copyMode) {
|
||||
promise = promise.then(copyMode.bind(copyMode, src, dst));
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Available options:
|
||||
// - ignore: array of patterns to be ignored (defaults to null)
|
||||
// - mode: force final mode of dst (defaults to null)
|
||||
// - copyMode: copy mode of src to dst, only if mode is not specified (defaults to true)
|
||||
function copyDir(src, dst, opts) {
|
||||
var promise;
|
||||
|
||||
opts = parseOptions(opts);
|
||||
|
||||
promise = copy({
|
||||
path: src,
|
||||
type: 'Directory',
|
||||
ignore: opts.ignore
|
||||
}, {
|
||||
path: dst,
|
||||
mode: opts.mode,
|
||||
type: 'Directory'
|
||||
});
|
||||
|
||||
if (opts.copyMode) {
|
||||
promise = promise.then(copyMode.bind(copyMode, src, dst));
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
module.exports.copyDir = copyDir;
|
||||
module.exports.copyFile = copyFile;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user