mirror of
https://github.com/bower/bower.git
synced 2026-04-24 03:00:19 -04:00
Compare commits
2639 Commits
v0.2.0
...
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 | ||
|
|
4876ccb010 | ||
|
|
e20e50514a | ||
|
|
76b3a1c11f | ||
|
|
c0a90f78b8 | ||
|
|
b0f1f7156c | ||
|
|
d4ada06724 | ||
|
|
129512b82e | ||
|
|
16e236831d | ||
|
|
80c12b3e5f | ||
|
|
eb1e4982d5 | ||
|
|
7a4e76d720 | ||
|
|
9d42e8646a | ||
|
|
dcdd8c5a7f | ||
|
|
43561efb3b | ||
|
|
82ae977ba6 | ||
|
|
1f247704ff | ||
|
|
e076f6011d | ||
|
|
59dee1c499 | ||
|
|
4da57cbcb6 | ||
|
|
c3e130bdf0 | ||
|
|
974ae8a93e | ||
|
|
5afe38c130 | ||
|
|
174b9b50d6 | ||
|
|
674b02bda6 | ||
|
|
e8684ded7d | ||
|
|
003a3e355b | ||
|
|
e32f3de9b5 | ||
|
|
e7299b1125 | ||
|
|
8117b41e8a | ||
|
|
d3d39141be | ||
|
|
b7cbd36a07 | ||
|
|
4e75896240 | ||
|
|
72e5f36b67 | ||
|
|
f030d98dab | ||
|
|
0ac0f4dee7 | ||
|
|
fbecb17fea | ||
|
|
557c453b12 | ||
|
|
80038f6925 | ||
|
|
5136cfc7ce | ||
|
|
f55675ab2b | ||
|
|
0f284fd128 | ||
|
|
2ec352bc70 | ||
|
|
4f64412c19 | ||
|
|
7ab29c5516 | ||
|
|
aaf0aa12e1 | ||
|
|
7429e62d9d | ||
|
|
423fab8822 | ||
|
|
b6eb6642f8 | ||
|
|
36e790d6a9 | ||
|
|
7b64b8700c | ||
|
|
2cc9c06eab | ||
|
|
481e6bc498 | ||
|
|
8aa1fb33b6 | ||
|
|
2c992f1ece | ||
|
|
9ac919b7af | ||
|
|
755d87338d | ||
|
|
12fd86e681 | ||
|
|
d7b465359c | ||
|
|
63ecde0ea7 | ||
|
|
44bfa55bee | ||
|
|
520e1c4def | ||
|
|
6a63f97ce8 | ||
|
|
6195281e70 | ||
|
|
ad359e2659 | ||
|
|
a8a4f6fd2c | ||
|
|
0cb38676ef | ||
|
|
02e3ed4588 | ||
|
|
0b2aa433c5 | ||
|
|
41a992e42a | ||
|
|
5d45b04b22 | ||
|
|
3b0d8b37e7 | ||
|
|
f026c6238d | ||
|
|
d6cb0736d6 | ||
|
|
8488c657af | ||
|
|
e2b94620ad | ||
|
|
254dab3da7 | ||
|
|
42f090840c | ||
|
|
e5f40350d2 | ||
|
|
584736ad90 | ||
|
|
30f2092cc2 | ||
|
|
c52ad6fb7c | ||
|
|
06f6c37add | ||
|
|
00aaeb9305 | ||
|
|
ca9cb2f61b | ||
|
|
a8c72398ba | ||
|
|
01bd65f8e6 | ||
|
|
a0c7e5df57 | ||
|
|
9240a982d6 | ||
|
|
1d3d37034f | ||
|
|
913acabdd2 | ||
|
|
abbeff8126 | ||
|
|
ecfb439543 | ||
|
|
53f7b6c704 | ||
|
|
9623f44d83 | ||
|
|
fe9e0dd8c1 | ||
|
|
d5b351cd38 | ||
|
|
0106c10483 | ||
|
|
d6e742012e | ||
|
|
4dc4cfdbcf | ||
|
|
8220514307 | ||
|
|
1d06f7e93c | ||
|
|
ec965791bf | ||
|
|
00ea8e8984 | ||
|
|
9d5f2fb599 | ||
|
|
983a7fa707 | ||
|
|
5a5a243486 | ||
|
|
acb8b9de53 | ||
|
|
aca618080b | ||
|
|
b7bf83c88f | ||
|
|
36b90691c8 | ||
|
|
05e44d5148 | ||
|
|
67290157b7 | ||
|
|
64ed10530b | ||
|
|
eb9f6aca03 | ||
|
|
82f5ad4af2 | ||
|
|
ae87fd4a32 | ||
|
|
9c34adf2dc | ||
|
|
6812883511 | ||
|
|
50c5edf95e | ||
|
|
8ce4c1cdec | ||
|
|
f242e93b08 | ||
|
|
b544425667 | ||
|
|
8dbffe6fa7 | ||
|
|
8a00820dbb | ||
|
|
12f72ae5cc | ||
|
|
ef721bcd9d | ||
|
|
a122f992ba | ||
|
|
a6c6e221f7 | ||
|
|
7337a80550 | ||
|
|
5f094a894a | ||
|
|
4a70c470b5 | ||
|
|
8e7ed131b9 | ||
|
|
3a6dbaee94 | ||
|
|
1553d67d4b | ||
|
|
ea5df078a6 | ||
|
|
549f857aed | ||
|
|
4fb74b326f | ||
|
|
3dec991e7e | ||
|
|
f9c1e81fb3 | ||
|
|
999519bd94 | ||
|
|
fa81d7831b | ||
|
|
8c00857dd1 | ||
|
|
10d7487f2e | ||
|
|
26503f8ee3 | ||
|
|
879f19c70a | ||
|
|
a93b166c1f | ||
|
|
3917fa0dc5 | ||
|
|
caa983361e | ||
|
|
bdb4b4b6be | ||
|
|
728d96b1c6 | ||
|
|
edbe667e73 | ||
|
|
0466e19456 | ||
|
|
88843bf855 | ||
|
|
e11651648f | ||
|
|
174c37d75d | ||
|
|
f9f3ee1d06 | ||
|
|
d20625c7bb | ||
|
|
f0fefc7620 | ||
|
|
667b094bb7 | ||
|
|
3a48ba582f | ||
|
|
be679d6bb1 | ||
|
|
4b134a3bdb | ||
|
|
a3fa306916 | ||
|
|
d561299cdf | ||
|
|
fa55112b53 | ||
|
|
4f5593e7bf | ||
|
|
a87b86b3e4 | ||
|
|
7ba28d5c29 | ||
|
|
53c48a2dc5 | ||
|
|
84b59b1e55 | ||
|
|
b4176ce250 | ||
|
|
9505f3763c | ||
|
|
6ac43b2f3a | ||
|
|
8f1d538dae | ||
|
|
c9e1bbbb0f | ||
|
|
366d7f82cc | ||
|
|
65367c2f5b | ||
|
|
b63ab2be88 | ||
|
|
66a61acd5c | ||
|
|
3b69d1a4d5 | ||
|
|
b594d84855 | ||
|
|
c4d3612638 | ||
|
|
951013c006 | ||
|
|
b5d7f366d3 | ||
|
|
8e4a7b7147 | ||
|
|
57e46aff76 | ||
|
|
49e08443b7 | ||
|
|
ae4356552f | ||
|
|
87456121b1 | ||
|
|
b5f68581df | ||
|
|
4a81edf5da | ||
|
|
a7612a8828 | ||
|
|
8bfa518f15 | ||
|
|
8dcc2d5979 | ||
|
|
75b9e21e77 | ||
|
|
bcf9b0e880 | ||
|
|
648fcace0f | ||
|
|
e80ed8d130 | ||
|
|
4dde213a6f | ||
|
|
d458bf152b | ||
|
|
9ba7149689 | ||
|
|
a5c65afc46 | ||
|
|
30d95d3751 | ||
|
|
faa3cf4cb8 | ||
|
|
f925fb9adb | ||
|
|
e021276072 | ||
|
|
15cd35efb9 | ||
|
|
069e9b80b6 | ||
|
|
be02ef81a4 | ||
|
|
19b9261e8b | ||
|
|
3ac918f306 | ||
|
|
dbd64a0a55 | ||
|
|
b3af02a4ab | ||
|
|
88d674848f | ||
|
|
3793267c52 | ||
|
|
6a07c16f17 | ||
|
|
41e6f0bdbc | ||
|
|
3b1642a52f | ||
|
|
4c3a1dbd91 | ||
|
|
fae41c4179 | ||
|
|
91c5bb8c36 | ||
|
|
b9b59c9fbc | ||
|
|
bc598a334b | ||
|
|
c645768267 | ||
|
|
e245709963 | ||
|
|
aa30fe885a | ||
|
|
2c7435a33f | ||
|
|
c06d8b271e | ||
|
|
b85a565798 | ||
|
|
82c20e8a6b | ||
|
|
04919746e8 | ||
|
|
64b86ccf04 | ||
|
|
4a83227ca8 | ||
|
|
01d2ee079c | ||
|
|
e9514423ce | ||
|
|
822efbe34c | ||
|
|
4f5f36e4b3 | ||
|
|
70df4932f2 | ||
|
|
133b1c12a7 | ||
|
|
657a5e0209 | ||
|
|
bdedd4d995 | ||
|
|
03bbbb22af | ||
|
|
bd11df44ee | ||
|
|
b46e8166cc | ||
|
|
2948eae448 | ||
|
|
b23cbb8c3f | ||
|
|
b3d04e9083 | ||
|
|
d59e1e45a8 | ||
|
|
8598f9493d | ||
|
|
376788f385 | ||
|
|
5ce5b4c3c6 | ||
|
|
496eeb1336 | ||
|
|
030e557575 | ||
|
|
4ab8cd398b | ||
|
|
8c9a08b3a3 | ||
|
|
8fcb328411 | ||
|
|
d60ca60360 | ||
|
|
eb747d7508 | ||
|
|
12921bfead | ||
|
|
d23adeef5a | ||
|
|
b0687baa00 | ||
|
|
7437be8869 | ||
|
|
8d9bba61a1 | ||
|
|
8684078437 | ||
|
|
9e3081e87c | ||
|
|
4ea387ed35 | ||
|
|
83661eec48 | ||
|
|
07b93ffe60 | ||
|
|
ab9c3f9ec7 | ||
|
|
381c5fea77 | ||
|
|
dc1f142241 | ||
|
|
197fc34618 | ||
|
|
9c81c3ce29 | ||
|
|
8dc17f64f8 | ||
|
|
d4329c3b00 | ||
|
|
d8278e0fb0 | ||
|
|
7021b112ea | ||
|
|
3af267669e | ||
|
|
b904177ccc | ||
|
|
0b85b50add | ||
|
|
b22cc1cc04 | ||
|
|
b371dd2cf7 | ||
|
|
00a4923d5f | ||
|
|
8fe0abfc4b | ||
|
|
f50520e04e | ||
|
|
6a6eb75a3b | ||
|
|
bff311f8ec | ||
|
|
a74f6630df | ||
|
|
f0836ba655 | ||
|
|
f7438617a2 | ||
|
|
f4d386b694 | ||
|
|
64d1d4a1c9 | ||
|
|
956dac2c50 | ||
|
|
51bd1f559c | ||
|
|
97f8efcbb9 | ||
|
|
f335f5f1fb | ||
|
|
712953e799 | ||
|
|
406ed297ca | ||
|
|
4d1882ed49 | ||
|
|
723cfd52d1 | ||
|
|
8a4963026a | ||
|
|
341ad5c10c | ||
|
|
8716c2bb6c | ||
|
|
4a40e755e7 | ||
|
|
59d49166c7 | ||
|
|
52e72b466a | ||
|
|
9825707837 | ||
|
|
173f5ed17e | ||
|
|
d53878c130 | ||
|
|
badcde0ef9 | ||
|
|
df4ee49726 | ||
|
|
a7e464a902 | ||
|
|
faa14a079c | ||
|
|
b7c3aa59a7 | ||
|
|
2abde5539d | ||
|
|
c5a0f0ddcf | ||
|
|
612084fb57 | ||
|
|
708d68aee7 | ||
|
|
760ef7a13a | ||
|
|
be6a424c1c | ||
|
|
be79b7028c | ||
|
|
3bcd2e08e8 | ||
|
|
5967acb15e | ||
|
|
71cddb628e | ||
|
|
24d126e702 | ||
|
|
c589625b01 | ||
|
|
a403585e1e | ||
|
|
008e0d8c8a | ||
|
|
f220ee5302 | ||
|
|
a1c16a3bae | ||
|
|
3f79247ac2 | ||
|
|
da9d553899 | ||
|
|
bf371acc41 | ||
|
|
6ef92b0c95 | ||
|
|
5cd8dd7094 | ||
|
|
6823acd0ff | ||
|
|
cb2cb7ea75 | ||
|
|
a2e593c06c | ||
|
|
6bd00a0e8e | ||
|
|
5dbb6d6346 | ||
|
|
8434acf9ed | ||
|
|
6f169fb3e2 | ||
|
|
a00d930d0f | ||
|
|
03e55cadbb | ||
|
|
2cad76d6ed | ||
|
|
b0bc14a75e | ||
|
|
058524ca53 | ||
|
|
0a4a484f3d | ||
|
|
e8b473b550 | ||
|
|
a134950f85 | ||
|
|
71a7b8e8f0 | ||
|
|
fd31f247a6 | ||
|
|
ed13cab994 | ||
|
|
b168f09419 | ||
|
|
77fb081b93 | ||
|
|
c9e0abfdf3 | ||
|
|
84f5948341 | ||
|
|
d8ffbe2c33 | ||
|
|
0e0a1c6432 | ||
|
|
0ca0ac0782 | ||
|
|
b14a1b4ba3 | ||
|
|
d53a74df23 | ||
|
|
eba74f1182 | ||
|
|
8eeeb2ddd6 | ||
|
|
e0c30b5573 | ||
|
|
9e7f136948 | ||
|
|
6c6e193362 |
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
|
||||
|
||||
670
CHANGELOG.md
Normal file
670
CHANGELOG.md
Normal file
@@ -0,0 +1,670 @@
|
||||
# Changelog
|
||||
|
||||
## 1.8.0 - 2016-11-07
|
||||
|
||||
- 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/bower/bower/issues/274))
|
||||
|
||||
## 0.8.0 - 2013-02-24
|
||||
- __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/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/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/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/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/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/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
|
||||
- Fix error reporting when reading invalid project component.json
|
||||
|
||||
## 0.6.6 - 2012-12-03
|
||||
- Improve error handling while reading component.json
|
||||
- Fix package name not being correctly collected in the error summary
|
||||
|
||||
## 0.6.5 - 2012-12-01
|
||||
- Fix error summary not being displayed in some edge cases
|
||||
- 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/bower/bower/issues/160))
|
||||
|
||||
## 0.6.3 - 2012-11-24
|
||||
- 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/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)
|
||||
- Fix error reporting for nested deps
|
||||
- Abort if a repository is detected when installing.
|
||||
This is useful to prevent people from loosing their work
|
||||
- Minor fixes and improvements
|
||||
|
||||
## 0.5.1 - 2012-11-20
|
||||
- Add errors summary to the end of install/update commands
|
||||
- Add windows instructions to the README
|
||||
|
||||
## 0.5.0 - 2012-11-19
|
||||
- __Remove package.json support__
|
||||
- __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/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
|
||||
- `bower ls` now informs when a package has a new commit (for non-tagged repos)
|
||||
- Add jshintrc and fix a lot of issues related with JSHint warnings
|
||||
- `bower register` now prompts if the user really wants to proceed
|
||||
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.
|
||||
|
||||
317
README.md
317
README.md
@@ -1,217 +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/maccman/package-jquery.git
|
||||
bower install http://code.jquery.com/jquery-1.7.2.js
|
||||
bower install ./repos/jquery
|
||||
|
||||
As you can see, packages can be installed by name, Git endpoint, URL or local path.
|
||||
|
||||
[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.
|
||||
|
||||
### Defining a package
|
||||
|
||||
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.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "myProject",
|
||||
"version": "1.0.0",
|
||||
"main": "./path/to/main.css",
|
||||
"dependencies": {
|
||||
"jquery": "~1.7.2"
|
||||
}
|
||||
}
|
||||
```sh
|
||||
$ npm install -g bower
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
For now, `name`, `version`, `main`, and `dependencies` 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:
|
||||
|
||||
```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"
|
||||
}
|
||||
}
|
||||
## 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
|
||||
```
|
||||
|
||||
There should only be at most one file per file type in the `main` list. So only one `.js` or `.css`.
|
||||
### Using packages
|
||||
|
||||
### Installing dependencies
|
||||
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).
|
||||
|
||||
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:
|
||||
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/)).
|
||||
|
||||
### Uninstalling packages
|
||||
|
||||
```
|
||||
/component.json
|
||||
/components/jquery/index.js
|
||||
/components/jquery/component.json
|
||||
To uninstall a locally installed package:
|
||||
|
||||
```sh
|
||||
$ bower uninstall <package-name>
|
||||
```
|
||||
|
||||
You can also install packages one at a time `bower install git://my/git/thing`
|
||||
### prezto and oh-my-zsh users
|
||||
|
||||
There are no system wide dependencies, no dependencies are shared between different apps, and the dependency tree is flat.
|
||||
On `prezto` or `oh-my-zsh`, do not forget to `alias bower='noglob bower'` or `bower install jquery\#1.9.1`
|
||||
|
||||
### Deploying
|
||||
### Never run Bower with sudo
|
||||
|
||||
The easiest approach is to use Bower statically, just reference the packages manually from a script tag:
|
||||
Bower is a user command; there is no need to execute it with superuser permissions.
|
||||
|
||||
<script src="components/jquery/index.js"></script>
|
||||
### Windows users
|
||||
|
||||
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/).
|
||||
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:
|
||||
|
||||
For example, to use Sprockets:
|
||||
<img src="https://cloud.githubusercontent.com/assets/10702007/10532690/d2e8991a-7386-11e5-9a57-613c7f92e84e.png" width="534" height="418" alt="Git for Windows" />
|
||||
|
||||
```ruby
|
||||
environment = Sprockets::Environment.new
|
||||
environment.append_path 'components'
|
||||
environment.append_path 'public'
|
||||
run environment
|
||||
<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
|
||||
```
|
||||
|
||||
### Package Consumption
|
||||
## Configuration
|
||||
|
||||
Bower also makes available a source mapping – this can be used by build tools to easily consume Bower components.
|
||||
Bower can be configured using JSON in a `.bowerrc` file. Read over available options at [bower.io/docs/config](http://bower.io/docs/config).
|
||||
|
||||
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"
|
||||
}
|
||||
## Support
|
||||
|
||||
* [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
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome [contributions](https://github.com/bower/bower/graphs/contributors) of all kinds from anyone. Please take a moment to review the [guidelines for contributing](CONTRIBUTING.md).
|
||||
|
||||
* [Bug reports](https://github.com/bower/bower/wiki/Report-a-Bug)
|
||||
* [Feature requests](CONTRIBUTING.md#features)
|
||||
* [Pull requests](CONTRIBUTING.md#pull-requests)
|
||||
|
||||
|
||||
Note that on Windows for tests to pass you need to configure Git before cloning:
|
||||
|
||||
```
|
||||
git config --global core.autocrlf input
|
||||
```
|
||||
|
||||
### 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:
|
||||
## Backers
|
||||
|
||||
bower register myawesomepackagename git://github.com/maccmans/face
|
||||
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/bower#backer)]
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
<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>
|
||||
|
||||
|
||||
### FAQ
|
||||
## Sponsors
|
||||
|
||||
**What distinguishes Bower from Jam, Volo, Component, or Ender? What does it do better?**
|
||||
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)]
|
||||
|
||||
Bower is a lower level component than Jam, Volo, Component, or Ender. These managers could consume Bower as a dependency.
|
||||
<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>
|
||||
|
||||
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.
|
||||
|
||||
Bower is working under the assumption that there is a single, common problem in frontend application development: dependency resolution. Past attempts (Jam, Volo, Ender, Component) 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.
|
||||
|
||||
|
||||
### Contact
|
||||
|
||||
Have a question? Ask on our mailing list!
|
||||
|
||||
twitter-bower@googlegroups.com
|
||||
|
||||
http://groups.google.com/group/twitter-bower
|
||||
|
||||
### Authors
|
||||
|
||||
+ [@fat](http://github.com/fat)
|
||||
+ [@maccman](http://github.com/maccman)
|
||||
|
||||
Thanks for assistance and contributions:
|
||||
|
||||
+ [@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)
|
||||
+ [@visionmedia](http://github.com/visionmedia)
|
||||
+ [@wagenet](http://github.com/wagenet)
|
||||
+ [@wycats](http://github.com/wycats)
|
||||
|
||||
## 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
|
||||
|
||||
39
bin/bower
39
bin/bower
@@ -1,40 +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) { data && console.log(data); })
|
||||
.on('end', function (data) { data && console.log(data); })
|
||||
.on('error', function (err) {
|
||||
if (options.verbose) throw err;
|
||||
else template('error', { message: err.message }).on('data', function (d) { console.log (d); });
|
||||
})
|
||||
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();
|
||||
}
|
||||
}
|
||||
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,35 +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 hogan = require('hogan.js');
|
||||
var nopt = require('nopt');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
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');
|
||||
var templateName = name ? 'help-' + name : 'help';
|
||||
return Q.promise(function (resolve) {
|
||||
fs.exists(json, resolve);
|
||||
})
|
||||
.then(function (exists) {
|
||||
if (!exists) {
|
||||
throw createError('Unknown command: ' + name, 'EUNKNOWNCMD', {
|
||||
command: name
|
||||
});
|
||||
}
|
||||
|
||||
if (!name) context = { commands: Object.keys(commands).join(', ') };
|
||||
_.extend(context, config)
|
||||
template(templateName, context).on('data', emitter.emit.bind(emitter, 'end'));
|
||||
return emitter;
|
||||
return require(json);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt({}, {}, argv);
|
||||
var paths = options.argv.remain.slice(1);
|
||||
return module.exports(paths[0]);
|
||||
};
|
||||
// -------------------
|
||||
|
||||
help.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
var options = cli.readOptions(argv);
|
||||
var name = options.argv.remain.slice(1).join(' ');
|
||||
|
||||
return [name];
|
||||
};
|
||||
|
||||
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,20 +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'),
|
||||
'lookup': require('./lookup'),
|
||||
'info': require('./info'),
|
||||
'register': require('./register'),
|
||||
'search': require('./search')
|
||||
};
|
||||
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,44 +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 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;
|
||||
|
||||
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 = info;
|
||||
|
||||
313
lib/commands/init.js
Normal file
313
lib/commands/init.js
Normal file
@@ -0,0 +1,313 @@
|
||||
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;
|
||||
|
||||
config = config || {};
|
||||
|
||||
if (!config.cwd) {
|
||||
config.cwd = process.cwd();
|
||||
}
|
||||
|
||||
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'
|
||||
});
|
||||
}
|
||||
|
||||
project = new Project(config, logger);
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
.then(function (good) {
|
||||
if (!good) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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,47 +1,51 @@
|
||||
// ==========================================
|
||||
// BOWER: Install API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
// 1. Recursively resolve dependencies
|
||||
// 2. Intelligently work out which deps to
|
||||
// use (versioning)
|
||||
// 3. Throw if deps conflict
|
||||
// ==========================================
|
||||
var endpointParser = require('bower-endpoint-parser');
|
||||
var Project = require('../core/Project');
|
||||
var defaultConfig = require('../config');
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var async = require('async');
|
||||
var nopt = require('nopt');
|
||||
function install(logger, endpoints, options, config) {
|
||||
var project;
|
||||
var decEndpoints;
|
||||
|
||||
var Manager = require('../core/manager');
|
||||
var save = require('../util/save');
|
||||
var list = require('./list');
|
||||
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 };
|
||||
var shorthand = { 'h': ['--help'], 'S': ['--save'] };
|
||||
// Convert endpoints to decomposed endpoints
|
||||
endpoints = endpoints || [];
|
||||
decEndpoints = endpoints.map(function (endpoint) {
|
||||
|
||||
module.exports = function (paths, options) {
|
||||
var emitter = new Emitter;
|
||||
var manager = new Manager(paths)
|
||||
// handle @ as version divider
|
||||
var splitParts = endpoint.split('/');
|
||||
splitParts[splitParts.length - 1] = splitParts[splitParts.length - 1].replace('@', '#');
|
||||
endpoint = splitParts.join('/');
|
||||
|
||||
if (options && options.save) save(emitter, manager, paths);
|
||||
return endpointParser.decompose(endpoint);
|
||||
});
|
||||
|
||||
manager
|
||||
.on('data' , emitter.emit.bind(emitter, 'data'))
|
||||
.on('error' , emitter.emit.bind(emitter, 'error'))
|
||||
.on('resolve' , emitter.emit.bind(emitter, 'end'))
|
||||
.resolve();
|
||||
return project.install(decEndpoints, options, config);
|
||||
}
|
||||
|
||||
return emitter;
|
||||
// -------------------
|
||||
|
||||
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.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 = install;
|
||||
|
||||
85
lib/commands/link.js
Normal file
85
lib/commands/link.js
Normal file
@@ -0,0 +1,85 @@
|
||||
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');
|
||||
|
||||
function link(logger, name, localName, config) {
|
||||
if (name) {
|
||||
return linkTo(logger, name, localName, config);
|
||||
} else {
|
||||
return linkSelf(logger, config);
|
||||
}
|
||||
}
|
||||
|
||||
function linkSelf(logger, config) {
|
||||
var project;
|
||||
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
return project.getJson()
|
||||
.then(function (json) {
|
||||
var src = config.cwd;
|
||||
var dst = path.join(config.storage.links, json.name);
|
||||
|
||||
// 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
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function linkTo(logger, name, localName, config) {
|
||||
var src;
|
||||
var dst;
|
||||
var project;
|
||||
|
||||
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
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
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];
|
||||
|
||||
return [name, localName];
|
||||
};
|
||||
|
||||
module.exports = link;
|
||||
@@ -1,181 +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 semver = require('semver');
|
||||
var archy = require('archy');
|
||||
var async = require('async');
|
||||
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'] };
|
||||
var optionTypes = { help: Boolean, paths: Boolean, map: Boolean };
|
||||
|
||||
var getTree = function (packages, subPackages, result) {
|
||||
var result = result || {};
|
||||
|
||||
_.each(subPackages || packages, function (pkg, name) {
|
||||
|
||||
result[pkg.name] = {};
|
||||
|
||||
Object.keys(pkg.json.dependencies || {}).forEach(function (name) {
|
||||
result[pkg.name][name] = {};
|
||||
});
|
||||
|
||||
var subPackages = {};
|
||||
|
||||
Object.keys(pkg.json.dependencies || {}).forEach(function (name) {
|
||||
subPackages[name] = packages[name] || new Package(name, null);
|
||||
});
|
||||
|
||||
getTree(packages, subPackages, result[pkg.name]);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
var generatePath = function (name, main) {
|
||||
if (typeof main == 'string') {
|
||||
return path.join(config.directory, name, main);
|
||||
} else {
|
||||
var main = main.map(function (main) { return generatePath(name, main); });
|
||||
return main.length == 1 ? main[0] : main;
|
||||
}
|
||||
}
|
||||
|
||||
var buildSource = function (pkg, shallow) {
|
||||
var result = {};
|
||||
|
||||
['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]);
|
||||
// Make relative option true by default when used with paths
|
||||
if (options.paths && options.relative == null) {
|
||||
options.relative = true;
|
||||
}
|
||||
|
||||
});
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
return result;
|
||||
}
|
||||
return project.getTree(options)
|
||||
.spread(function (tree, flattened) {
|
||||
// Relativize paths
|
||||
// Also normalize paths on windows
|
||||
project.walkTree(tree, function (node) {
|
||||
if (node.missing) {
|
||||
return;
|
||||
}
|
||||
|
||||
var getNodes = function (packages, tree) {
|
||||
return Object.keys(tree).map(function (key) {
|
||||
var upgrade;
|
||||
if (options.relative) {
|
||||
node.canonicalDir = path.relative(config.cwd, node.canonicalDir);
|
||||
}
|
||||
if (options.paths) {
|
||||
node.canonicalDir = normalize(node.canonicalDir);
|
||||
}
|
||||
}, true);
|
||||
|
||||
var version = packages[key].json.repository
|
||||
&& packages[key].json.repository.type == 'asset'
|
||||
&& packages[key]
|
||||
&& packages[key].version == "0.0.0" ?
|
||||
packages[key].json.repository.url : packages[key] && packages[key].version;
|
||||
// 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 (version && packages[key].tags.indexOf(version)) {
|
||||
upgrade = packages[key].tags[0];
|
||||
}
|
||||
if (options.relative) {
|
||||
node.canonicalDir = path.relative(config.cwd, node.canonicalDir);
|
||||
}
|
||||
if (options.paths) {
|
||||
node.canonicalDir = normalize(node.canonicalDir);
|
||||
}
|
||||
});
|
||||
|
||||
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 cliTree = function (emitter, packages, tree) {
|
||||
emitter.emit('data', archy({
|
||||
label: process.cwd(),
|
||||
nodes: getNodes(packages, tree)
|
||||
}));
|
||||
};
|
||||
|
||||
module.exports = function (options) {
|
||||
var manager = new Manager;
|
||||
var emitter = new Emitter;
|
||||
|
||||
options = options || {};
|
||||
|
||||
manager.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
manager.on('error', emitter.emit.bind(emitter, 'error'));
|
||||
|
||||
manager.once('resolveLocal', function () {
|
||||
var packages = {};
|
||||
|
||||
Object.keys(manager.dependencies).forEach(function (key) {
|
||||
packages[key] = manager.dependencies[key][0];
|
||||
});
|
||||
|
||||
async.forEach(_.values(packages), function (pkg, next) {
|
||||
pkg.once('loadJSON', function () {
|
||||
if (this.json.repository && this.json.repository.type == 'git') {
|
||||
pkg.once('versions', function (versions) {
|
||||
pkg.tags = versions.map(semver.clean);
|
||||
next();
|
||||
}).versions();
|
||||
} else {
|
||||
pkg.tags = [];
|
||||
next();
|
||||
// Render paths?
|
||||
if (options.paths) {
|
||||
return paths(flattened);
|
||||
}
|
||||
}).loadJSON();
|
||||
}, function () {
|
||||
var tree = getTree(packages);
|
||||
if (!options.paths && !options.map && options.argv) return cliTree(emitter, packages, tree);
|
||||
tree = options.paths ? shallowTree(packages, tree) : deepTree(packages, tree);
|
||||
emitter.emit('data', options.argv ? JSON.stringify(tree, null, 2) : tree);
|
||||
|
||||
// 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, '*')
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}).resolveLocal();
|
||||
// Set the versions also for the root node
|
||||
tree.versions = [];
|
||||
|
||||
return emitter;
|
||||
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;
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
if (options.help) return help('list');
|
||||
return module.exports(options);
|
||||
};
|
||||
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,49 +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 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', emitter.emit.bind(emitter, 'data'));
|
||||
} else {
|
||||
template('warning-missing', {name: name})
|
||||
.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
template('lookup', {name: name, url: url})
|
||||
.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
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 = 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,38 +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');
|
||||
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'] };
|
||||
name = (name || '').trim();
|
||||
source = (source || '').trim();
|
||||
|
||||
module.exports = function (name, url) {
|
||||
var emitter = new Emitter;
|
||||
url = source.match(githubSourceRegex) ? getGithubUrl(source) : source;
|
||||
|
||||
source.register(name, url, function (err) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
// Bypass any cache
|
||||
config.offline = false;
|
||||
config.force = true;
|
||||
|
||||
template('register', {name: name, url: url})
|
||||
.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
});
|
||||
return Q.try(function () {
|
||||
// Verify name and url
|
||||
if (!name || !url) {
|
||||
throw createError('Usage: bower register <name> <url>', 'EINVFORMAT');
|
||||
}
|
||||
|
||||
return emitter;
|
||||
// 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');
|
||||
}
|
||||
|
||||
// If non interactive or user forced, bypass confirmation
|
||||
if (!config.interactive || force) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Confirm if the user really wants to register
|
||||
return Q.nfcall(logger.prompt.bind(logger), {
|
||||
type: 'confirm',
|
||||
message: 'Registering a package will make it installable via the registry (' +
|
||||
chalk.cyan.underline(config.registry.register) + '), continue?',
|
||||
default: true
|
||||
});
|
||||
})
|
||||
.then(function (result) {
|
||||
// If user response was negative, abort
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register
|
||||
registryClient = repository.getRegistryClient();
|
||||
|
||||
logger.action('register', url, {
|
||||
name: name,
|
||||
url: url
|
||||
});
|
||||
|
||||
return Q.nfcall(registryClient.register.bind(registryClient), name, url);
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------
|
||||
|
||||
register.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]);
|
||||
};
|
||||
module.exports = register;
|
||||
|
||||
@@ -1,49 +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 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);
|
||||
|
||||
if (results.length) {
|
||||
template('search', {results: results})
|
||||
.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
if (name) {
|
||||
return Q.nfcall(registryClient.search.bind(registryClient), name);
|
||||
} else {
|
||||
template('search-empty', {results: results})
|
||||
.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
// 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 = search;
|
||||
|
||||
@@ -1,81 +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 _ = 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 save = require('../util/save');
|
||||
var help = require('./help');
|
||||
var project;
|
||||
|
||||
var shorthand = { 'h': ['--help'], 'S': ['--save'] };
|
||||
var optionTypes = { help: Boolean };
|
||||
options = options || {};
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
module.exports = function (names, options) {
|
||||
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 = [];
|
||||
|
||||
var packages, uninstallables;
|
||||
var emitter = new Emitter;
|
||||
var manager = new Manager;
|
||||
// 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));
|
||||
}
|
||||
});
|
||||
|
||||
if (options.save) save.discard(emitter, manager, names);
|
||||
|
||||
manager.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
manager.on('error', emitter.emit.bind(emitter, 'error'));
|
||||
|
||||
var resolveLocal = function () {
|
||||
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 () {
|
||||
showWarnings();
|
||||
uninstall();
|
||||
function clean(project, names, removed) {
|
||||
removed = removed || {};
|
||||
|
||||
return project.getTree()
|
||||
.spread(function (tree, flattened) {
|
||||
var nodes = [];
|
||||
var dependantsCounter = {};
|
||||
|
||||
// Grab the nodes of each specified name
|
||||
mout.object.forOwn(flattened, function (node) {
|
||||
if (names.indexOf(node.endpoint.name) !== -1) {
|
||||
nodes.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
var showWarnings = function () {
|
||||
packages.forEach(function (pkg) {
|
||||
if (!pkg.json.dependencies) return;
|
||||
// -------------------
|
||||
|
||||
var conflicts = _.intersection(
|
||||
Object.keys(pkg.json.dependencies),
|
||||
_.pluck(uninstallables, 'name')
|
||||
);
|
||||
uninstall.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
conflicts.forEach(function (conflictName) {
|
||||
template('warning-uninstall', {packageName: pkg.name, conflictName: conflictName})
|
||||
.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
});
|
||||
});
|
||||
};
|
||||
var options = cli.readOptions({
|
||||
'save': { type: Boolean, shorthand: 'S' },
|
||||
'save-dev': { type: Boolean, shorthand: 'D' }
|
||||
}, argv);
|
||||
|
||||
var uninstall = function () {
|
||||
async.forEach(uninstallables, function (pkg, next) {
|
||||
pkg.on('uninstall', next).uninstall();
|
||||
}, emitter.emit.bind(emitter, 'end'));
|
||||
};
|
||||
var names = options.argv.remain.slice(1);
|
||||
|
||||
manager.on('resolveLocal', resolveLocal).resolveLocal();
|
||||
delete options.argv;
|
||||
|
||||
return emitter;
|
||||
return [names, options];
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
if (options.help) return help('uninstall');
|
||||
var names = options.argv.remain.slice(1);
|
||||
|
||||
return module.exports(names, options)
|
||||
};
|
||||
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,60 +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 install = require('./install');
|
||||
var help = require('./help');
|
||||
options = options || {};
|
||||
config = defaultConfig(config);
|
||||
project = new Project(config, logger);
|
||||
|
||||
var shorthand = { 'h': ['--help'] };
|
||||
var optionTypes = { help: Boolean };
|
||||
// If names is an empty array, null them
|
||||
if (names && !names.length) {
|
||||
names = null;
|
||||
}
|
||||
|
||||
module.exports = function (argv) {
|
||||
var manager = new Manager;
|
||||
var emitter = new Emitter;
|
||||
return project.update(names, options);
|
||||
}
|
||||
|
||||
manager.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
manager.on('error', emitter.emit.bind(emitter, 'error'));
|
||||
// -------------------
|
||||
|
||||
var installURLS = function (err, urls) {
|
||||
var installEmitter = install(urls);
|
||||
installEmitter.on('data', emitter.emit.bind(emitter, 'data'));
|
||||
installEmitter.on('error', emitter.emit.bind(emitter, 'error'));
|
||||
installEmitter.on('end', emitter.emit.bind(emitter, 'end'));
|
||||
};
|
||||
update.readOptions = function (argv) {
|
||||
var cli = require('../util/cli');
|
||||
|
||||
manager.once('resolveLocal', function () {
|
||||
var packages = {};
|
||||
var options = cli.readOptions({
|
||||
'force-latest': { type: Boolean, shorthand: 'F' },
|
||||
'production': { type: Boolean, shorthand: 'p' }
|
||||
}, argv);
|
||||
|
||||
_.each(manager.dependencies, function (value, name) {
|
||||
packages[name] = value[0];
|
||||
});
|
||||
var names = options.argv.remain.slice(1);
|
||||
|
||||
var urls = async.map(_.values(packages), function (pkg, next) {
|
||||
pkg.once('loadJSON', function () {
|
||||
pkg.once('fetchURL', function (url) {
|
||||
next(null, url + '#~' + pkg.version);
|
||||
}).fetchURL();
|
||||
}).loadJSON();
|
||||
}, installURLS);
|
||||
}).resolveLocal();
|
||||
delete options.argv;
|
||||
|
||||
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);
|
||||
};
|
||||
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,4 +0,0 @@
|
||||
module.exports = {
|
||||
directory: 'components',
|
||||
json: 'component.json'
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
// ==========================================
|
||||
// BOWER: Manager 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
|
||||
// - end: fired when finished installing
|
||||
// ==========================================
|
||||
|
||||
var Package = require('./package');
|
||||
var config = require('./config');
|
||||
var prune = require('../util/prune');
|
||||
var events = require('events');
|
||||
var async = require('async');
|
||||
var path = require('path');
|
||||
var glob = require('glob');
|
||||
var fs = require('fs');
|
||||
|
||||
// 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) {
|
||||
this.dependencies = {};
|
||||
this.cwd = process.cwd();
|
||||
this.endpoints = endpoints || [];
|
||||
};
|
||||
|
||||
Manager.prototype = Object.create(events.EventEmitter.prototype);
|
||||
Manager.prototype.constructor = Manager;
|
||||
|
||||
Manager.prototype.resolve = function () {
|
||||
var resolved = function () {
|
||||
this.prune();
|
||||
this.on('install', this.emit.bind(this, 'resolve'));
|
||||
this.install();
|
||||
}.bind(this);
|
||||
|
||||
this.once('resolveLocal', function () {
|
||||
if (this.endpoints.length) {
|
||||
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);
|
||||
this.dependencies[name] = [];
|
||||
this.dependencies[name].push(new Package(name, dir, this));
|
||||
}.bind(this));
|
||||
this.emit('resolveLocal');
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Manager.prototype.resolveEndpoints = function () {
|
||||
// Iterate through paths
|
||||
// Add to depedencies array
|
||||
// Prune & install
|
||||
|
||||
async.forEach(this.endpoints, function (endpoint, next) {
|
||||
var name = path.basename(endpoint).replace(/(\.git)?(#.*)?$/, '');
|
||||
var pkg = new Package(name, endpoint, this);
|
||||
this.dependencies[name] = this.dependencies[name] || [];
|
||||
this.dependencies[name].push(pkg);
|
||||
pkg.on('resolve', next).resolve();
|
||||
}.bind(this), this.emit.bind(this, 'resolveEndpoints'));
|
||||
};
|
||||
|
||||
Manager.prototype.loadJSON = function () {
|
||||
var json = path.join(this.cwd, config.json);
|
||||
|
||||
fs.exists(json, function (exists) {
|
||||
if (!exists) return this.emit('error', new Error('Could not find local ' + config.json));
|
||||
fs.readFile(json, 'utf8', function (err, json) {
|
||||
if (err) return this.emit('error', err);
|
||||
this.json = JSON.parse(json);
|
||||
this.name = this.json.name;
|
||||
this.version = this.json.version;
|
||||
this.emit('loadJSON');
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Manager.prototype.resolveFromJson = function () {
|
||||
// loadJSON
|
||||
// Resolve dependencies
|
||||
// Add to dependencies array
|
||||
// Prune & install
|
||||
|
||||
this.once('loadJSON', function () {
|
||||
|
||||
if (!this.json.dependencies) return this.emit('error', new Error('Could not find any dependencies'));
|
||||
|
||||
async.forEach(Object.keys(this.json.dependencies), function (name, next) {
|
||||
var endpoint = this.json.dependencies[name];
|
||||
var pkg = new Package(name, endpoint, this);
|
||||
this.dependencies[name] = this.dependencies[name] || [];
|
||||
this.dependencies[name].push(pkg);
|
||||
pkg.on('resolve', next).resolve();
|
||||
}.bind(this), this.emit.bind(this, 'resolveFromJson'));
|
||||
|
||||
}.bind(this)).loadJSON();
|
||||
};
|
||||
|
||||
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 () {
|
||||
try {
|
||||
this.dependencies = prune(this.getDeepDependencies());
|
||||
} catch (err) {
|
||||
this.emit('error', err);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Manager.prototype.install = function () {
|
||||
async.forEach(Object.keys(this.dependencies), function (name, next) {
|
||||
this.dependencies[name][0].once('install', next).install();
|
||||
}.bind(this), this.emit.bind(this, 'install'));
|
||||
return this;
|
||||
};
|
||||
|
||||
module.exports = Manager;
|
||||
@@ -1,425 +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 spawn = require('child_process').spawn;
|
||||
var _ = require('lodash');
|
||||
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 url = require('url');
|
||||
var tmp = require('tmp');
|
||||
var fs = require('fs');
|
||||
|
||||
var config = require('./config');
|
||||
var source = require('./source');
|
||||
var template = require('../util/template');
|
||||
var readJSON = require('../util/read-json');
|
||||
|
||||
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 cache = process.platform === "win32"
|
||||
? path.resolve(process.env.APPDATA || home || temp, "bower-cache")
|
||||
: path.resolve(home || temp, ".bower")
|
||||
|
||||
var Package = function (name, endpoint, manager) {
|
||||
this.dependencies = {};
|
||||
this.json = {};
|
||||
this.name = name;
|
||||
this.manager = manager;
|
||||
|
||||
if (endpoint) {
|
||||
|
||||
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);
|
||||
this.name = this.name.replace(this.assetType, '');
|
||||
|
||||
} else if (/^[\.\/~]/.test(endpoint)) {
|
||||
this.path = path.resolve(endpoint);
|
||||
|
||||
} else if (/^https?:\/\//.exec(endpoint)) {
|
||||
this.assetUrl = endpoint;
|
||||
this.assetType = path.extname(endpoint);
|
||||
this.name = this.name.replace(this.assetType, '');
|
||||
|
||||
} else {
|
||||
this.tag = endpoint.split('#', 2)[1];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (this.manager) {
|
||||
this.on('data', this.manager.emit.bind(this.manager, 'data'));
|
||||
this.on('error', this.manager.emit.bind(this.manager, 'error'));
|
||||
}
|
||||
};
|
||||
|
||||
Package.prototype = Object.create(events.EventEmitter.prototype);
|
||||
|
||||
Package.prototype.constructor = Package;
|
||||
|
||||
Package.prototype.resolve = function () {
|
||||
|
||||
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.gitUrl = url;
|
||||
this.emit('lookup');
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.install = function () {
|
||||
if (path.resolve(this.path) == this.localPath) return this.emit('install');
|
||||
mkdirp(path.dirname(this.localPath), function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
rimraf(this.localPath, function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
return fs.rename(this.path, this.localPath, function (err) {
|
||||
if (!err) return this.cleanUpLocal();
|
||||
fstream.Reader(this.path)
|
||||
.on('error', this.emit.bind(this, 'error'))
|
||||
.on('end', rimraf.bind(this, this.path, this.cleanUpLocal.bind(this)))
|
||||
.pipe(
|
||||
fstream.Writer({
|
||||
type: 'Directory',
|
||||
path: this.localPath
|
||||
})
|
||||
);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
Package.prototype.cleanUpLocal = function () {
|
||||
if (this.gitUrl) this.json.repository = { type: "git", url: this.gitUrl };
|
||||
if (this.assetUrl) this.json = this.generateAssetJSON();
|
||||
fs.writeFile(path.join(this.localPath, config.json), JSON.stringify(this.json, null, 2));
|
||||
rimraf(path.join(this.localPath, '.git'), this.emit.bind(this, 'install'));
|
||||
};
|
||||
Package.prototype.generateAssetJSON = function () {
|
||||
var semverParser = new RegExp('(' + semver.expressions.parse.toString().replace(/\$?\/\^?/g, '') + ')');
|
||||
return {
|
||||
name: this.name,
|
||||
main: 'index' + this.assetType,
|
||||
version: semverParser.exec(this.assetUrl) ? RegExp.$1 : "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.bind(this, 'uninstall');
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
// Private
|
||||
Package.prototype.loadJSON = function (name) {
|
||||
var pathname = name || ( this.assetType ? 'index' + this.assetType : config.json );
|
||||
|
||||
readJSON(path.join(this.path, pathname), function (err, json) {
|
||||
|
||||
if (err) {
|
||||
if (!name) return this.loadJSON('package.json');
|
||||
return this.assetUrl ? this.emit('loadJSON') : this.path && this.on('describeTag', function (tag) {
|
||||
this.version = this.tag = semver.clean(tag);
|
||||
this.emit('loadJSON')
|
||||
}.bind(this)).describeTag();
|
||||
}
|
||||
this.json = json;
|
||||
this.name = this.json.name;
|
||||
this.version = this.json.version;
|
||||
this.emit('loadJSON');
|
||||
}.bind(this), this);
|
||||
}
|
||||
|
||||
Package.prototype.download = function () {
|
||||
template('action', { name: 'downloading', shizzle: this.assetUrl })
|
||||
.on('data', this.emit.bind(this, 'data'));
|
||||
|
||||
var src = url.parse(this.assetUrl);
|
||||
var req = src.protocol === 'https:' ? https : http;
|
||||
|
||||
if (process.env.HTTP_PROXY) {
|
||||
src = url.parse(process.env.HTTP_PROXY);
|
||||
src.path = this.assetUrl;
|
||||
}
|
||||
|
||||
tmp.dir(function (err, tmpPath) {
|
||||
|
||||
var file = fs.createWriteStream(path.join((this.path = tmpPath), 'index' + this.assetType));
|
||||
|
||||
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;
|
||||
this.download();
|
||||
}
|
||||
|
||||
res.on('data', function (data) {
|
||||
file.write(data);
|
||||
});
|
||||
|
||||
res.on('end', function () {
|
||||
file.end();
|
||||
this.once('loadJSON', this.addDependencies).loadJSON();
|
||||
}.bind(this));
|
||||
|
||||
}.bind(this)).on('error', this.emit.bind(this, 'error'));
|
||||
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
Package.prototype.copy = function () {
|
||||
template('action', { name: 'copying', shizzle: this.path }).on('data', this.emit.bind(this, 'data'));
|
||||
|
||||
tmp.dir(function (err, tmpPath) {
|
||||
fs.stat(this.path, function (err, stats) {
|
||||
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.addDependencies).loadJSON();
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
var reader = fstream.Reader(this.path).pipe(
|
||||
fstream.Writer({
|
||||
type: 'Directory',
|
||||
path: (this.path = tmpPath)
|
||||
})
|
||||
);
|
||||
|
||||
this.once('loadJSON', this.addDependencies);
|
||||
|
||||
reader.on('error', this.emit.bind(this, 'error'));
|
||||
reader.on('end', this.loadJSON.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.getDeepDependencies = function (result) {
|
||||
var result = result || [];
|
||||
for (var name in this.dependencies) {
|
||||
result.push(this.dependencies[name])
|
||||
this.dependencies[name].getDeepDependencies(result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
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, this.emit.bind(this, 'resolve'));
|
||||
};
|
||||
|
||||
Package.prototype.exists = function (callback) {
|
||||
fs.exists(this.localPath, callback);
|
||||
};
|
||||
|
||||
Package.prototype.clone = function () {
|
||||
template('action', { name: 'cloning', shizzle: this.gitUrl }).on('data', this.emit.bind(this, 'data'));
|
||||
this.path = path.resolve(cache, this.name);
|
||||
this.once('cache', function () {
|
||||
this.once('loadJSON', this.copy.bind(this)).checkout();
|
||||
}.bind(this)).cache();
|
||||
}
|
||||
|
||||
Package.prototype.cache = function () {
|
||||
mkdirp(cache, function (err) {
|
||||
if (err) return this.emit('error', err);
|
||||
fs.stat(this.path, function (err) {
|
||||
if (!err) {
|
||||
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 (process.env.HTTP_PROXY) {
|
||||
url = url.replace(/^git:/, 'https:');
|
||||
}
|
||||
var cp = spawn('git', ['clone', url, this.path]);
|
||||
cp.stderr.setEncoding('utf8');
|
||||
cp.stderr.on('data', this.emit.bind(this, 'data'));
|
||||
cp.on('close', function (code) {
|
||||
if (code != 0) return this.emit('error', new Error('Git status: ' + code));
|
||||
this.emit('cache');
|
||||
}.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) {
|
||||
versions = versions.filter(function (version) {
|
||||
return semver.satisfies(version, this.tag);
|
||||
}.bind(this));
|
||||
|
||||
if (!versions.length) {
|
||||
return this.emit('error', new Error(
|
||||
'Can not find tag: ' + this.name + '#' + this.tag
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Use latest version
|
||||
this.tag = versions[0];
|
||||
|
||||
if (this.tag) {
|
||||
template('action', {
|
||||
name: 'checking out',
|
||||
shizzle: this.name + '#' + this.tag
|
||||
}).on('data', this.emit.bind(this, 'data'));
|
||||
|
||||
spawn('git', [ 'checkout', '-b', this.tag, this.tag], { cwd: this.path }).on('close', function (code) {
|
||||
if (code == 128) {
|
||||
return spawn('git', [ 'checkout', this.tag], { cwd: this.path }).on('close', function (code) {
|
||||
this.emit('checkout');
|
||||
this.loadJSON();
|
||||
}.bind(this));
|
||||
}
|
||||
if (code != 0) return this.emit('error', new Error('Git status: ' + code));
|
||||
this.emit('checkout');
|
||||
this.loadJSON();
|
||||
}.bind(this));
|
||||
}
|
||||
}).versions();
|
||||
};
|
||||
|
||||
Package.prototype.describeTag = function () {
|
||||
var cp = spawn('git', ['describe', '--always', '--tag'], { cwd: path.resolve(cache, this.name) });
|
||||
|
||||
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
|
||||
else if (code != 0) return this.emit('error', new Error('Git status: ' + code));
|
||||
this.emit('describeTag', tag.replace(/\n$/, ''));
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
Package.prototype.versions = function () {
|
||||
this.on('fetch', function () {
|
||||
var cp = spawn('git', ['tag'], { cwd: path.resolve(cache, this.name) });
|
||||
|
||||
var versions = '';
|
||||
|
||||
cp.stdout.setEncoding('utf8');
|
||||
cp.stdout.on('data', function (data) {
|
||||
versions += data;
|
||||
});
|
||||
|
||||
cp.on('close', function (code) {
|
||||
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;
|
||||
});
|
||||
this.emit('versions', versions);
|
||||
}.bind(this));
|
||||
}.bind(this)).fetch();
|
||||
};
|
||||
|
||||
Package.prototype.fetch = function () {
|
||||
var cp = spawn('git', ['fetch'], { cwd: path.resolve(cache, this.name) });
|
||||
cp.on('close', function (code) {
|
||||
if (code != 0) return this.emit('error', new Error('Git status: ' + code));
|
||||
this.emit('fetch');
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Package.prototype.fetchURL = function () {
|
||||
if (this.json.repository && this.json.repository.type == 'git') {
|
||||
this.emit('fetchURL', this.json.repository.url);
|
||||
} else {
|
||||
this.emit('error', new Error('No git url found for ' + this.json.name));
|
||||
}
|
||||
};
|
||||
|
||||
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,72 +0,0 @@
|
||||
// ==========================================
|
||||
// BOWER: Source Api
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
var request = require('request');
|
||||
var _ = require('lodash');
|
||||
|
||||
var endpoint = 'https://bower.herokuapp.com/packages';
|
||||
|
||||
if (process.env.HTTP_PROXY) {
|
||||
request = request.defaults({'proxy': process.env.HTTP_PROXY});
|
||||
}
|
||||
|
||||
exports.lookup = function (name, callback) {
|
||||
request.get(endpoint + '/' + encodeURIComponent(name), function (err, response, body) {
|
||||
if (err || response.statusCode !== 200) return callback(err || new Error(name + ' not found'));
|
||||
callback(err, body && JSON.parse(body).url);
|
||||
});
|
||||
};
|
||||
|
||||
exports.register = function (name, url, callback) {
|
||||
var body = {name: name, url: url};
|
||||
|
||||
request.post({url: endpoint, form: body}, function (err, response, body) {
|
||||
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) {
|
||||
request.get(endpoint + '/search/' + encodeURIComponent(name), function (err, response, body) {
|
||||
callback(err, body && JSON.parse(body));
|
||||
});
|
||||
};
|
||||
|
||||
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('resolve', function () {
|
||||
pkg.once('versions', function (versions) {
|
||||
callback(null, { pkg: pkg, versions: versions });
|
||||
}).versions();
|
||||
}).resolve();
|
||||
});
|
||||
};
|
||||
|
||||
exports.all = function (callback) {
|
||||
request.get(endpoint, function (err, response, body) {
|
||||
callback(err, body && JSON.parse(body));
|
||||
});
|
||||
};
|
||||
26
lib/index.js
26
lib/index.js
@@ -1,11 +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')
|
||||
};
|
||||
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;
|
||||
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;
|
||||
14
lib/util/createError.js
Normal file
14
lib/util/createError.js
Normal file
@@ -0,0 +1,14 @@
|
||||
var mout = require('mout');
|
||||
|
||||
function createError(msg, code, props) {
|
||||
var err = new Error(msg);
|
||||
err.code = code;
|
||||
|
||||
if (props) {
|
||||
mout.object.mixIn(err, props);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
module.exports = createError;
|
||||
50
lib/util/createLink.js
Normal file
50
lib/util/createLink.js
Normal file
@@ -0,0 +1,50 @@
|
||||
var fs = require('./fs');
|
||||
var path = require('path');
|
||||
var Q = require('q');
|
||||
var mkdirp = require('mkdirp');
|
||||
var createError = require('./createError');
|
||||
|
||||
var isWin = process.platform === 'win32';
|
||||
|
||||
function createLink(src, dst, type) {
|
||||
var dstDir = path.dirname(dst);
|
||||
|
||||
// Create directory
|
||||
return Q.nfcall(mkdirp, dstDir)
|
||||
// Check if source exists
|
||||
.then(function () {
|
||||
return Q.nfcall(fs.stat, src)
|
||||
.fail(function (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
throw createError('Failed to create link to ' + path.basename(src), 'ENOENT', {
|
||||
details: src + ' does not exist or points to a non-existent file'
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
})
|
||||
// Create symlink
|
||||
.then(function (stat) {
|
||||
type = type || (stat.isDirectory() ? 'dir' : 'file');
|
||||
|
||||
return Q.nfcall(fs.symlink, src, dst, type)
|
||||
.fail(function (err) {
|
||||
if (!isWin || err.code !== 'EPERM') {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Try with type "junction" on Windows
|
||||
// Junctions behave equally to true symlinks and can be created in
|
||||
// non elevated terminal (well, not always..)
|
||||
return Q.nfcall(fs.symlink, src, dst, 'junction')
|
||||
.fail(function (err) {
|
||||
throw createError('Unable to create link to ' + path.basename(src), err.code, {
|
||||
details: err.message.trim() + '\n\nTry running this command in an elevated terminal (run as root/administrator).'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = createLink;
|
||||
143
lib/util/download.js
Normal file
143
lib/util/download.js
Normal file
@@ -0,0 +1,143 @@
|
||||
var progress = require('request-progress');
|
||||
var request = require('request');
|
||||
var Q = require('q');
|
||||
var mout = require('mout');
|
||||
var retry = require('retry');
|
||||
var createError = require('./createError');
|
||||
var createWriteStream = require('fs-write-stream-atomic');
|
||||
var destroy = require('destroy');
|
||||
|
||||
var errorCodes = [
|
||||
'EADDRINFO',
|
||||
'ETIMEDOUT',
|
||||
'ECONNRESET',
|
||||
'ESOCKETTIMEDOUT',
|
||||
'ENOTFOUND'
|
||||
];
|
||||
|
||||
function download(url, file, options) {
|
||||
var operation;
|
||||
var deferred = Q.defer();
|
||||
var progressDelay = 8000;
|
||||
|
||||
options = mout.object.mixIn({
|
||||
retries: 5,
|
||||
factor: 2,
|
||||
minTimeout: 1000,
|
||||
maxTimeout: 35000,
|
||||
randomize: true,
|
||||
progressDelay: progressDelay,
|
||||
gzip: true
|
||||
}, options || {});
|
||||
|
||||
// Retry on network errors
|
||||
operation = retry.operation(options);
|
||||
|
||||
operation.attempt(function () {
|
||||
Q.fcall(fetch, url, file, options)
|
||||
.then(function (response) {
|
||||
deferred.resolve(response);
|
||||
})
|
||||
.progress(function (status) {
|
||||
deferred.notify(status);
|
||||
})
|
||||
.fail(function (error) {
|
||||
// Save timeout before retrying to report
|
||||
var timeout = operation._timeouts[0];
|
||||
|
||||
// Reject if error is not a network error
|
||||
if (errorCodes.indexOf(error.code) === -1) {
|
||||
return deferred.reject(error);
|
||||
}
|
||||
|
||||
// Next attempt will start reporting download progress immediately
|
||||
progressDelay = 0;
|
||||
|
||||
// This will schedule next retry or return false
|
||||
if (operation.retry(error)) {
|
||||
deferred.notify({
|
||||
retry: true,
|
||||
delay: timeout,
|
||||
error: error
|
||||
});
|
||||
} else {
|
||||
deferred.reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function fetch(url, file, options) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
var contentLength;
|
||||
var bytesDownloaded = 0;
|
||||
|
||||
var reject = function (error) {
|
||||
deferred.reject(error);
|
||||
};
|
||||
|
||||
var req = progress(request(url, options), {
|
||||
delay: options.progressDelay
|
||||
})
|
||||
.on('response', function (response) {
|
||||
contentLength = Number(response.headers['content-length']);
|
||||
|
||||
var status = response.statusCode;
|
||||
|
||||
if (status < 200 || status >= 300) {
|
||||
return deferred.reject(createError('Status code of ' + status, 'EHTTP'));
|
||||
}
|
||||
|
||||
var writeStream = createWriteStream(file);
|
||||
var errored = false;
|
||||
|
||||
// Change error listener so it cleans up writeStream before exiting
|
||||
req.removeListener('error', reject);
|
||||
req.on('error', function (error) {
|
||||
errored = true;
|
||||
destroy(req);
|
||||
destroy(writeStream);
|
||||
|
||||
// Wait for writeStream to cleanup after itself...
|
||||
// TODO: Maybe there's a better way?
|
||||
setTimeout(function () {
|
||||
deferred.reject(error);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
writeStream.on('finish', function () {
|
||||
if (!errored) {
|
||||
destroy(req);
|
||||
deferred.resolve(response);
|
||||
}
|
||||
});
|
||||
|
||||
req.pipe(writeStream);
|
||||
})
|
||||
.on('data', function (data) {
|
||||
bytesDownloaded += data.length;
|
||||
})
|
||||
.on('progress', function (state) {
|
||||
deferred.notify(state);
|
||||
})
|
||||
.on('error', reject)
|
||||
.on('end', function () {
|
||||
// Check if the whole file was downloaded
|
||||
// In some unstable connections the ACK/FIN packet might be sent in the
|
||||
// middle of the download
|
||||
// See: https://github.com/joyent/node/issues/6143
|
||||
if (contentLength && bytesDownloaded < contentLength) {
|
||||
req.emit('error', createError(
|
||||
'Transfer closed with ' + (contentLength - bytesDownloaded) + ' bytes remaining to read',
|
||||
'EINCOMPLETE'
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
module.exports = download;
|
||||
266
lib/util/extract.js
Normal file
266
lib/util/extract.js
Normal file
@@ -0,0 +1,266 @@
|
||||
var path = require('path');
|
||||
var fs = require('./fs');
|
||||
var zlib = require('zlib');
|
||||
var DecompressZip = require('decompress-zip');
|
||||
var tar = require('tar-fs');
|
||||
var Q = require('q');
|
||||
var mout = require('mout');
|
||||
var junk = require('junk');
|
||||
var createError = require('./createError');
|
||||
var createWriteStream = require('fs-write-stream-atomic');
|
||||
var destroy = require('destroy');
|
||||
var tmp = require('tmp');
|
||||
|
||||
// This forces the default chunk size to something small in an attempt
|
||||
// to avoid issue #314
|
||||
zlib.Z_DEFAULT_CHUNK = 1024 * 8;
|
||||
|
||||
var extractors;
|
||||
var extractorTypes;
|
||||
|
||||
extractors = {
|
||||
'.zip': extractZip,
|
||||
'.tar': extractTar,
|
||||
'.tar.gz': extractTarGz,
|
||||
'.tgz': extractTarGz,
|
||||
'.gz': extractGz,
|
||||
'application/zip': extractZip,
|
||||
'application/x-zip': extractZip,
|
||||
'application/x-zip-compressed': extractZip,
|
||||
'application/x-tar': extractTar,
|
||||
'application/x-tgz': extractTarGz,
|
||||
'application/x-gzip': extractGz
|
||||
};
|
||||
|
||||
extractorTypes = Object.keys(extractors);
|
||||
|
||||
function extractZip(archive, dst) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
new DecompressZip(archive)
|
||||
.on('error', deferred.reject)
|
||||
.on('extract', deferred.resolve.bind(deferred, dst))
|
||||
.extract({
|
||||
path: dst,
|
||||
follow: false, // Do not follow symlinks (#699)
|
||||
filter: filterSymlinks // Filter symlink files
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function extractTar(archive, dst) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
var stream = fs.createReadStream(archive);
|
||||
|
||||
var reject = function (error) {
|
||||
destroy(stream);
|
||||
deferred.reject(error);
|
||||
};
|
||||
|
||||
stream.on('error', reject)
|
||||
.pipe(tar.extract(dst, {
|
||||
ignore: isSymlink, // Filter symlink files
|
||||
dmode: 0555, // Ensure dirs are readable
|
||||
fmode: 0444 // Ensure files are readable
|
||||
}))
|
||||
.on('error', reject)
|
||||
.on('finish', function (result) {
|
||||
destroy(stream);
|
||||
deferred.resolve(dst);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function extractTarGz(archive, dst) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
var stream = fs.createReadStream(archive);
|
||||
|
||||
var reject = function (error) {
|
||||
destroy(stream);
|
||||
deferred.reject(error);
|
||||
};
|
||||
|
||||
stream.on('error', reject)
|
||||
.pipe(zlib.createGunzip())
|
||||
.on('error', reject)
|
||||
.pipe(tar.extract(dst, {
|
||||
ignore: isSymlink, // Filter symlink files
|
||||
dmode: 0555, // Ensure dirs are readable
|
||||
fmode: 0444 // Ensure files are readable
|
||||
}))
|
||||
.on('error', reject)
|
||||
.on('finish', function (result) {
|
||||
destroy(stream);
|
||||
deferred.resolve(dst);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function extractGz(archive, dst) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
var stream = fs.createReadStream(archive);
|
||||
|
||||
var reject = function (error) {
|
||||
destroy(stream);
|
||||
deferred.reject(error);
|
||||
};
|
||||
stream.on('error', reject)
|
||||
.pipe(zlib.createGunzip())
|
||||
.on('error', reject)
|
||||
.pipe(createWriteStream(dst))
|
||||
.on('error', reject)
|
||||
.on('finish', function (result) {
|
||||
destroy(stream);
|
||||
deferred.resolve(dst);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function isSymlink(entry) {
|
||||
return entry.type === 'SymbolicLink';
|
||||
}
|
||||
|
||||
function filterSymlinks(entry) {
|
||||
return entry.type !== 'SymbolicLink';
|
||||
}
|
||||
|
||||
function getExtractor(archive) {
|
||||
// Make the archive lower case to match against the types
|
||||
// This ensures that upper-cased extensions work
|
||||
archive = archive.toLowerCase();
|
||||
|
||||
var type = mout.array.find(extractorTypes, function (type) {
|
||||
return mout.string.endsWith(archive, type);
|
||||
});
|
||||
|
||||
return type ? extractors[type] : null;
|
||||
}
|
||||
|
||||
function isSingleDir(dir) {
|
||||
return Q.nfcall(fs.readdir, dir)
|
||||
.then(function (files) {
|
||||
var singleDir;
|
||||
|
||||
// Remove any OS specific files from the files array
|
||||
// before checking its length
|
||||
files = files.filter(junk.isnt);
|
||||
|
||||
if (files.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
singleDir = path.join(dir, files[0]);
|
||||
|
||||
return Q.nfcall(fs.stat, singleDir)
|
||||
.then(function (stat) {
|
||||
return stat.isDirectory() ? singleDir : false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function moveDirectory(srcDir, destDir) {
|
||||
return Q.nfcall(fs.readdir, srcDir)
|
||||
.then(function (files) {
|
||||
var promises = files.map(function (file) {
|
||||
var src = path.join(srcDir, file);
|
||||
var dst = path.join(destDir, file);
|
||||
|
||||
return Q.nfcall(fs.rename, src, dst);
|
||||
});
|
||||
|
||||
return Q.all(promises);
|
||||
})
|
||||
.then(function () {
|
||||
return Q.nfcall(fs.rmdir, srcDir);
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
|
||||
function canExtract(src, mimeType) {
|
||||
if (mimeType && mimeType !== 'application/octet-stream') {
|
||||
return !!getExtractor(mimeType);
|
||||
}
|
||||
|
||||
return !!getExtractor(src);
|
||||
}
|
||||
|
||||
// Available options:
|
||||
// - keepArchive: true to keep the archive afterwards (defaults to false)
|
||||
// - keepStructure: true to keep the extracted structure unchanged (defaults to false)
|
||||
function extract(src, dst, opts) {
|
||||
var extractor;
|
||||
var promise;
|
||||
|
||||
opts = opts || {};
|
||||
extractor = getExtractor(src);
|
||||
|
||||
// Try to get extractor from mime type
|
||||
if (!extractor && opts.mimeType) {
|
||||
extractor = getExtractor(opts.mimeType);
|
||||
}
|
||||
|
||||
// If extractor is null, then the archive type is unknown
|
||||
if (!extractor) {
|
||||
return Q.reject(createError('File ' + src + ' is not a known archive', 'ENOTARCHIVE'));
|
||||
}
|
||||
|
||||
// Extract to a temporary directory in case of file name clashes
|
||||
return Q.nfcall(tmp.dir, {
|
||||
template: dst + '-XXXXXX',
|
||||
mode: 0777 & ~process.umask()
|
||||
}).then(function (tempDir) {
|
||||
// nfcall may return multiple callback arguments as an array
|
||||
return Array.isArray(tempDir) ? tempDir[0] : tempDir;
|
||||
}).then(function (tempDir) {
|
||||
|
||||
// Check archive file size
|
||||
promise = Q.nfcall(fs.stat, src)
|
||||
.then(function (stat) {
|
||||
if (stat.size <= 8) {
|
||||
throw createError('File ' + src + ' is an invalid archive', 'ENOTARCHIVE');
|
||||
}
|
||||
|
||||
// Extract archive
|
||||
return extractor(src, tempDir);
|
||||
});
|
||||
|
||||
// Remove archive
|
||||
if (!opts.keepArchive) {
|
||||
promise = promise
|
||||
.then(function () {
|
||||
return Q.nfcall(fs.unlink, src);
|
||||
});
|
||||
}
|
||||
|
||||
// Move contents from the temporary directory
|
||||
// If the contents are a single directory (and we're not preserving structure),
|
||||
// move its contents directly instead.
|
||||
promise = promise
|
||||
.then(function () {
|
||||
return isSingleDir(tempDir);
|
||||
})
|
||||
.then(function (singleDir) {
|
||||
if (singleDir && !opts.keepStructure) {
|
||||
return moveDirectory(singleDir, dst);
|
||||
} else {
|
||||
return moveDirectory(tempDir, dst);
|
||||
}
|
||||
});
|
||||
|
||||
// Resolve promise to the dst dir
|
||||
return promise.then(function () {
|
||||
return dst;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = extract;
|
||||
module.exports.canExtract = canExtract;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user