Compare commits
886 Commits
ngcontaine
...
7.1.x
Author | SHA1 | Date | |
---|---|---|---|
912911220a | |||
395f9cd8d2 | |||
94e4589d30 | |||
f43e29b3d5 | |||
9ab35a1165 | |||
d622558458 | |||
26501495fc | |||
ad160257fa | |||
b108e9a036 | |||
be08611d11 | |||
6b5c151b88 | |||
183f27894c | |||
d1a14f6989 | |||
ce4cff60f0 | |||
78bd3c70de | |||
b22405767f | |||
cd1e206cb3 | |||
453589f9fc | |||
fa3af8b1e0 | |||
c220328274 | |||
0d8528b0df | |||
9ac4a4d8a0 | |||
fe8c6b04ef | |||
63d395cb7e | |||
f5e7208bea | |||
b3759fdc90 | |||
ef056c5fb1 | |||
a808c7d150 | |||
89ace1a2d9 | |||
52fd4a2d35 | |||
8c32a2e8bc | |||
3ed1e842d6 | |||
d53c421768 | |||
01102cb235 | |||
97eb85826f | |||
1059789f9f | |||
0dba3ee359 | |||
80818549fe | |||
348c94911c | |||
26c79efa4a | |||
0b8db92a2b | |||
d0e6eeb51e | |||
93078e3709 | |||
29ab72980c | |||
5452889aa6 | |||
590b84fc1d | |||
b6deb00b97 | |||
01114dc10a | |||
7b1f3527be | |||
575ef9f5a4 | |||
11996cd1c4 | |||
8fc763d58f | |||
7a5136bcc1 | |||
a9f39a4fdf | |||
35d0284f45 | |||
74346e987a | |||
f221e9010e | |||
90c273a788 | |||
11d2a8cc81 | |||
ad106abece | |||
84f2928c8c | |||
610472bf3b | |||
7bed896f11 | |||
d7c72fced7 | |||
3ca5de7efd | |||
485e2a147a | |||
b24f1984f3 | |||
f8846ef14e | |||
9a93259b64 | |||
194f1f084f | |||
fba9fa2302 | |||
820e2027e0 | |||
0508143128 | |||
a97fbfd823 | |||
e552cadc90 | |||
35ab809326 | |||
919ef66a7c | |||
a37c65827a | |||
1371cf6d33 | |||
e9655720f8 | |||
8512e34521 | |||
21c9cfff5a | |||
e902f5ee6a | |||
013a241b94 | |||
0d0bc1f725 | |||
a655d288ca | |||
e55088a364 | |||
2e41631026 | |||
2bc85fb77e | |||
8167bfc87c | |||
711ef2ded4 | |||
5c57920dbb | |||
9b4d95974a | |||
8087b6b611 | |||
1ca292392b | |||
079c4b3482 | |||
e476c38dee | |||
8799ea7ea0 | |||
03787924da | |||
1022db1c8c | |||
560cb99f55 | |||
5acfdee3ca | |||
fc2c23ef4b | |||
1ddc34cbc5 | |||
184d63d91d | |||
236ac060b0 | |||
2cce8d4ebc | |||
a149c3a8db | |||
013c0ae2e1 | |||
c8aebaeb76 | |||
56254b53bd | |||
5d0e1147df | |||
a0f239fc71 | |||
88032eb302 | |||
827e223edb | |||
14d75d6971 | |||
8282e15c2b | |||
bdf5f3e499 | |||
a71038b5fa | |||
d735160a11 | |||
498c4c32b1 | |||
b9c7da6503 | |||
bf71b107b3 | |||
0d9b27ff26 | |||
c8c8648abf | |||
8ce59a583b | |||
573fb783e1 | |||
d666370e16 | |||
a72250bace | |||
026b7e34b3 | |||
4390e10dfd | |||
e56c8bf8d1 | |||
ca40565f9a | |||
975c269752 | |||
34306c356e | |||
2a7210e382 | |||
391767fb8f | |||
bf3beb5959 | |||
01917733a1 | |||
859da3af50 | |||
a43998c089 | |||
20729b3378 | |||
cf1ebdc079 | |||
893c1735dd | |||
81e975ad93 | |||
20ea5b5634 | |||
4222b63639 | |||
b5dbf5154e | |||
92e80af875 | |||
a4934a74b6 | |||
91bffa9c53 | |||
60800da6c1 | |||
b07bd30b70 | |||
4f965ad2a1 | |||
848f4148c0 | |||
65943b458f | |||
6574e61062 | |||
ee12e725c0 | |||
3ec7c5081d | |||
e22a302cad | |||
92b05652b2 | |||
4756324b5c | |||
ce5242462b | |||
1c9e526a83 | |||
8a626288a6 | |||
bf6ac6cef8 | |||
3da82338d1 | |||
f8f1168fa6 | |||
a752971bca | |||
f0c4f31771 | |||
a63fd2d0f5 | |||
d97994b27f | |||
c31e78f670 | |||
bc652a2943 | |||
e6e590479e | |||
123da1a8c2 | |||
7695dbd0bd | |||
9741f5b8cf | |||
1b97971eb7 | |||
0ada23a5fb | |||
6b1780d77e | |||
7865abf667 | |||
efa443bba3 | |||
f5a0ec0d7c | |||
5247594e86 | |||
095b6e8113 | |||
8b9249a670 | |||
f80c6008af | |||
b278ea1f09 | |||
1810cdf2c3 | |||
97ef8ae9e7 | |||
99c5db1fb1 | |||
e5c9f7a507 | |||
9729e8f4b9 | |||
78b6f88cb3 | |||
552836ebf0 | |||
2f36a9591d | |||
83c9bff242 | |||
4efb460127 | |||
ac1988d8e6 | |||
edb50a2f51 | |||
c89045fb77 | |||
1258ec041d | |||
499e303ea3 | |||
e618032d53 | |||
b9eeb1c383 | |||
1529ecd8fa | |||
51f26cb4e0 | |||
0d972d9bbf | |||
19546c234e | |||
791d192e63 | |||
15e671ec5a | |||
3567e81175 | |||
ff2ee644b4 | |||
b57ce9299a | |||
9e26216c40 | |||
a4398aa17f | |||
53e4e0c1a3 | |||
5e769d9a25 | |||
931a363612 | |||
3ca1a57f81 | |||
297c54ebb3 | |||
dc2464eaaa | |||
7dbc103cbe | |||
93837e9545 | |||
6c5c97b2f9 | |||
099d1a67a0 | |||
6744b19297 | |||
f9a4abb6dc | |||
4e4bca6bbc | |||
d0037b22ef | |||
d568d7a352 | |||
86d41a1db9 | |||
e6a0c45674 | |||
7d2a746090 | |||
c13f46c7c5 | |||
516af6c531 | |||
9c2d0d0b24 | |||
64647af1a6 | |||
c016066d9b | |||
beabfb7960 | |||
804fb99d66 | |||
387db75003 | |||
c40677a4f5 | |||
d4b46e271a | |||
53bae68617 | |||
952ca59336 | |||
affcbbdd7e | |||
496372dd30 | |||
e9e804fb02 | |||
aed95fd8c7 | |||
aca8ea9c0b | |||
4a01ada291 | |||
10618752e6 | |||
c60418b1f4 | |||
c78c221028 | |||
1aca54da06 | |||
f5d5a3df59 | |||
f017b26e5d | |||
c4ad83e7cd | |||
cf6ea283bb | |||
ea0a99610d | |||
df6a8b28de | |||
3b9bc73db4 | |||
18b6d580c5 | |||
03417df54e | |||
61cd5f7c0f | |||
68dfa04f8a | |||
6902977665 | |||
bc68b592b1 | |||
a64859b4bc | |||
2f30bbb495 | |||
603e7935aa | |||
030d43b9f3 | |||
dff10085e8 | |||
e804143183 | |||
bec4ca0c73 | |||
81acbad058 | |||
360be02c0e | |||
adce5064b0 | |||
1918f8d5b5 | |||
eb5d3088a4 | |||
683d53db4b | |||
a2929dfd57 | |||
911bfef04c | |||
aadbc8a9d3 | |||
2e7b5c5d64 | |||
86580a8460 | |||
8634d0bcd8 | |||
492576114d | |||
ca1e538752 | |||
afbee736ea | |||
84e311038d | |||
4dfa71f018 | |||
4e9f2e5895 | |||
081f95c812 | |||
152ca66eba | |||
17586f1e94 | |||
6712492dc2 | |||
9f4299d47b | |||
ad6771dcd4 | |||
2c25d29b25 | |||
ec29fd3e7b | |||
d042c4afe0 | |||
5d740785a9 | |||
68b2211e64 | |||
332394d87c | |||
62b4ff5250 | |||
634e9e970a | |||
678c28175e | |||
87a7f2dcb9 | |||
8fc4ae51fb | |||
66be3c9f51 | |||
b95089db20 | |||
8a4ec8e565 | |||
95743e3a64 | |||
9ad54d74d2 | |||
0e1cceed50 | |||
23648b052a | |||
91d2368e37 | |||
ede65dbede | |||
578e4c7642 | |||
96770e5c6b | |||
1130e48541 | |||
2a869271f6 | |||
c0bf222a05 | |||
d72d50d83e | |||
ef6f1605db | |||
ea7ec4a6b3 | |||
72f3d99920 | |||
f6991dec3b | |||
0367d14044 | |||
8bf51db3e7 | |||
4f250726aa | |||
86d3099141 | |||
d4c3742e25 | |||
95993e1dd5 | |||
d52d82d744 | |||
d2e6d6978e | |||
da4a28e692 | |||
b876356527 | |||
07509da78d | |||
9ff8155cd9 | |||
56f44be0aa | |||
d50d400231 | |||
19fcfc3d00 | |||
c048358cf9 | |||
8171a2ab94 | |||
2fd4c372d5 | |||
f76ce84ae1 | |||
f3859130f2 | |||
0f274d65c4 | |||
cb71b93351 | |||
4b51d31aea | |||
40bbfbf961 | |||
30f319a11f | |||
5d7ba57dd5 | |||
9cd8327051 | |||
1d8f939c96 | |||
2e28d9f012 | |||
7c730fe5b3 | |||
f233131974 | |||
aefa06f7df | |||
d878f3df93 | |||
f8741c0985 | |||
5e2ce9b2a6 | |||
d725ab5142 | |||
f1a860fbf7 | |||
141f9b2386 | |||
95f852e856 | |||
2c7386c961 | |||
d5cbcef0ea | |||
b0476f308b | |||
9dc52d9d04 | |||
297dc2bc02 | |||
0cc9842bf6 | |||
54ea10288e | |||
1880c9531f | |||
13a803d4f7 | |||
f6c2db818e | |||
67789f10ef | |||
b83f1300bd | |||
6e16a17015 | |||
d744dd70e0 | |||
4e91ead40b | |||
7f39f37003 | |||
bf9ab53f12 | |||
d99f661af7 | |||
5d58a31d86 | |||
07b89902d5 | |||
ce6948fc1b | |||
38d626a3fa | |||
9b8a244a15 | |||
1bbf28ad19 | |||
3b24e0edb6 | |||
b647608c96 | |||
31c462ae3f | |||
da39fd70d2 | |||
ee0b857172 | |||
e0d6782d26 | |||
2f9e957523 | |||
38dbae9ca0 | |||
5a3077f46c | |||
d0cc019c1a | |||
26e8032bd0 | |||
84af7b065d | |||
83dc3c0ee0 | |||
5952775a03 | |||
57531737e5 | |||
3fd50f07fd | |||
4237c34c78 | |||
361eaf1888 | |||
cca89ec36e | |||
e4c7f369f2 | |||
d573a14119 | |||
ff767dd153 | |||
34c6ce6b08 | |||
6737e91974 | |||
1006eab482 | |||
a9f2952882 | |||
100c7eff25 | |||
dc7349915d | |||
213c25fb08 | |||
615a515bba | |||
838a3f204f | |||
15c2467dbd | |||
74ea4e9b5d | |||
4481571999 | |||
2132c8f461 | |||
2b3cac5b31 | |||
8d5e3e6981 | |||
30f1dc002a | |||
5da3b00222 | |||
631998b2df | |||
30d6233e83 | |||
6468711e16 | |||
d698b0eadf | |||
1f3331f5e6 | |||
b6c9678f21 | |||
4c2ce4e8ba | |||
bb0f95b6fb | |||
853faf4c71 | |||
00c7db02d1 | |||
13143b850e | |||
c1724062f1 | |||
7570f7222f | |||
f1e3d5125d | |||
3511f08a81 | |||
982bc7f2aa | |||
3903e5ebe7 | |||
0918adf39d | |||
42c331bbf2 | |||
0bae97a726 | |||
c5949f85ef | |||
24521f549c | |||
4bd9f53e8f | |||
2a78dcbd5a | |||
2ea57cdcc3 | |||
31022cbecf | |||
ce8053103e | |||
0b885ecaf7 | |||
1700bd6f08 | |||
b89bc37170 | |||
39df4dbde7 | |||
716d887e51 | |||
5b4cf38166 | |||
4c0ad5238e | |||
9e8903ada1 | |||
aa55d88408 | |||
07fc4c2464 | |||
b9bd95b3b2 | |||
3f89aeb80a | |||
6c530d3a85 | |||
03bf0d6b41 | |||
d4cee514f6 | |||
331989cea3 | |||
bbf96db2f2 | |||
72c2578d14 | |||
1a666dea61 | |||
7634c1cb31 | |||
95914a0fbf | |||
9c50891d6e | |||
624433c51d | |||
09cc458bfc | |||
efe0c5f371 | |||
d9d226087c | |||
7bad1d356d | |||
0add00a743 | |||
d557f1d9de | |||
665627e254 | |||
a609bf50ed | |||
f8195e5e3d | |||
65b209359a | |||
3a65c9ad4e | |||
77e58c6179 | |||
23d625172a | |||
c3643615fc | |||
638aaecc7d | |||
5a79decba4 | |||
225162aa6c | |||
a43b80a4c9 | |||
beebf7fe14 | |||
0ef1f7ef0d | |||
fc6dad40ac | |||
ecd473bbce | |||
8a3fd58cad | |||
8024857d4c | |||
3fa876c5e7 | |||
516515d9e3 | |||
948f507ba0 | |||
81a8ee1ddb | |||
5e58da14f0 | |||
fa8e633be5 | |||
be337a2e52 | |||
4d164b6ca4 | |||
371ffef4ce | |||
fdfedceeec | |||
9e5d440a0b | |||
0f7d2ca7a8 | |||
81c9720acb | |||
ea2cfbbd2e | |||
2326b9c294 | |||
41de0e0d98 | |||
f6a2dbf4f5 | |||
b115374601 | |||
7d82053117 | |||
1c9b06504b | |||
dd8a85158e | |||
164f79a7b0 | |||
327c614799 | |||
1aa8cfbf74 | |||
95d0626a1e | |||
5183bbffbe | |||
e76a570908 | |||
9afc9a7464 | |||
931e603f80 | |||
45732e5b91 | |||
b2db32b715 | |||
9e32dc7c95 | |||
a19b690338 | |||
bab5b68910 | |||
3daeadd235 | |||
ff15043e48 | |||
ea20ae63d0 | |||
7777a99fe5 | |||
9ebb4c02a2 | |||
7fbeb04b7c | |||
42ee50fc22 | |||
163b7c94a9 | |||
071934e92a | |||
735dfd3b1a | |||
70cd112872 | |||
989555352d | |||
99736750fc | |||
0a3f8173f0 | |||
6a64ac4151 | |||
062fe5c2cf | |||
4b494f23f5 | |||
bd186c7ef9 | |||
1657c997cd | |||
1e69d601fb | |||
34b6d5fff9 | |||
632f66a461 | |||
f7b17a4784 | |||
9562324ea4 | |||
880c0add56 | |||
64c96186da | |||
26209fca49 | |||
7f03528dbc | |||
d17602f31d | |||
7d0e17530b | |||
053bf27fb3 | |||
39f42bad1c | |||
3f8ac238f1 | |||
e0e2038718 | |||
67608a907e | |||
7acdad6921 | |||
7466a99dda | |||
912f3d186f | |||
e26cb21e4e | |||
be4edf15ee | |||
d5e9405d4f | |||
d1b7bb52e7 | |||
1312693f88 | |||
72ff9c880c | |||
3060b3e29b | |||
ee28b64d74 | |||
1246ba53c7 | |||
c29ff722a0 | |||
67ad9468d3 | |||
f922808b8d | |||
67435d456c | |||
fbfce79b93 | |||
2a14dfa4ba | |||
8e71ad6027 | |||
25289664ea | |||
e5644204dc | |||
69b9758ab8 | |||
7ea5161d4d | |||
b0879046b7 | |||
456f23f76a | |||
9623e7c639 | |||
83302d193e | |||
807070fe83 | |||
3ac8a63499 | |||
6a24db2bc6 | |||
50d1cba174 | |||
44c05c05af | |||
7d08722e80 | |||
13cdd13511 | |||
9320ec0f43 | |||
5dd225cb43 | |||
1b1c8ee545 | |||
10e414f617 | |||
b46fa92ae5 | |||
0bdea1f69c | |||
5a31bde649 | |||
decc0b840d | |||
55d54c7e97 | |||
ccceff5ecc | |||
6c6bc95ac0 | |||
c9cfcfa728 | |||
f543d71cc3 | |||
3a5cb1cb11 | |||
ab6f055479 | |||
3683c6a188 | |||
4006c9b6e6 | |||
245b85f72a | |||
bfeed842ee | |||
77942cc690 | |||
7aed64d3a1 | |||
9953fe7c00 | |||
3cce4afa0d | |||
8f25321787 | |||
51dfdd5dd1 | |||
fdaf573073 | |||
35bf95281f | |||
7a78889994 | |||
19c4e705ff | |||
36d6e6076e | |||
868047e87f | |||
5f1273ba2e | |||
355a7cae3c | |||
4c615f7de7 | |||
79466baef8 | |||
b0070dfb9a | |||
9ed4e3df60 | |||
a2da485d90 | |||
9cb17ecc39 | |||
35936864bc | |||
532e53678d | |||
f859d83298 | |||
ac68c75e26 | |||
fe45b9cebd | |||
aaaa34021c | |||
730679964f | |||
cb59d87489 | |||
d216a46412 | |||
a2878b0b1d | |||
5977b72e9c | |||
7373da9b11 | |||
783a682a7d | |||
d22418d417 | |||
4b1fd98093 | |||
935ef13096 | |||
f4b60588fb | |||
15dadb92ef | |||
ce06a75ebf | |||
9889276b15 | |||
d0f7eadc09 | |||
4b132c9848 | |||
46729c76a0 | |||
f22deb2e2d | |||
57de9fc41a | |||
31034f5146 | |||
c5899f4cd4 | |||
ab379ab72a | |||
32e479ffec | |||
391c708d7e | |||
c51331689f | |||
68fadd9b97 | |||
2ad1bb4eb9 | |||
794c3595d4 | |||
b807106f54 | |||
86e6a2099a | |||
9993c72335 | |||
f455518d80 | |||
7cf5807100 | |||
9523991a9b | |||
9acd04c192 | |||
c091d40fb0 | |||
b7baf632c0 | |||
4c0d4fc649 | |||
5b3c08b237 | |||
68f2e0c391 | |||
9c1c945489 | |||
ef5338663d | |||
380b3d7653 | |||
4decc8521d | |||
4d544bcb46 | |||
4c819f79b2 | |||
ac3252a73b | |||
a08af77b70 | |||
aac08e0438 | |||
63b795ae4a | |||
5f6900ecc0 | |||
325e8010e9 | |||
632b19d5c2 | |||
add1198b88 | |||
0ed2df2a36 | |||
bc1f2d6411 | |||
d7326d81ba | |||
c683f74225 | |||
b286abeabe | |||
eeebe28c0f | |||
ffc6e199bf | |||
a01acec7fe | |||
021f4344b1 | |||
f113b49909 | |||
d8d276c245 | |||
e42bd012f9 | |||
6d6b0ff1ad | |||
f378454c92 | |||
c8c8436e58 | |||
b31c8b6063 | |||
897261efdc | |||
35d70ff265 | |||
fc0a7959a4 | |||
182c08bee1 | |||
e2bc0ad6c2 | |||
73333ee3e5 | |||
c819859598 | |||
79aefa7659 | |||
e1990a5a80 | |||
4cff5b2964 | |||
459758231b | |||
f29b218060 | |||
39a67548ac | |||
bc88f318f6 | |||
a5b7008c8e | |||
0aafbac99b | |||
ac5aa8f46d | |||
1fb3c4ffee | |||
3c8aa0b301 | |||
15a2b8f622 | |||
d19108531c | |||
354d1944bb | |||
eaccd03ed7 | |||
343df337f4 | |||
9b14483824 | |||
bd42caf1c7 | |||
7db8111973 | |||
74fef157e6 | |||
9661bed3ba | |||
95168e4de0 | |||
13c3e241c8 | |||
8d098d389a | |||
79a2567aa3 | |||
5649acd03f | |||
ebd01e8e79 | |||
e08955b557 | |||
04dfca41f4 | |||
129f69c3bc | |||
c7e2930f25 | |||
6a62ed2245 | |||
482e12c940 | |||
0c344715e5 | |||
cf095d982d | |||
23ec88ef23 | |||
2bd767c4a6 | |||
1e02cd9961 | |||
bc02e19831 | |||
47eb2122c0 | |||
b8422b41bb | |||
8ac4dd6447 | |||
206ae7a233 | |||
79b6256789 | |||
72dce34f42 | |||
7d39bc68fb | |||
32ad2438ca | |||
fc4b993d98 | |||
ff028f0b39 | |||
fef9cebed0 | |||
c08549ae38 | |||
3808416479 | |||
cf8ad24dcf | |||
cee7448efc | |||
7f1cace2a2 | |||
56c86c7e79 | |||
82a14dc107 | |||
a880686081 | |||
026b60cd70 | |||
cea2e0477c | |||
96f9f03d25 | |||
9931bd7576 | |||
48094835bf | |||
e42c1b0da8 | |||
e7ade38731 | |||
d5f47d6b71 | |||
64aa6701f6 | |||
12ccf57340 | |||
c634176035 | |||
4bb10d224c | |||
5d689469f6 | |||
855ad8804e | |||
29d3f3f6dd | |||
33101359c6 | |||
44eef5c343 | |||
68b7847b4c | |||
2b2e841e5b | |||
549de1e21a | |||
48e73c1558 | |||
41ac58ab7d | |||
f37cf52b4c | |||
927323f24e | |||
b94436d86c | |||
bc5cb8153e | |||
34b848ad51 | |||
d7e5bbf2d0 | |||
a9a81f91cf | |||
07c10e2844 | |||
df5999a739 | |||
3fb0da2de5 | |||
8f0fcc3f71 | |||
ca1e56dc8b | |||
d0e710d472 | |||
bc7f962039 | |||
78d42a9503 | |||
dd5e35ee67 | |||
f91b0455c0 | |||
e8bab1349f | |||
e952c65759 | |||
5241ea086d | |||
cbbad1b791 | |||
96ee898cee | |||
2c40a86b61 | |||
a53a559f5a | |||
6de393b2b8 | |||
56283ed594 | |||
ddd3bf83c7 | |||
9b1bb370a3 | |||
976389836e | |||
f76a9ad156 | |||
6f1100a7e9 | |||
b99d7ed5bf | |||
f47f2628e1 | |||
5653874683 | |||
21e566d9bc | |||
bdbb2f9bfa | |||
2e32d4ee17 | |||
8f81dba367 | |||
aedebaf025 | |||
47f4412650 | |||
a09c3923db | |||
10a656fc38 | |||
8dc2b119fb | |||
f2ba55f2fb | |||
00d3666d95 | |||
21009b06a1 | |||
379e8c5e19 | |||
f3b552f51f | |||
c5b594e351 | |||
86a3be8610 | |||
d5bd86ae5d | |||
a9099e8f70 | |||
96d6b79ada | |||
13ccdfd89d | |||
a0c4b2d8f0 | |||
7ba0cb7c93 | |||
d83f9d432a | |||
e3633888ed | |||
ed266daf2c | |||
89af5291de | |||
91d79939be | |||
96eb79b1c7 | |||
83a1334876 | |||
2a21ca09d2 | |||
ddc13352e9 | |||
62be8c2e2f | |||
d2dfd48be0 | |||
d6cd041cbd | |||
694b8ae779 | |||
152de20774 | |||
34ec9244a6 | |||
268e9772d5 | |||
e2c67ba155 |
3
.bazelignore
Normal file
3
.bazelignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
aio/node_modules
|
@ -1,3 +1,11 @@
|
|||||||
|
# Load any settings specific to the current user
|
||||||
|
try-import .bazelrc.user
|
||||||
|
################################
|
||||||
|
# Settings for Angular team members only
|
||||||
|
################################
|
||||||
|
# To enable this feature check the "Remote caching" section in docs/BAZEL.md.
|
||||||
|
build:angular-team --remote_http_cache=https://storage.googleapis.com/angular-team-cache
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# Typescript / Angular / Sass #
|
# Typescript / Angular / Sass #
|
||||||
###############################
|
###############################
|
||||||
@ -24,12 +32,18 @@ build --symlink_prefix=/
|
|||||||
# Performance: avoid stat'ing input files
|
# Performance: avoid stat'ing input files
|
||||||
build --watchfs
|
build --watchfs
|
||||||
|
|
||||||
|
# Turn off legacy external runfiles
|
||||||
|
run --nolegacy_external_runfiles
|
||||||
|
test --nolegacy_external_runfiles
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# Release support #
|
# Release support #
|
||||||
|
# Turn on these settings with #
|
||||||
|
# --config=release #
|
||||||
###############################
|
###############################
|
||||||
|
|
||||||
# Releases should always be stamped with version control info
|
# Releases should always be stamped with version control info
|
||||||
build --workspace_status_command=./tools/bazel_stamp_vars.sh
|
build:release --workspace_status_command=./tools/bazel_stamp_vars.sh
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# Output #
|
# Output #
|
||||||
@ -57,6 +71,31 @@ test --experimental_ui
|
|||||||
################################
|
################################
|
||||||
# Temporary Settings for Ivy #
|
# Temporary Settings for Ivy #
|
||||||
################################
|
################################
|
||||||
# to determine if the compiler used should be Ivy or ViewEngine one can use `--define=compile=local` on
|
# to determine if the compiler used should be Ivy or ViewEngine one can use `--define=compile=local` on
|
||||||
# any bazel target. This is a temporary flag until codebase is permanently switched to Ivy.
|
# any bazel target. This is a temporary flag until codebase is permanently switched to Ivy.
|
||||||
build --define=compile=legacy
|
build --define=compile=legacy
|
||||||
|
|
||||||
|
###############################
|
||||||
|
# Remote Build Execution support
|
||||||
|
# Turn on these settings with
|
||||||
|
# --config=remote
|
||||||
|
###############################
|
||||||
|
|
||||||
|
# Load default settings for Remote Build Execution
|
||||||
|
import %workspace%/third_party/github.com/bazelbuild/bazel-toolchains/bazelrc/bazel-0.20.0.bazelrc
|
||||||
|
|
||||||
|
# Increase the default number of jobs by 50% because our build has lots of
|
||||||
|
# parallelism
|
||||||
|
build:remote --jobs=150
|
||||||
|
|
||||||
|
# Point to our custom execution platform; see tools/BUILD.bazel
|
||||||
|
build:remote --extra_execution_platforms=//tools:rbe_ubuntu1604-angular
|
||||||
|
build:remote --host_platform=//tools:rbe_ubuntu1604-angular
|
||||||
|
build:remote --platforms=//tools:rbe_ubuntu1604-angular
|
||||||
|
|
||||||
|
# Remote instance.
|
||||||
|
build:remote --remote_instance_name=projects/internal-200822/instances/default_instance
|
||||||
|
|
||||||
|
# Do not accept remote cache.
|
||||||
|
# We need to understand the security risks of using prior build artifacts.
|
||||||
|
build:remote --remote_accept_cached=false
|
42
.buildkite/Dockerfile
Normal file
42
.buildkite/Dockerfile
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Heavily based on https://github.com/StefanScherer/dockerfiles-windows/ images.
|
||||||
|
# Combines the node windowsservercore image with the Bazel Prerequisites (https://docs.bazel.build/versions/master/install-windows.html).
|
||||||
|
# msys install taken from https://github.com/StefanScherer/dockerfiles-windows/issues/30
|
||||||
|
# VS redist install taken from https://github.com/StefanScherer/dockerfiles-windows/blob/master/apache/Dockerfile
|
||||||
|
# The nanoserver image won't work because MSYS2 does not run in it https://github.com/Alexpux/MSYS2-packages/issues/1493
|
||||||
|
|
||||||
|
# Before building this image, you must locally build node-windows:10.13.0-windowsservercore-1803.
|
||||||
|
# Clone https://github.com/StefanScherer/dockerfiles-windows/commit/4ce7101a766b9b880ac262479dd9126b64d656cf and build using
|
||||||
|
# docker build -t node-windows:10.13.0-windowsservercore-1803 --build-arg core=microsoft/windowsservercore:1803 --build-arg target=microsoft/windowsservercore:1803 .
|
||||||
|
FROM node-windows:10.13.0-windowsservercore-1803
|
||||||
|
|
||||||
|
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||||
|
|
||||||
|
# Install 7zip to extract msys2
|
||||||
|
RUN Invoke-WebRequest -UseBasicParsing 'https://www.7-zip.org/a/7z1805-x64.exe' -OutFile 7z.exe
|
||||||
|
# For some reason the last letter in the destination directory is lost. So '/D=C:\\7zip0' will extract to '/D=C:\\7zip'.
|
||||||
|
RUN Start-Process -FilePath 'C:\\7z.exe' -ArgumentList '/S', '/D=C:\\7zip0' -NoNewWindow -Wait
|
||||||
|
|
||||||
|
# Extract msys2
|
||||||
|
RUN Invoke-WebRequest -UseBasicParsing 'http://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20180531.tar.xz' -OutFile msys2.tar.xz
|
||||||
|
RUN Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'e', 'msys2.tar.xz' -Wait
|
||||||
|
RUN Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'x', 'msys2.tar', '-oC:\\' -Wait
|
||||||
|
RUN Remove-Item msys2.tar.xz
|
||||||
|
RUN Remove-Item msys2.tar
|
||||||
|
RUN Remove-Item 7z.exe
|
||||||
|
RUN Remove-Item -Recurse 7zip
|
||||||
|
|
||||||
|
# Add MSYS2 to PATH, and set BAZEL_SH
|
||||||
|
RUN [Environment]::SetEnvironmentVariable('Path', $env:Path + ';C:\msys64\usr\bin', [System.EnvironmentVariableTarget]::Machine)
|
||||||
|
RUN [Environment]::SetEnvironmentVariable('BAZEL_SH', 'C:\msys64\usr\bin\bash.exe', [System.EnvironmentVariableTarget]::Machine)
|
||||||
|
|
||||||
|
# Install Microsoft Visual C++ Redistributable for Visual Studio 2015
|
||||||
|
RUN Invoke-WebRequest -UseBasicParsing 'https://download.microsoft.com/download/9/3/F/93FCF1E7-E6A4-478B-96E7-D4B285925B00/vc_redist.x64.exe' -OutFile vc_redist.x64.exe
|
||||||
|
RUN Start-Process 'c:\\vc_redist.x64.exe' -ArgumentList '/Install', '/Passive', '/NoRestart' -NoNewWindow -Wait
|
||||||
|
RUN Remove-Item vc_redist.x64.exe
|
||||||
|
|
||||||
|
# Add a fix for https://github.com/docker/for-win/issues/2920 as entry point to the container.
|
||||||
|
SHELL ["cmd", "/c"]
|
||||||
|
COPY "fix-msys64.cmd" "C:\\fix-msys64.cmd"
|
||||||
|
ENTRYPOINT cmd /C C:\\fix-msys64.cmd && cmd /c
|
||||||
|
|
||||||
|
CMD ["cmd.exe"]
|
96
.buildkite/README.md
Normal file
96
.buildkite/README.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# BuildKite configuration
|
||||||
|
|
||||||
|
This folder contains configuration for the [BuildKite](https://buildkite.com) based CI checks for
|
||||||
|
this repository.
|
||||||
|
|
||||||
|
BuildKite is a CI provider that provides build coordination and reports while we provide the
|
||||||
|
infrastructure.
|
||||||
|
|
||||||
|
CI runs are triggered by new PRs and will show up on the GitHub checks interface, along with the
|
||||||
|
other current CI solutions.
|
||||||
|
|
||||||
|
Currently it is only used for tests on Windows platforms.
|
||||||
|
|
||||||
|
|
||||||
|
## The build pipeline
|
||||||
|
|
||||||
|
BuildKite uses a pipeline for each repository. The `pipeline.yml` file defines pipeline
|
||||||
|
[build steps](https://buildkite.com/docs/pipelines/defining-steps) for this repository.
|
||||||
|
|
||||||
|
Run results can be seen in the GitHub checks interface and in the
|
||||||
|
[pipeline dashboard](https://buildkite.com/angular/angular).
|
||||||
|
|
||||||
|
Although most configuration is done via `pipeline.yml`, some options are only available
|
||||||
|
in the online [pipeline settings](https://buildkite.com/angular/angular/settings).
|
||||||
|
|
||||||
|
|
||||||
|
## Infrastructure
|
||||||
|
|
||||||
|
BuildKite does not provide the host machines where the builds runs, providing instead the
|
||||||
|
[BuildKite Agent](https://buildkite.com/docs/agent/v3) that should be run our own infrastructure.
|
||||||
|
|
||||||
|
|
||||||
|
### Agents
|
||||||
|
|
||||||
|
This agent polls the BuildKite API for builds, runs them, and reports back the results.
|
||||||
|
Agents are the unit of concurrency: each agent can run one build at any given time.
|
||||||
|
Adding agents allows more builds to be ran at the same time.
|
||||||
|
|
||||||
|
Individual agents can have tags, and pipeline steps can target only agents with certain tags via the
|
||||||
|
`agents` field in `pipeline.yml`.
|
||||||
|
For example: agents on Windows machines are tagged as `windows`, and the Windows specific build
|
||||||
|
steps list `windows: true` in their `agents` field.
|
||||||
|
|
||||||
|
You can see the current agent pool, along with their tags, in the
|
||||||
|
[agents list](https://buildkite.com/organizations/angular/agents).
|
||||||
|
|
||||||
|
|
||||||
|
### Our host machines
|
||||||
|
|
||||||
|
We use [Google Cloud](https://cloud.google.com/) as our cloud provider, under the
|
||||||
|
[Angular project](https://console.cloud.google.com/home/dashboard?project=internal-200822).
|
||||||
|
To access this project you need need to be logged in with a Google account that's a member of
|
||||||
|
team@angular.io.
|
||||||
|
For googlers this may be your google.com account, for others it is an angular.io account.
|
||||||
|
|
||||||
|
In this project we have a number of Windows VMs running, each of them with several agents.
|
||||||
|
The `provision-windows-buildkite.ps1` file contains instructions on how to create new host VMs that
|
||||||
|
are fully configured to run the BuildKite agents as services.
|
||||||
|
|
||||||
|
Our pipeline uses [docker-buildkite-plugin](https://github.com/buildkite-plugins/docker-buildkite-plugin)
|
||||||
|
to run build steps inside docker containers.
|
||||||
|
This way we achieve isolation and hermeticity.
|
||||||
|
|
||||||
|
The `Dockerfile` file describes a custom Docker image that includes NodeJs, Yarn, and the Bazel
|
||||||
|
pre-requisites on Windows.
|
||||||
|
|
||||||
|
To upload a new version of the docker image, follow any build instructions in `Dockerfile` and then
|
||||||
|
run `docker build -t angular/node-bazel-windows:NEW_VERSION`, followed by
|
||||||
|
`docker push angular/node-bazel-windows:NEW_VERSION`.
|
||||||
|
After being pushed it should be available online, and you can use the new version in `pipeline.yml`.
|
||||||
|
|
||||||
|
|
||||||
|
## Caretaker
|
||||||
|
|
||||||
|
BuildKite status can be found at https://www.buildkitestatus.com/.
|
||||||
|
|
||||||
|
Issues related to the BuildKite setup should be escalated to the Tools Team via the current
|
||||||
|
caretaker, followed by Alex Eagle and Filipe Silva.
|
||||||
|
|
||||||
|
Support requests should be submitted via email to support@buildkite.com and cc Igor, Misko, Alex,
|
||||||
|
Jeremy and Manu
|
||||||
|
|
||||||
|
|
||||||
|
## Rollout strategy
|
||||||
|
|
||||||
|
At the moment our BuildKite CI uses 1 host VM running 4 agents, thus being capable of 4 concurrent
|
||||||
|
builds.
|
||||||
|
The only test running is `bazel test //tools/ts-api-guardian:all`, and the PR check is not
|
||||||
|
mandatory.
|
||||||
|
|
||||||
|
In the future we should add cache support to speed up the initial `yarn` install, and also Bazel
|
||||||
|
remote caching to speed up Bazel builds.
|
||||||
|
|
||||||
|
After the current setup is verified as stable and reliable the GitHub PR check can become mandatory.
|
||||||
|
|
||||||
|
The tests ran should also be expanded to cover most, if not all, of the Bazel tests.
|
6
.buildkite/fix-msys64.cmd
Normal file
6
.buildkite/fix-msys64.cmd
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@echo off
|
||||||
|
REM Fix for https://github.com/docker/for-win/issues/2920
|
||||||
|
REM echo "Fixing msys64 folder..."
|
||||||
|
REM Touch all .dll files inside C:\msys64\
|
||||||
|
forfiles /p C:\msys64\ /s /m *.dll /c "cmd /c Copy /B @path+,, >NUL"
|
||||||
|
REM echo "Fixed msys64 folder."
|
10
.buildkite/pipeline.yml
Normal file
10
.buildkite/pipeline.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
steps:
|
||||||
|
- label: windows-test
|
||||||
|
commands:
|
||||||
|
- "yarn install --frozen-lockfile --non-interactive --network-timeout 100000"
|
||||||
|
- "yarn bazel test //tools/ts-api-guardian:all --noshow_progress"
|
||||||
|
plugins:
|
||||||
|
- docker#v2.1.0:
|
||||||
|
image: "filipesilva/node-bazel-windows:0.0.2"
|
||||||
|
agents:
|
||||||
|
windows: true
|
92
.buildkite/provision-windows-buildkite.ps1
Normal file
92
.buildkite/provision-windows-buildkite.ps1
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# PowerShell script to provision a Windows Server with BuildKite
|
||||||
|
# This script follows https://buildkite.com/docs/agent/v3/windows.
|
||||||
|
|
||||||
|
# Instructions
|
||||||
|
|
||||||
|
# VM creation:
|
||||||
|
# In Google Cloud Platform, create a Compute Engine instance.
|
||||||
|
# We recommend machine type n1-highcpu-16 (16 vCPUs, 14.4 GB memory).
|
||||||
|
# Use a windows boot disk with container support such as
|
||||||
|
# "Windows Server version 1803 Datacenter Core for Containers".
|
||||||
|
# Give it a name, then click "Create".
|
||||||
|
|
||||||
|
# VM setup:
|
||||||
|
# In the Compute Engine menu, select "VM Instances". Click on the VM name you chose before.
|
||||||
|
# Click "Set Windows Password" to choose a username and password.
|
||||||
|
# Click RDP to open a remote desktop via browser, using the username and password.
|
||||||
|
# In the Windows command prompt start an elevated powershell by inputing
|
||||||
|
# "powershell -Command "Start-Process PowerShell -Verb RunAs" followed by Enter.
|
||||||
|
# Download and execute this script from GitHub, passing the token (mandatory), tags (optional)
|
||||||
|
# and number of agents (optional) as args:
|
||||||
|
# ```
|
||||||
|
# Invoke-WebRequest -Uri https://raw.githubusercontent.com/angular/angular/master/.buildkite/provision-windows-buildkite.ps1 -OutFile provision.ps1
|
||||||
|
# .\provision.ps1 -token "MY_TOKEN" -tags "windows=true,another_tag=true" -agents 4
|
||||||
|
# ```
|
||||||
|
# The VM should restart and be fully configured.
|
||||||
|
|
||||||
|
# Creating extra VMs
|
||||||
|
# You can create an image of the current VM by following the instructions below.
|
||||||
|
# https://cloud.google.com/compute/docs/instances/windows/creating-windows-os-image
|
||||||
|
# Then create a new VM and choose "Custom images".
|
||||||
|
|
||||||
|
|
||||||
|
# Script proper.
|
||||||
|
|
||||||
|
# Get the token and tags from arguments.
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory=$true)][string]$token,
|
||||||
|
[string]$tags = ""
|
||||||
|
[Int]$agents = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Allow HTTPS
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls"
|
||||||
|
|
||||||
|
# Helper to add to PATH.
|
||||||
|
# Will take current PATH so avoid running it after anything to modifies only the powershell session path.
|
||||||
|
function Add-Path ([string]$newPathItem) {
|
||||||
|
$Env:Path+= ";" + $newPathItem + ";"
|
||||||
|
[Environment]::SetEnvironmentVariable("Path",$env:Path, [System.EnvironmentVariableTarget]::Machine)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install Git for Windows
|
||||||
|
Write-Host "Installing Git for Windows."
|
||||||
|
Invoke-WebRequest -Uri https://github.com/git-for-windows/git/releases/download/v2.19.1.windows.1/Git-2.19.1-64-bit.exe -OutFile git.exe
|
||||||
|
.\git.exe /VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /COMPONENTS="icons,ext\reg\shellhere,assoc,assoc_sh" /DIR="C:\git"
|
||||||
|
Add-Path "C:\git\bin"
|
||||||
|
Remove-Item git.exe
|
||||||
|
|
||||||
|
# Download NSSM (https://nssm.cc/) to run the BuildKite agent as a service.
|
||||||
|
Write-Host "Downloading NSSM."
|
||||||
|
Invoke-WebRequest -Uri https://nssm.cc/ci/nssm-2.24-101-g897c7ad.zip -OutFile nssm.zip
|
||||||
|
Expand-Archive -Path nssm.zip -DestinationPath C:\nssm
|
||||||
|
Add-Path "C:\nssm\nssm-2.24-101-g897c7ad\win64"
|
||||||
|
Remove-Item nssm.zip
|
||||||
|
|
||||||
|
# Run the BuildKite agent install script
|
||||||
|
Write-Host "Installing BuildKite agent."
|
||||||
|
$env:buildkiteAgentToken = $token
|
||||||
|
$env:buildkiteAgentTags = $tags
|
||||||
|
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||||
|
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/buildkite/agent/master/install.ps1'))
|
||||||
|
|
||||||
|
# Configure the BuildKite agent clone and timestamp behavior
|
||||||
|
Add-Content C:\buildkite-agent\buildkite-agent.cfg "`ngit-clone-flags=--config core.autocrlf=input --config core.eol=lf --config core.longpaths=true --config core.symlinks=true`n"
|
||||||
|
Add-Content C:\buildkite-agent\buildkite-agent.cfg "`ntimestamp-lines=true`n"
|
||||||
|
|
||||||
|
# Register the BuildKite agent service using NSSM, so that it persists through restarts and is
|
||||||
|
# restarted if the process dies.
|
||||||
|
for ($i=1; $i -le $agents; $i++)
|
||||||
|
{
|
||||||
|
$agentName = "buildkite-agent-$i"
|
||||||
|
Write-Host "Registering $agentName as a service."
|
||||||
|
nssm.exe install $agentName "C:\buildkite-agent\bin\buildkite-agent.exe" "start"
|
||||||
|
nssm.exe set $agentName AppStdout "C:\buildkite-agent\$agentName.log"
|
||||||
|
nssm.exe set $agentName AppStderr "C:\buildkite-agent\$agentName.log"
|
||||||
|
nssm.exe status $agentName
|
||||||
|
nssm.exe start $agentName
|
||||||
|
nssm.exe status $agentName
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restart the machine.
|
||||||
|
Restart-Computer
|
@ -13,7 +13,7 @@ a GitHub token that enables publishing snapshots.
|
|||||||
|
|
||||||
To create the github_token file, we take this approach:
|
To create the github_token file, we take this approach:
|
||||||
- Find the angular-builds:token in http://valentine
|
- Find the angular-builds:token in http://valentine
|
||||||
- Go inside the ngcontainer docker image so you use the same version of openssl as we will at runtime: `docker run --rm -it angular/ngcontainer`
|
- Go inside the CircleCI default docker image so you use the same version of openssl as we will at runtime: `docker run --rm -it circleci/node:10.12`
|
||||||
- echo "https://[token]:@github.com" > credentials
|
- echo "https://[token]:@github.com" > credentials
|
||||||
- openssl aes-256-cbc -e -in credentials -out .circleci/github_token -k $KEY
|
- openssl aes-256-cbc -e -in credentials -out .circleci/github_token -k $KEY
|
||||||
- If needed, base64-encode the result so you can copy-paste it out of docker: `base64 github_token`
|
- If needed, base64-encode the result so you can copy-paste it out of docker: `base64 github_token`
|
@ -1,6 +1,6 @@
|
|||||||
# These options are enabled when running on CI
|
# These options are enabled when running on CI
|
||||||
# We do this by copying this file to /etc/bazel.bazelrc at the start of the build.
|
# We do this by copying this file to /etc/bazel.bazelrc at the start of the build.
|
||||||
# See remote cache documentation in /docs/BAZEL.md
|
# See documentation in /docs/BAZEL.md
|
||||||
|
|
||||||
# Don't be spammy in the logs
|
# Don't be spammy in the logs
|
||||||
# TODO(gmagolan): Hide progress again once build performance improves
|
# TODO(gmagolan): Hide progress again once build performance improves
|
||||||
@ -8,18 +8,11 @@
|
|||||||
# error: Too long with no output (exceeded 10m0s)
|
# error: Too long with no output (exceeded 10m0s)
|
||||||
# build --noshow_progress
|
# build --noshow_progress
|
||||||
|
|
||||||
# Don't run manual tests
|
|
||||||
test --test_tag_filters=-manual
|
|
||||||
|
|
||||||
# Print all the options that apply to the build.
|
# Print all the options that apply to the build.
|
||||||
# This helps us diagnose which options override others
|
# This helps us diagnose which options override others
|
||||||
# (e.g. /etc/bazel.bazelrc vs. tools/bazel.rc)
|
# (e.g. /etc/bazel.bazelrc vs. tools/bazel.rc)
|
||||||
build --announce_rc
|
build --announce_rc
|
||||||
|
|
||||||
# Create dist/bin symlink to $(bazel info bazel-bin)
|
|
||||||
# We use this when uploading artifacts after the build finishes
|
|
||||||
build --symlink_prefix=dist/
|
|
||||||
|
|
||||||
# Workaround https://github.com/bazelbuild/bazel/issues/3645
|
# Workaround https://github.com/bazelbuild/bazel/issues/3645
|
||||||
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
|
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
|
||||||
# Limit Bazel to consuming resources that fit in CircleCI "xlarge" class
|
# Limit Bazel to consuming resources that fit in CircleCI "xlarge" class
|
||||||
@ -28,3 +21,6 @@ build --local_resources=14336,8.0,1.0
|
|||||||
|
|
||||||
# Retry in the event of flakes, eg. https://circleci.com/gh/angular/angular/31309
|
# Retry in the event of flakes, eg. https://circleci.com/gh/angular/angular/31309
|
||||||
test --flaky_test_attempts=2
|
test --flaky_test_attempts=2
|
||||||
|
|
||||||
|
# More details on failures
|
||||||
|
build --verbose_failures=true
|
||||||
|
@ -7,36 +7,50 @@
|
|||||||
# To validate changes, use an online parser, eg.
|
# To validate changes, use an online parser, eg.
|
||||||
# http://yaml-online-parser.appspot.com/
|
# http://yaml-online-parser.appspot.com/
|
||||||
|
|
||||||
# Variables
|
# Note that the browser docker image comes with Chrome and Firefox preinstalled. This is just
|
||||||
|
# needed for jobs that run tests without Bazel. Bazel runs tests with browsers that will be
|
||||||
## IMPORTANT
|
# fetched by the Webtesting rules. Therefore for jobs that run tests with Bazel, we don't need a
|
||||||
# If you change the `docker_image` version, also change the `cache_key` suffix and the version of
|
# docker image with browsers pre-installed.
|
||||||
# `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file.
|
# **NOTE**: If you change the version of the docker images, also change the `cache_key` suffix.
|
||||||
var_1: &docker_image angular/ngcontainer:0.4.0
|
var_1: &default_docker_image circleci/node:10.12
|
||||||
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-bust1-0.4.0
|
var_2: &browsers_docker_image circleci/node:10.12-browsers
|
||||||
|
var_3: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-node-10.12
|
||||||
|
|
||||||
# Define common ENV vars
|
# Define common ENV vars
|
||||||
var_3: &define_env_vars
|
var_4: &define_env_vars
|
||||||
run: echo "export PROJECT_ROOT=$(pwd)" >> $BASH_ENV
|
|
||||||
|
|
||||||
# See remote cache documentation in /docs/BAZEL.md
|
|
||||||
var_4: &setup-bazel-remote-cache
|
|
||||||
run:
|
run:
|
||||||
name: Start up bazel remote cache proxy
|
name: Define environment variables
|
||||||
command: ~/bazel-remote-proxy -backend circleci://
|
command: ./.circleci/env.sh
|
||||||
background: true
|
|
||||||
|
var_5: &setup_bazel_remote_execution
|
||||||
|
run:
|
||||||
|
name: "Setup bazel RBE remote execution"
|
||||||
|
command: |
|
||||||
|
openssl aes-256-cbc -d -in .circleci/gcp_token -k "$CI_REPO_NAME" -out /home/circleci/.gcp_credentials
|
||||||
|
echo "export GOOGLE_APPLICATION_CREDENTIALS=/home/circleci/.gcp_credentials" >> $BASH_ENV
|
||||||
|
sudo bash -c "echo 'build --config=remote' >> /etc/bazel.bazelrc"
|
||||||
|
|
||||||
# Settings common to each job
|
# Settings common to each job
|
||||||
anchor_1: &job_defaults
|
var_6: &job_defaults
|
||||||
working_directory: ~/ng
|
working_directory: ~/ng
|
||||||
docker:
|
docker:
|
||||||
- image: *docker_image
|
- image: *default_docker_image
|
||||||
|
|
||||||
# After checkout, rebase on top of master.
|
# After checkout, rebase on top of master.
|
||||||
# Similar to travis behavior, but not quite the same.
|
# Similar to travis behavior, but not quite the same.
|
||||||
# See https://discuss.circleci.com/t/1662
|
# See https://discuss.circleci.com/t/1662
|
||||||
anchor_2: &post_checkout
|
var_7: &post_checkout
|
||||||
post: git pull --ff-only origin "refs/pull/${CIRCLE_PULL_REQUEST//*pull\//}/merge"
|
post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
|
||||||
|
|
||||||
|
var_8: &yarn_install
|
||||||
|
run:
|
||||||
|
name: Running Yarn install
|
||||||
|
command: yarn install --frozen-lockfile --non-interactive
|
||||||
|
|
||||||
|
var_9: &setup_circleci_bazel_config
|
||||||
|
run:
|
||||||
|
name: Setting up CircleCI bazel configuration
|
||||||
|
command: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
jobs:
|
jobs:
|
||||||
@ -45,55 +59,69 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
|
|
||||||
# Check BUILD.bazel formatting before we have a node_modules directory
|
|
||||||
# Then we don't need any exclude pattern to avoid checking those files
|
|
||||||
- run: 'buildifier -mode=check $(find . -type f \( -name "*.bzl" -or -name BUILD.bazel -or -name BUILD \)) ||
|
|
||||||
(echo "BUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)'
|
|
||||||
# Run the skylark linter to check our Bazel rules
|
|
||||||
# deprecated-api is disabled because we use actions.new_file(genfiles_dir)
|
|
||||||
# which has no replacement, see https://github.com/bazelbuild/bazel/issues/4858
|
|
||||||
- run: 'find . -type f -name "*.bzl" |
|
|
||||||
xargs java -jar /usr/local/bin/Skylint_deploy.jar --disable-checks=deprecated-api ||
|
|
||||||
(echo -e "\n.bzl files have lint errors. Please run ''yarn skylint''"; exit 1)'
|
|
||||||
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
|
- *define_env_vars
|
||||||
|
- *yarn_install
|
||||||
|
|
||||||
|
- run: 'yarn bazel:format -mode=check ||
|
||||||
|
(echo "BUILD files not formatted. Please run ''yarn bazel:format''" ; exit 1)'
|
||||||
|
# Run the skylark linter to check our Bazel rules
|
||||||
|
- run: 'yarn bazel:lint ||
|
||||||
|
(echo -e "\n.bzl files have lint errors. Please run ''yarn bazel:lint-fix''"; exit 1)'
|
||||||
|
|
||||||
- run: yarn install --frozen-lockfile --non-interactive
|
|
||||||
- run: ./node_modules/.bin/gulp lint
|
- run: ./node_modules/.bin/gulp lint
|
||||||
|
|
||||||
test:
|
test:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
resource_class: xlarge
|
resource_class: xlarge
|
||||||
steps:
|
steps:
|
||||||
- *define_env_vars
|
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
# See remote cache documentation in /docs/BAZEL.md
|
|
||||||
- run: .circleci/setup_cache.sh
|
|
||||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
|
||||||
- *setup-bazel-remote-cache
|
|
||||||
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
|
- *define_env_vars
|
||||||
|
- *setup_circleci_bazel_config
|
||||||
|
- *yarn_install
|
||||||
|
|
||||||
- run: ls /home/circleci/bazel_repository_cache || true
|
# Setup remote execution and run RBE-compatible tests.
|
||||||
- run: bazel info release
|
- *setup_bazel_remote_execution
|
||||||
- run: bazel run @nodejs//:yarn
|
- run: yarn bazel test //... --build_tag_filters=-ivy-only --test_tag_filters=-ivy-only,-local
|
||||||
# Use bazel query so that we explicitly ask for all buildable targets to be built as well
|
# Now run RBE incompatible tests locally.
|
||||||
# This avoids waiting for the slowest build target to finish before running the first test
|
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
- run: yarn bazel test //... --build_tag_filters=-ivy-only,local --test_tag_filters=-ivy-only,local
|
||||||
# NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/...
|
|
||||||
- run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only --test_tag_filters=-manual,-ivy-only
|
|
||||||
|
|
||||||
# CircleCI will allow us to go back and view/download these artifacts from past builds.
|
- save_cache:
|
||||||
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
|
key: *cache_key
|
||||||
# The destination keys need be format {projectName}/{context}/{fileName} so that the github-robot can process them for size calculations
|
paths:
|
||||||
# projectName should remain consistant to group files
|
- "node_modules"
|
||||||
# context and fileName can be almost anything (within usual URI rules)
|
- "~/bazel_repository_cache"
|
||||||
# There should only be exactly 2 forward slashes in the path
|
|
||||||
# This is so they're backwards compatiable with the existing data we have on bundle sizes
|
# Temporary job to test what will happen when we flip the Ivy flag to true
|
||||||
|
test_ivy_aot:
|
||||||
|
<<: *job_defaults
|
||||||
|
resource_class: xlarge
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
<<: *post_checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: *cache_key
|
||||||
|
- *define_env_vars
|
||||||
|
- *setup_circleci_bazel_config
|
||||||
|
- *yarn_install
|
||||||
|
- *setup_bazel_remote_execution
|
||||||
|
|
||||||
|
# We need to explicitly specify the --symlink_prefix option because otherwise we would
|
||||||
|
# not be able to easily find the output bin directory when uploading artifacts for size
|
||||||
|
# measurements.
|
||||||
|
- run: yarn test-ivy-aot //... --symlink_prefix=dist/
|
||||||
|
|
||||||
|
# Publish bundle artifacts which will be used to calculate the size change. **Note**: Make
|
||||||
|
# sure that the size plugin from the Angular robot fetches the artifacts from this CircleCI
|
||||||
|
# job (see .github/angular-robot.yml). Additionally any artifacts need to be stored with the
|
||||||
|
# following path format: "{projectName}/{context}/{fileName}". This format is necessary
|
||||||
|
# because otherwise the bot is not able to pick up the artifacts from CircleCI. See:
|
||||||
|
# https://github.com/angular/github-robot/blob/master/functions/src/plugins/size.ts#L392-L394
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js
|
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js
|
||||||
destination: core/hello_world/bundle
|
destination: core/hello_world/bundle
|
||||||
@ -106,48 +134,131 @@ jobs:
|
|||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
|
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
|
||||||
destination: core/todo/bundle.br
|
destination: core/todo/bundle.br
|
||||||
- save_cache:
|
|
||||||
key: *cache_key
|
test_aio:
|
||||||
paths:
|
|
||||||
- "node_modules"
|
|
||||||
- "~/bazel_repository_cache"
|
|
||||||
# Temporary job to test what will happen when we flip the Ivy flag to true
|
|
||||||
test_ivy_jit:
|
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
resource_class: xlarge
|
docker:
|
||||||
|
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
|
||||||
|
- image: *browsers_docker_image
|
||||||
steps:
|
steps:
|
||||||
- *define_env_vars
|
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
# See remote cache documentation in /docs/BAZEL.md
|
|
||||||
- run: .circleci/setup_cache.sh
|
|
||||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
|
||||||
- *setup-bazel-remote-cache
|
|
||||||
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
|
|
||||||
- run: bazel run @yarn//:yarn
|
|
||||||
- run: bazel query --output=label //... | xargs bazel test --define=compile=jit --build_tag_filters=ivy-jit --test_tag_filters=-manual,ivy-jit
|
|
||||||
|
|
||||||
test_ivy_aot:
|
|
||||||
<<: *job_defaults
|
|
||||||
resource_class: xlarge
|
|
||||||
steps:
|
|
||||||
- *define_env_vars
|
- *define_env_vars
|
||||||
|
# Build aio
|
||||||
|
- run: yarn --cwd aio build --progress=false
|
||||||
|
# Lint the code
|
||||||
|
- run: yarn --cwd aio lint
|
||||||
|
# Run PWA-score tests
|
||||||
|
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
||||||
|
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||||
|
# Check the bundle sizes.
|
||||||
|
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
||||||
|
- run: yarn --cwd aio payload-size
|
||||||
|
# Run unit tests
|
||||||
|
- run: yarn --cwd aio test --watch=false
|
||||||
|
# Run e2e tests
|
||||||
|
- run: yarn --cwd aio e2e
|
||||||
|
# Run unit tests for Firebase redirects
|
||||||
|
- run: yarn --cwd aio redirects-test
|
||||||
|
|
||||||
|
deploy_aio:
|
||||||
|
<<: *job_defaults
|
||||||
|
docker:
|
||||||
|
# Needed because before deploying the deploy-production script runs the PWA score tests.
|
||||||
|
- image: *browsers_docker_image
|
||||||
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
# See remote cache documentation in /docs/BAZEL.md
|
|
||||||
- run: .circleci/setup_cache.sh
|
|
||||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
|
||||||
- *setup-bazel-remote-cache
|
|
||||||
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
|
- *define_env_vars
|
||||||
|
# Deploy angular.io to production (if necessary)
|
||||||
|
- run: setPublicVar CI_STABLE_BRANCH "$(npm info @angular/core dist-tags.latest | sed -r 's/^\s*([0-9]+\.[0-9]+)\.[0-9]+.*$/\1.x/')"
|
||||||
|
- run: yarn --cwd aio deploy-production
|
||||||
|
|
||||||
- run: bazel run @yarn//:yarn
|
test_aio_local:
|
||||||
- run: bazel query --output=label //... | xargs bazel test --define=compile=local --build_tag_filters=ivy-local --test_tag_filters=-manual,ivy-local
|
<<: *job_defaults
|
||||||
|
docker:
|
||||||
|
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
|
||||||
|
- image: *browsers_docker_image
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
<<: *post_checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: *cache_key
|
||||||
|
- attach_workspace:
|
||||||
|
at: dist
|
||||||
|
- *define_env_vars
|
||||||
|
# Build aio (with local Angular packages)
|
||||||
|
- run: yarn --cwd aio build-local --progress=false
|
||||||
|
# Run PWA-score tests
|
||||||
|
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
||||||
|
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||||
|
# Run unit tests
|
||||||
|
- run: yarn --cwd aio test --watch=false
|
||||||
|
# Run e2e tests
|
||||||
|
- run: yarn --cwd aio e2e
|
||||||
|
|
||||||
|
test_aio_tools:
|
||||||
|
<<: *job_defaults
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
<<: *post_checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: *cache_key
|
||||||
|
- attach_workspace:
|
||||||
|
at: dist
|
||||||
|
- *define_env_vars
|
||||||
|
# Install
|
||||||
|
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
||||||
|
- run: yarn --cwd aio extract-cli-command-docs
|
||||||
|
# Run tools tests
|
||||||
|
- run: yarn --cwd aio tools-test
|
||||||
|
- run: ./aio/aio-builds-setup/scripts/test.sh
|
||||||
|
|
||||||
|
test_docs_examples_0:
|
||||||
|
<<: *job_defaults
|
||||||
|
docker:
|
||||||
|
# Needed because the example e2e tests depend on Chrome.
|
||||||
|
- image: *browsers_docker_image
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
<<: *post_checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: *cache_key
|
||||||
|
- attach_workspace:
|
||||||
|
at: dist
|
||||||
|
- *define_env_vars
|
||||||
|
# Install root
|
||||||
|
- *yarn_install
|
||||||
|
# Install aio
|
||||||
|
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
||||||
|
# Run examples tests
|
||||||
|
- run: yarn --cwd aio example-e2e --setup --local --shard=0/2
|
||||||
|
|
||||||
|
test_docs_examples_1:
|
||||||
|
<<: *job_defaults
|
||||||
|
docker:
|
||||||
|
# Needed because the example e2e tests depend on Chrome.
|
||||||
|
- image: *browsers_docker_image
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
<<: *post_checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: *cache_key
|
||||||
|
- attach_workspace:
|
||||||
|
at: dist
|
||||||
|
- *define_env_vars
|
||||||
|
# Install root
|
||||||
|
- *yarn_install
|
||||||
|
# Install aio
|
||||||
|
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
||||||
|
# Run examples tests
|
||||||
|
- run: yarn --cwd aio example-e2e --setup --local --shard=1/2
|
||||||
|
|
||||||
|
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
|
||||||
aio_preview:
|
aio_preview:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
environment:
|
environment:
|
||||||
@ -157,13 +268,32 @@ jobs:
|
|||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
- run: yarn install --frozen-lockfile --non-interactive
|
- *define_env_vars
|
||||||
- run: ./aio/scripts/build-artifacts.sh $AIO_SNAPSHOT_ARTIFACT_PATH
|
- *yarn_install
|
||||||
|
- run: ./aio/scripts/build-artifacts.sh $AIO_SNAPSHOT_ARTIFACT_PATH $CI_PULL_REQUEST $CI_COMMIT
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: *aio_preview_artifact_path
|
path: *aio_preview_artifact_path
|
||||||
# The `destination` needs to be kept in synch with the value of
|
# The `destination` needs to be kept in synch with the value of
|
||||||
# `AIO_ARTIFACT_PATH` in `aio/aio-builds-setup/Dockerfile`
|
# `AIO_ARTIFACT_PATH` in `aio/aio-builds-setup/Dockerfile`
|
||||||
destination: aio/dist/aio-snapshot.tgz
|
destination: aio/dist/aio-snapshot.tgz
|
||||||
|
- run: node ./aio/scripts/create-preview $CIRCLE_BUILD_NUM
|
||||||
|
|
||||||
|
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
|
||||||
|
test_aio_preview:
|
||||||
|
<<: *job_defaults
|
||||||
|
docker:
|
||||||
|
# Needed because the test-preview script runs e2e tests and the PWA score test with Chrome.
|
||||||
|
- image: *browsers_docker_image
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
<<: *post_checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: *cache_key
|
||||||
|
- *define_env_vars
|
||||||
|
- run: yarn install --cwd aio --frozen-lockfile --non-interactive
|
||||||
|
- run:
|
||||||
|
name: Wait for preview and run tests
|
||||||
|
command: node aio/scripts/test-preview.js $CI_PULL_REQUEST $CI_COMMIT $CI_AIO_MIN_PWA_SCORE
|
||||||
|
|
||||||
# This job exists only for backwards-compatibility with old scripts and tests
|
# This job exists only for backwards-compatibility with old scripts and tests
|
||||||
# that rely on the pre-Bazel dist/packages-dist layout.
|
# that rely on the pre-Bazel dist/packages-dist layout.
|
||||||
@ -176,24 +306,16 @@ jobs:
|
|||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
resource_class: xlarge
|
resource_class: xlarge
|
||||||
steps:
|
steps:
|
||||||
- *define_env_vars
|
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
# See remote cache documentation in /docs/BAZEL.md
|
- restore_cache:
|
||||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
key: *cache_key
|
||||||
- run: bazel run @nodejs//:yarn
|
- *define_env_vars
|
||||||
- run:
|
- *setup_circleci_bazel_config
|
||||||
# RBE is enabled by appending rbe-bazel.rc.
|
- *yarn_install
|
||||||
name: Enable RBE
|
- *setup_bazel_remote_execution
|
||||||
command: 'sudo bash -c "cat .circleci/rbe-bazel.rc >> /etc/bazel.bazelrc"'
|
|
||||||
- run:
|
- run: scripts/build-packages-dist.sh
|
||||||
name: "Setup GCP environment"
|
|
||||||
command: 'openssl aes-256-cbc -d -in .circleci/gcp_token -k "${CIRCLE_PROJECT_REPONAME}" -out /home/circleci/.gcp_credentials'
|
|
||||||
- run:
|
|
||||||
name: build-packages-dist
|
|
||||||
command: scripts/build-packages-dist.sh
|
|
||||||
environment:
|
|
||||||
GOOGLE_APPLICATION_CREDENTIALS: /home/circleci/.gcp_credentials
|
|
||||||
|
|
||||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||||
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
|
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
|
||||||
@ -202,7 +324,7 @@ jobs:
|
|||||||
paths:
|
paths:
|
||||||
- packages-dist
|
- packages-dist
|
||||||
- packages-dist-ivy-jit
|
- packages-dist-ivy-jit
|
||||||
- packages-dist-ivy-local
|
- packages-dist-ivy-aot
|
||||||
|
|
||||||
# We run the integration tests outside of Bazel for now.
|
# We run the integration tests outside of Bazel for now.
|
||||||
# They are a separate workflow job so that they can be easily re-run.
|
# They are a separate workflow job so that they can be easily re-run.
|
||||||
@ -212,35 +334,41 @@ jobs:
|
|||||||
# See comments inside the integration/run_tests.sh script.
|
# See comments inside the integration/run_tests.sh script.
|
||||||
integration_test:
|
integration_test:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
|
docker:
|
||||||
|
# Needed because the integration tests expect Chrome to be installed (e.g cli-hello-world)
|
||||||
|
- image: *browsers_docker_image
|
||||||
# Note: we run Bazel in one of the integration tests, and it can consume >2G
|
# Note: we run Bazel in one of the integration tests, and it can consume >2G
|
||||||
# of memory. Together with the system under test, this can exhaust the RAM
|
# of memory. Together with the system under test, this can exhaust the RAM
|
||||||
# on a 4G worker so we use a larger machine here too.
|
# on a 4G worker so we use a larger machine here too.
|
||||||
resource_class: xlarge
|
resource_class: xlarge
|
||||||
steps:
|
steps:
|
||||||
- *define_env_vars
|
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: *cache_key
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: dist
|
at: dist
|
||||||
- run: xvfb-run --auto-servernum ./integration/run_tests.sh
|
- *define_env_vars
|
||||||
|
- run: ./integration/run_tests.sh
|
||||||
|
|
||||||
# This job updates the content of repos like github.com/angular/core-builds
|
# This job updates the content of repos like github.com/angular/core-builds
|
||||||
# for every green build on angular/angular.
|
# for every green build on angular/angular.
|
||||||
publish_snapshot:
|
publish_snapshot:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
steps:
|
steps:
|
||||||
|
- checkout:
|
||||||
|
<<: *post_checkout
|
||||||
|
- *define_env_vars
|
||||||
# See below - ideally this job should not trigger for non-upstream builds.
|
# See below - ideally this job should not trigger for non-upstream builds.
|
||||||
# But since it does, we have to check this condition.
|
# But since it does, we have to check this condition.
|
||||||
- run:
|
- run:
|
||||||
name: Skip this job for Pull Requests and Fork builds
|
name: Skip this job for Pull Requests and Fork builds
|
||||||
# Note, `|| true` on the end makes this step always exit 0
|
# Note, `|| true` on the end makes this step always exit 0
|
||||||
command: '[[
|
command: '[[
|
||||||
-v CIRCLE_PR_NUMBER
|
"$CI_PULL_REQUEST" != "false"
|
||||||
|| "$CIRCLE_PROJECT_USERNAME" != "angular"
|
|| "$CI_REPO_OWNER" != "angular"
|
||||||
|| "$CIRCLE_PROJECT_REPONAME" != "angular"
|
|| "$CI_REPO_NAME" != "angular"
|
||||||
]] && circleci step halt || true'
|
]] && circleci step halt || true'
|
||||||
- checkout:
|
|
||||||
<<: *post_checkout
|
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: dist
|
at: dist
|
||||||
# CircleCI has a config setting to force SSH for all github connections
|
# CircleCI has a config setting to force SSH for all github connections
|
||||||
@ -254,12 +382,23 @@ jobs:
|
|||||||
|
|
||||||
aio_monitoring:
|
aio_monitoring:
|
||||||
<<: *job_defaults
|
<<: *job_defaults
|
||||||
|
docker:
|
||||||
|
# This job needs Chrome to be globally installed because the tests run with Protractor
|
||||||
|
# which does not load the browser through the Bazel webtesting rules.
|
||||||
|
- image: *browsers_docker_image
|
||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
<<: *post_checkout
|
<<: *post_checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: *cache_key
|
key: *cache_key
|
||||||
- run: xvfb-run --auto-servernum ./aio/scripts/test-production.sh
|
- *define_env_vars
|
||||||
|
- run:
|
||||||
|
name: Run tests against the deployed apps
|
||||||
|
command: ./aio/scripts/test-production.sh $CI_AIO_MIN_PWA_SCORE
|
||||||
|
- run:
|
||||||
|
name: Notify caretaker about failure
|
||||||
|
command: 'curl --request POST --header "Content-Type: application/json" --data "{\"text\":\":x: \`$CIRCLE_JOB\` job failed on build $CIRCLE_BUILD_NUM: $CIRCLE_BUILD_URL :scream:\"}" $CI_SECRET_SLACK_CARETAKER_WEBHOOK_URL'
|
||||||
|
when: on_fail
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
@ -267,10 +406,32 @@ workflows:
|
|||||||
jobs:
|
jobs:
|
||||||
- lint
|
- lint
|
||||||
- test
|
- test
|
||||||
- test_ivy_jit
|
|
||||||
- test_ivy_aot
|
- test_ivy_aot
|
||||||
- build-packages-dist
|
- build-packages-dist
|
||||||
- aio_preview
|
- test_aio
|
||||||
|
- deploy_aio:
|
||||||
|
requires:
|
||||||
|
- test_aio
|
||||||
|
- test_aio_local:
|
||||||
|
requires:
|
||||||
|
- build-packages-dist
|
||||||
|
- test_aio_tools:
|
||||||
|
requires:
|
||||||
|
- build-packages-dist
|
||||||
|
- test_docs_examples_0:
|
||||||
|
requires:
|
||||||
|
- build-packages-dist
|
||||||
|
- test_docs_examples_1:
|
||||||
|
requires:
|
||||||
|
- build-packages-dist
|
||||||
|
- aio_preview:
|
||||||
|
# Only run on PR builds. (There can be no previews for non-PR builds.)
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: /pull\/\d+/
|
||||||
|
- test_aio_preview:
|
||||||
|
requires:
|
||||||
|
- aio_preview
|
||||||
- integration_test:
|
- integration_test:
|
||||||
requires:
|
requires:
|
||||||
- build-packages-dist
|
- build-packages-dist
|
||||||
@ -282,9 +443,12 @@ workflows:
|
|||||||
requires:
|
requires:
|
||||||
# Only publish if tests and integration tests pass
|
# Only publish if tests and integration tests pass
|
||||||
- test
|
- test
|
||||||
- test_ivy_jit
|
|
||||||
- test_ivy_aot
|
- test_ivy_aot
|
||||||
- integration_test
|
- integration_test
|
||||||
|
# Only publish if `aio`/`docs` tests using the locally built Angular packages pass
|
||||||
|
- test_aio_local
|
||||||
|
- test_docs_examples_0
|
||||||
|
- test_docs_examples_1
|
||||||
# Get the artifacts to publish from the build-packages-dist job
|
# Get the artifacts to publish from the build-packages-dist job
|
||||||
# since the publishing script expects the legacy outputs layout.
|
# since the publishing script expects the legacy outputs layout.
|
||||||
- build-packages-dist
|
- build-packages-dist
|
||||||
@ -299,6 +463,3 @@ workflows:
|
|||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
notify:
|
|
||||||
webhooks:
|
|
||||||
- url: https://ngbuilds.io/circle-build
|
|
||||||
|
38
.circleci/env-helpers.inc.sh
Normal file
38
.circleci/env-helpers.inc.sh
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
####################################################################################################
|
||||||
|
# Helpers for defining environment variables for CircleCI.
|
||||||
|
#
|
||||||
|
# In CircleCI, each step runs in a new shell. The way to share ENV variables across steps is to
|
||||||
|
# export them from `$BASH_ENV`, which is automatically sourced at the beginning of every step (for
|
||||||
|
# the default `bash` shell).
|
||||||
|
#
|
||||||
|
# See also https://circleci.com/docs/2.0/env-vars/#using-bash_env-to-set-environment-variables.
|
||||||
|
####################################################################################################
|
||||||
|
|
||||||
|
# Set and print an environment variable.
|
||||||
|
#
|
||||||
|
# Use this function for setting environment variables that are public, i.e. it is OK for them to be
|
||||||
|
# visible to anyone through the CI logs.
|
||||||
|
#
|
||||||
|
# Usage: `setPublicVar <name> <value>`
|
||||||
|
function setPublicVar() {
|
||||||
|
setSecretVar $1 $2;
|
||||||
|
echo "$1=$2";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set (without printing) an environment variable.
|
||||||
|
#
|
||||||
|
# Use this function for setting environment variables that are secret, i.e. should not be visible to
|
||||||
|
# everyone through the CI logs.
|
||||||
|
#
|
||||||
|
# Usage: `setSecretVar <name> <value>`
|
||||||
|
function setSecretVar() {
|
||||||
|
# WARNING: Secrets (e.g. passwords, access tokens) should NOT be printed.
|
||||||
|
# (Keep original shell options to restore at the end.)
|
||||||
|
local -r originalShellOptions=$(set +o);
|
||||||
|
set +x -eu -o pipefail;
|
||||||
|
|
||||||
|
echo "export $1=\"${2:-}\";" >> $BASH_ENV;
|
||||||
|
|
||||||
|
# Restore original shell options.
|
||||||
|
eval "$originalShellOptions";
|
||||||
|
}
|
35
.circleci/env.sh
Executable file
35
.circleci/env.sh
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Load helpers and make them available everywhere (through `$BASH_ENV`).
|
||||||
|
readonly envHelpersPath="`dirname $0`/env-helpers.inc.sh";
|
||||||
|
source $envHelpersPath;
|
||||||
|
echo "source $envHelpersPath;" >> $BASH_ENV;
|
||||||
|
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
# Define PUBLIC environment variables for CircleCI.
|
||||||
|
####################################################################################################
|
||||||
|
setPublicVar PROJECT_ROOT "$(pwd)";
|
||||||
|
setPublicVar CI_AIO_MIN_PWA_SCORE "95";
|
||||||
|
# This is the branch being built; e.g. `pull/12345` for PR builds.
|
||||||
|
setPublicVar CI_BRANCH "$CIRCLE_BRANCH";
|
||||||
|
setPublicVar CI_COMMIT "$CIRCLE_SHA1";
|
||||||
|
# `CI_COMMIT_RANGE` will only be available when `CIRCLE_COMPARE_URL` is also available,
|
||||||
|
# i.e. on push builds (a.k.a. non-PR builds). That is fine, since we only need it in push builds.
|
||||||
|
setPublicVar CI_COMMIT_RANGE "$(sed -r 's|^.*/([0-9a-f]+\.\.\.[0-9a-f]+)$|\1|i' <<< ${CIRCLE_COMPARE_URL:-})";
|
||||||
|
setPublicVar CI_PULL_REQUEST "${CIRCLE_PR_NUMBER:-false}";
|
||||||
|
setPublicVar CI_REPO_NAME "$CIRCLE_PROJECT_REPONAME";
|
||||||
|
setPublicVar CI_REPO_OWNER "$CIRCLE_PROJECT_USERNAME";
|
||||||
|
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
# Define SECRET environment variables for CircleCI.
|
||||||
|
####################################################################################################
|
||||||
|
setSecretVar CI_SECRET_AIO_DEPLOY_FIREBASE_TOKEN "$AIO_DEPLOY_TOKEN";
|
||||||
|
setSecretVar CI_SECRET_PAYLOAD_FIREBASE_TOKEN "$ANGULAR_PAYLOAD_TOKEN";
|
||||||
|
# Defined in https://angular-team.slack.com/apps/A0F7VRE7N-circleci.
|
||||||
|
setSecretVar CI_SECRET_SLACK_CARETAKER_WEBHOOK_URL "$SLACK_CARETAKER_WEBHOOK_URL";
|
||||||
|
|
||||||
|
|
||||||
|
# Source `$BASH_ENV` to make the variables available immediately.
|
||||||
|
source $BASH_ENV;
|
Binary file not shown.
@ -1,77 +0,0 @@
|
|||||||
# These options are enabled when running on CI with Remote Build Execution.
|
|
||||||
|
|
||||||
################################################################
|
|
||||||
# Toolchain related flags for remote build execution. #
|
|
||||||
################################################################
|
|
||||||
# Remote Build Execution requires a strong hash function, such as SHA256.
|
|
||||||
startup --host_jvm_args=-Dbazel.DigestFunction=SHA256
|
|
||||||
|
|
||||||
# Depending on how many machines are in the remote execution instance, setting
|
|
||||||
# this higher can make builds faster by allowing more jobs to run in parallel.
|
|
||||||
# Setting it too high can result in jobs that timeout, however, while waiting
|
|
||||||
# for a remote machine to execute them.
|
|
||||||
build --jobs=150
|
|
||||||
|
|
||||||
# Set several flags related to specifying the platform, toolchain and java
|
|
||||||
# properties.
|
|
||||||
# These flags are duplicated rather than imported from (for example)
|
|
||||||
# %workspace%/configs/ubuntu16_04_clang/1.0/toolchain.bazelrc to make this
|
|
||||||
# bazelrc a standalone file that can be copied more easily.
|
|
||||||
# These flags should only be used as is for the rbe-ubuntu16-04 container
|
|
||||||
# and need to be adapted to work with other toolchain containers.
|
|
||||||
build --host_javabase=@bazel_toolchains//configs/ubuntu16_04_clang/1.0:jdk8
|
|
||||||
build --javabase=@bazel_toolchains//configs/ubuntu16_04_clang/1.0:jdk8
|
|
||||||
build --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8
|
|
||||||
build --java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8
|
|
||||||
build --crosstool_top=@bazel_toolchains//configs/ubuntu16_04_clang/1.0/bazel_0.15.0/default:toolchain
|
|
||||||
build --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
|
|
||||||
# Platform flags:
|
|
||||||
# The toolchain container used for execution is defined in the target indicated
|
|
||||||
# by "extra_execution_platforms", "host_platform" and "platforms".
|
|
||||||
# If you are using your own toolchain container, you need to create a platform
|
|
||||||
# target with "constraint_values" that allow for the toolchain specified with
|
|
||||||
# "extra_toolchains" to be selected (given constraints defined in
|
|
||||||
# "exec_compatible_with").
|
|
||||||
# More about platforms: https://docs.bazel.build/versions/master/platforms.html
|
|
||||||
build --extra_toolchains=@bazel_toolchains//configs/ubuntu16_04_clang/1.0/bazel_0.15.0/cpp:cc-toolchain-clang-x86_64-default
|
|
||||||
build --extra_execution_platforms=@bazel_toolchains//configs/ubuntu16_04_clang/1.0:rbe_ubuntu1604
|
|
||||||
build --host_platform=@bazel_toolchains//configs/ubuntu16_04_clang/1.0:rbe_ubuntu1604
|
|
||||||
build --platforms=@bazel_toolchains//configs/ubuntu16_04_clang/1.0:rbe_ubuntu1604
|
|
||||||
|
|
||||||
# Set various strategies so that all actions execute remotely. Mixing remote
|
|
||||||
# and local execution will lead to errors unless the toolchain and remote
|
|
||||||
# machine exactly match the host machine.
|
|
||||||
build --spawn_strategy=remote
|
|
||||||
build --strategy=Javac=remote
|
|
||||||
build --strategy=Closure=remote
|
|
||||||
build --genrule_strategy=remote
|
|
||||||
build --define=EXECUTOR=remote
|
|
||||||
|
|
||||||
# Enable the remote cache so action results can be shared across machines,
|
|
||||||
# developers, and workspaces.
|
|
||||||
build --remote_cache=remotebuildexecution.googleapis.com
|
|
||||||
|
|
||||||
# Enable remote execution so actions are performed on the remote systems.
|
|
||||||
build --remote_executor=remotebuildexecution.googleapis.com
|
|
||||||
|
|
||||||
# Remote instance.
|
|
||||||
build --remote_instance_name=projects/internal-200822/instances/default_instance
|
|
||||||
|
|
||||||
# Enable encryption.
|
|
||||||
build --tls_enabled=true
|
|
||||||
|
|
||||||
# Enforce stricter environment rules, which eliminates some non-hermetic
|
|
||||||
# behavior and therefore improves both the remote cache hit rate and the
|
|
||||||
# correctness and repeatability of the build.
|
|
||||||
build --experimental_strict_action_env=true
|
|
||||||
|
|
||||||
# Set a higher timeout value, just in case.
|
|
||||||
build --remote_timeout=3600
|
|
||||||
|
|
||||||
# Enable authentication. This will pick up application default credentials by
|
|
||||||
# default. You can use --auth_credentials=some_file.json to use a service
|
|
||||||
# account credential instead.
|
|
||||||
build --auth_enabled=true
|
|
||||||
|
|
||||||
# Do not accept remote cache.
|
|
||||||
build --remote_accept_cached=false
|
|
107
.circleci/trigger-webhook.js
Normal file
107
.circleci/trigger-webhook.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usage (cli):
|
||||||
|
* ```
|
||||||
|
* node create-preview <build-number> <job-name> <webhook-url>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Usage (JS):
|
||||||
|
* ```js
|
||||||
|
* require('./trigger-webhook').
|
||||||
|
* triggerWebhook(buildNumber, jobName, webhookUrl).
|
||||||
|
* then(...);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Triggers a notification webhook with CircleCI specific info.
|
||||||
|
*
|
||||||
|
* It can be used for notifying external servers and trigger operations based on CircleCI job status
|
||||||
|
* (e.g. triggering the creation of a preview based on previously stored build atrifacts).
|
||||||
|
*
|
||||||
|
* The body of the sent payload is of the form:
|
||||||
|
* ```json
|
||||||
|
* {
|
||||||
|
* "payload": {
|
||||||
|
* "build_num": ${buildNumber}
|
||||||
|
* "build_parameters": {
|
||||||
|
* "CIRCLE_JOB": "${jobName}"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* When used from JS, it returns a promise which resolves to an object of the form:
|
||||||
|
* ```json
|
||||||
|
* {
|
||||||
|
* "statucCode": ${statusCode},
|
||||||
|
* "responseText": "${responseText}"
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
* - When used from the cli, the command will exit with an error code if the response's status code
|
||||||
|
* is outside the [200, 400) range.
|
||||||
|
* - When used from JS, the returned promise will be resolved, even if the response's status code is
|
||||||
|
* outside the [200, 400) range. It is up to the caller to decide how this should be handled.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Imports
|
||||||
|
const {request} = require('https');
|
||||||
|
|
||||||
|
// Exports
|
||||||
|
module.exports = {
|
||||||
|
triggerWebhook,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run
|
||||||
|
if (require.resolve === module) {
|
||||||
|
_main(process.argv.slice(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
function _main(args) {
|
||||||
|
triggerWebhook(...args).
|
||||||
|
then(({statusCode, responseText}) => (200 <= statusCode && statusCode < 400) ?
|
||||||
|
console.log(`Status: ${statusCode}\n${responseText}`) :
|
||||||
|
Promise.reject(new Error(`Request failed (status: ${statusCode}): ${responseText}`))).
|
||||||
|
catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function postJson(url, data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const opts = {method: 'post', headers: {'Content-Type': 'application/json'}};
|
||||||
|
const onResponse = res => {
|
||||||
|
const statusCode = res.statusCode || -1;
|
||||||
|
let responseText = '';
|
||||||
|
|
||||||
|
res.
|
||||||
|
on('error', reject).
|
||||||
|
on('data', d => responseText += d).
|
||||||
|
on('end', () => resolve({statusCode, responseText}));
|
||||||
|
};
|
||||||
|
|
||||||
|
request(url, opts, onResponse).
|
||||||
|
on('error', reject).
|
||||||
|
end(JSON.stringify(data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function triggerWebhook(buildNumber, jobName, webhookUrl) {
|
||||||
|
if (!buildNumber || !jobName || !webhookUrl || isNaN(buildNumber)) {
|
||||||
|
throw new Error(
|
||||||
|
'Missing or invalid arguments.\n' +
|
||||||
|
'Expected: buildNumber (number), jobName (string), webhookUrl (string)');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
payload: {
|
||||||
|
build_num: +buildNumber,
|
||||||
|
build_parameters: {CIRCLE_JOB: jobName},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return postJson(webhookUrl, data);
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
# http://editorconfig.org
|
# https://editorconfig.org
|
||||||
|
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
|
61
.github/ISSUE_TEMPLATE.md
vendored
61
.github/ISSUE_TEMPLATE.md
vendored
@ -1,59 +1,10 @@
|
|||||||
<!--
|
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
||||||
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
|
|
||||||
|
|
||||||
ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
|
Please help us process issues more efficiently by filing an
|
||||||
-->
|
issue using one of the following templates:
|
||||||
|
|
||||||
## I'm submitting a...
|
https://github.com/angular/angular/issues/new/choose
|
||||||
<!-- Check one of the following options with "x" -->
|
|
||||||
<pre><code>
|
|
||||||
[ ] Regression (a behavior that used to work and stopped working in a new release)
|
|
||||||
[ ] Bug report <!-- Please search GitHub for a similar issue or PR before submitting -->
|
|
||||||
[ ] Performance issue
|
|
||||||
[ ] Feature request
|
|
||||||
[ ] Documentation issue or request
|
|
||||||
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
|
|
||||||
[ ] Other... Please describe:
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
## Current behavior
|
Thank you!
|
||||||
<!-- Describe how the issue manifests. -->
|
|
||||||
|
|
||||||
|
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
||||||
## Expected behavior
|
|
||||||
<!-- Describe what the desired behavior would be. -->
|
|
||||||
|
|
||||||
|
|
||||||
## Minimal reproduction of the problem with instructions
|
|
||||||
<!--
|
|
||||||
For bug reports please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via
|
|
||||||
https://stackblitz.com or similar (you can use this template as a starting point: https://stackblitz.com/fork/angular-gitter).
|
|
||||||
-->
|
|
||||||
|
|
||||||
## What is the motivation / use case for changing the behavior?
|
|
||||||
<!-- Describe the motivation or the concrete use case. -->
|
|
||||||
|
|
||||||
|
|
||||||
## Environment
|
|
||||||
|
|
||||||
<pre><code>
|
|
||||||
Angular version: X.Y.Z
|
|
||||||
<!-- Check whether this is still an issue in the most recent Angular version -->
|
|
||||||
|
|
||||||
Browser:
|
|
||||||
- [ ] Chrome (desktop) version XX
|
|
||||||
- [ ] Chrome (Android) version XX
|
|
||||||
- [ ] Chrome (iOS) version XX
|
|
||||||
- [ ] Firefox version XX
|
|
||||||
- [ ] Safari (desktop) version XX
|
|
||||||
- [ ] Safari (iOS) version XX
|
|
||||||
- [ ] IE version XX
|
|
||||||
- [ ] Edge version XX
|
|
||||||
|
|
||||||
For Tooling issues:
|
|
||||||
- Node version: XX <!-- run `node --version` -->
|
|
||||||
- Platform: <!-- Mac, Linux, Windows -->
|
|
||||||
|
|
||||||
Others:
|
|
||||||
<!-- Anything else relevant? Operating system version, IDE, package manager, HTTP server, ... -->
|
|
||||||
</code></pre>
|
|
||||||
|
67
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
Normal file
67
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
name: "\U0001F41EBug report"
|
||||||
|
about: Report a bug in the Angular Framework
|
||||||
|
---
|
||||||
|
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
|
||||||
|
|
||||||
|
Oh hi there! 😄
|
||||||
|
|
||||||
|
To expedite issue processing please search open and closed issues before submitting a new one.
|
||||||
|
Existing issues often contain information about workarounds, resolution, or progress updates.
|
||||||
|
|
||||||
|
🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
|
||||||
|
|
||||||
|
|
||||||
|
# 🐞 bug report
|
||||||
|
|
||||||
|
### Affected Package
|
||||||
|
<!-- Can you pin-point one or more @angular/* packages as the source of the bug? -->
|
||||||
|
<!-- ✍️edit: --> The issue is caused by package @angular/....
|
||||||
|
|
||||||
|
|
||||||
|
### Is this a regression?
|
||||||
|
|
||||||
|
<!-- Did this behavior use to work in the previous version? -->
|
||||||
|
<!-- ✍️--> Yes, the previous version in which this bug was not present was: ....
|
||||||
|
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
<!-- ✍️--> A clear and concise description of the problem...
|
||||||
|
|
||||||
|
|
||||||
|
## 🔬 Minimal Reproduction
|
||||||
|
<!--
|
||||||
|
Please create and share minimal reproduction of the issue starting with this template: https://stackblitz.com/fork/angular-issue-repro2
|
||||||
|
-->
|
||||||
|
<!-- ✍️--> https://stackblitz.com/...
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If StackBlitz is not suitable for reproduction of your issue, please create a minimal GitHub repository with the reproduction of the issue. Share the link to the repo below along with step-by-step instructions to reproduce the problem, as well as expected and actual behavior.
|
||||||
|
|
||||||
|
Issues that don't have enough info and can't be reproduced will be closed.
|
||||||
|
|
||||||
|
You can read more about issue submission guidelines here: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-submitting-an-issue
|
||||||
|
-->
|
||||||
|
|
||||||
|
## 🔥 Exception or Error
|
||||||
|
<pre><code>
|
||||||
|
<!-- If the issue is accompanied by an exception or an error, please share it below: -->
|
||||||
|
<!-- ✍️-->
|
||||||
|
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
|
||||||
|
## 🌍 Your Environment
|
||||||
|
|
||||||
|
**Angular Version:**
|
||||||
|
<pre><code>
|
||||||
|
<!-- run `ng version` and paste output below -->
|
||||||
|
<!-- ✍️-->
|
||||||
|
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
**Anything else relevant?**
|
||||||
|
<!-- ✍️Is this a browser specific issue? If so, please specify the browser and version. -->
|
||||||
|
|
||||||
|
<!-- ✍️Do any of these matter: operating system, IDE, package manager, HTTP server, ...? If so, please mention it below. -->
|
32
.github/ISSUE_TEMPLATE/2-feature-request.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/2-feature-request.md
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
name: "\U0001F680Feature request"
|
||||||
|
about: Suggest a feature for Angular Framework
|
||||||
|
|
||||||
|
---
|
||||||
|
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
|
||||||
|
|
||||||
|
Oh hi there! 😄
|
||||||
|
|
||||||
|
To expedite issue processing please search open and closed issues before submitting a new one.
|
||||||
|
Existing issues often contain information about workarounds, resolution, or progress updates.
|
||||||
|
|
||||||
|
🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
|
||||||
|
|
||||||
|
|
||||||
|
# 🚀 feature request
|
||||||
|
|
||||||
|
### Relevant Package
|
||||||
|
<!-- Can you pin-point one or more @angular/* packages the are relevant for this feature request? -->
|
||||||
|
<!-- ✍️edit: --> This feature request is for @angular/....
|
||||||
|
|
||||||
|
|
||||||
|
### Description
|
||||||
|
<!-- ✍️--> A clear and concise description of the problem or missing capability...
|
||||||
|
|
||||||
|
|
||||||
|
### Describe the solution you'd like
|
||||||
|
<!-- ✍️--> If you have a solution in mind, please describe it.
|
||||||
|
|
||||||
|
|
||||||
|
### Describe alternatives you've considered
|
||||||
|
<!-- ✍️--> Have you considered any alternative solutions or workarounds?
|
55
.github/ISSUE_TEMPLATE/3-docs-bug.md
vendored
Normal file
55
.github/ISSUE_TEMPLATE/3-docs-bug.md
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
name: "📚 Docs or angular.io issue report"
|
||||||
|
about: Report an issue in Angular's documentation or angular.io application
|
||||||
|
|
||||||
|
---
|
||||||
|
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
|
||||||
|
|
||||||
|
Oh hi there! 😄
|
||||||
|
|
||||||
|
To expedite issue processing please search open and closed issues before submitting a new one.
|
||||||
|
Existing issues often contain information about workarounds, resolution, or progress updates.
|
||||||
|
|
||||||
|
🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
|
||||||
|
|
||||||
|
# 📚 Docs or angular.io bug report
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
<!-- ✍️edit:--> A clear and concise description of the problem...
|
||||||
|
|
||||||
|
|
||||||
|
## 🔬 Minimal Reproduction
|
||||||
|
|
||||||
|
### What's the affected URL?**
|
||||||
|
<!-- ✍️edit:--> https://angular.io/...
|
||||||
|
|
||||||
|
### Reproduction Steps**
|
||||||
|
<!-- If applicable please list the steps to take to reproduce the issue -->
|
||||||
|
<!-- ✍️edit:-->
|
||||||
|
|
||||||
|
### Expected vs Actual Behavior**
|
||||||
|
<!-- If applicable please describe the difference between the expected and actual behavior after following the repro steps. -->
|
||||||
|
<!-- ✍️edit:-->
|
||||||
|
|
||||||
|
|
||||||
|
## 📷Screenshot
|
||||||
|
<!-- Often a screenshot can help to capture the issue better than a long description. -->
|
||||||
|
<!-- ✍️upload a screenshot:-->
|
||||||
|
|
||||||
|
|
||||||
|
## 🔥 Exception or Error
|
||||||
|
<pre><code>
|
||||||
|
<!-- If the issue is accompanied by an exception or an error, please share it below: -->
|
||||||
|
<!-- ✍️-->
|
||||||
|
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
|
||||||
|
## 🌍 Your Environment
|
||||||
|
|
||||||
|
### Browser info
|
||||||
|
<!-- ✍️Is this a browser specific issue? If so, please specify the device, browser, and version. -->
|
||||||
|
|
||||||
|
### Anything else relevant?
|
||||||
|
<!-- ✍️Please provide additional info if necessary. -->
|
11
.github/ISSUE_TEMPLATE/4-security-issue-disclosure.md
vendored
Normal file
11
.github/ISSUE_TEMPLATE/4-security-issue-disclosure.md
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
name: ⚠️ Security issue disclosure
|
||||||
|
about: Report a security issue in Angular Framework, Material, or CLI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
||||||
|
|
||||||
|
Please read https://angular.io/guide/security#report-issues on how to disclose security related issues.
|
||||||
|
|
||||||
|
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
16
.github/ISSUE_TEMPLATE/5-support-request.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/5-support-request.md
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: "❓Support request"
|
||||||
|
about: Questions and requests for support
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
||||||
|
|
||||||
|
Please do not file questions or support requests on the GitHub issues tracker.
|
||||||
|
|
||||||
|
You can get your questions answered using other communication channels. Please see:
|
||||||
|
https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
|
||||||
|
|
||||||
|
Thank you!
|
||||||
|
|
||||||
|
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
13
.github/ISSUE_TEMPLATE/6-angular-cli.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE/6-angular-cli.md
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: "\U0001F6E0️Angular CLI"
|
||||||
|
about: Issues and feature requests for Angular CLI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
||||||
|
|
||||||
|
Please file any Angular CLI issues at: https://github.com/angular/angular-cli/issues/new
|
||||||
|
|
||||||
|
For the time being, we keep Angular CLI issues in a separate repository.
|
||||||
|
|
||||||
|
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
13
.github/ISSUE_TEMPLATE/7-angular-material.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE/7-angular-material.md
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: "\U0001F48EAngular Material"
|
||||||
|
about: Issues and feature requests for Angular Material
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
||||||
|
|
||||||
|
Please file any Angular Material issues at: https://github.com/angular/material2/issues/new
|
||||||
|
|
||||||
|
For the time being, we keep Angular Material issues in a separate repository.
|
||||||
|
|
||||||
|
🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑
|
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -10,17 +10,17 @@ Please check if your PR fulfills the following requirements:
|
|||||||
What kind of change does this PR introduce?
|
What kind of change does this PR introduce?
|
||||||
|
|
||||||
<!-- Please check the one that applies to this PR using "x". -->
|
<!-- Please check the one that applies to this PR using "x". -->
|
||||||
```
|
|
||||||
[ ] Bugfix
|
- [ ] Bugfix
|
||||||
[ ] Feature
|
- [ ] Feature
|
||||||
[ ] Code style update (formatting, local variables)
|
- [ ] Code style update (formatting, local variables)
|
||||||
[ ] Refactoring (no functional changes, no api changes)
|
- [ ] Refactoring (no functional changes, no api changes)
|
||||||
[ ] Build related changes
|
- [ ] Build related changes
|
||||||
[ ] CI related changes
|
- [ ] CI related changes
|
||||||
[ ] Documentation content changes
|
- [ ] Documentation content changes
|
||||||
[ ] angular.io application / infrastructure changes
|
- [ ] angular.io application / infrastructure changes
|
||||||
[ ] Other... Please describe:
|
- [ ] Other... Please describe:
|
||||||
```
|
|
||||||
|
|
||||||
## What is the current behavior?
|
## What is the current behavior?
|
||||||
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
|
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
|
||||||
@ -32,10 +32,10 @@ Issue Number: N/A
|
|||||||
|
|
||||||
|
|
||||||
## Does this PR introduce a breaking change?
|
## Does this PR introduce a breaking change?
|
||||||
```
|
|
||||||
[ ] Yes
|
- [ ] Yes
|
||||||
[ ] No
|
- [ ] No
|
||||||
```
|
|
||||||
|
|
||||||
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
|
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
|
||||||
|
|
||||||
|
47
.github/angular-robot.yml
vendored
47
.github/angular-robot.yml
vendored
@ -3,11 +3,8 @@
|
|||||||
#options for the size plugin
|
#options for the size plugin
|
||||||
size:
|
size:
|
||||||
disabled: false
|
disabled: false
|
||||||
maxSizeIncrease: 1000
|
maxSizeIncrease: 2000
|
||||||
circleCiStatusName: "ci/circleci: build-packages-dist"
|
circleCiStatusName: "ci/circleci: test_ivy_aot"
|
||||||
status:
|
|
||||||
disabled: false
|
|
||||||
context: "ci/angular: size"
|
|
||||||
|
|
||||||
# options for the merge plugin
|
# options for the merge plugin
|
||||||
merge:
|
merge:
|
||||||
@ -42,14 +39,28 @@ merge:
|
|||||||
- "packages/**"
|
- "packages/**"
|
||||||
# list of patterns to ignore for the files changed by the PR
|
# list of patterns to ignore for the files changed by the PR
|
||||||
exclude:
|
exclude:
|
||||||
|
- "packages/*"
|
||||||
|
- "packages/bazel/*"
|
||||||
|
- "packages/bazel/src/builders/**"
|
||||||
|
- "packages/bazel/src/ng_package/**"
|
||||||
|
- "packages/bazel/src/protractor/**"
|
||||||
|
- "packages/bazel/src/schematics/**"
|
||||||
|
- "packages/compiler-cli/src/ngcc/**"
|
||||||
|
- "packages/docs/**"
|
||||||
|
- "packages/elements/schematics/**"
|
||||||
|
- "packages/examples/**"
|
||||||
- "packages/language-service/**"
|
- "packages/language-service/**"
|
||||||
|
- "packages/private/**"
|
||||||
|
- "packages/service-worker/**"
|
||||||
- "**/.gitignore"
|
- "**/.gitignore"
|
||||||
- "**/.gitkeep"
|
- "**/.gitkeep"
|
||||||
|
- "**/yarn.lock"
|
||||||
- "**/package.json"
|
- "**/package.json"
|
||||||
- "**/tsconfig-build.json"
|
- "**/tsconfig-build.json"
|
||||||
- "**/tsconfig.json"
|
- "**/tsconfig.json"
|
||||||
- "**/rollup.config.js"
|
- "**/rollup.config.js"
|
||||||
- "**/BUILD.bazel"
|
- "**/BUILD.bazel"
|
||||||
|
- "**/*.md"
|
||||||
- "packages/**/integrationtest/**"
|
- "packages/**/integrationtest/**"
|
||||||
- "packages/**/test/**"
|
- "packages/**/test/**"
|
||||||
|
|
||||||
@ -60,6 +71,10 @@ merge:
|
|||||||
# label to monitor
|
# label to monitor
|
||||||
mergeLabel: "PR action: merge"
|
mergeLabel: "PR action: merge"
|
||||||
|
|
||||||
|
# adding any of these labels will also add the merge label
|
||||||
|
mergeLinkedLabels:
|
||||||
|
- "PR action: merge-assistance"
|
||||||
|
|
||||||
# list of checks that will determine if the merge label can be added
|
# list of checks that will determine if the merge label can be added
|
||||||
checks:
|
checks:
|
||||||
|
|
||||||
@ -68,7 +83,7 @@ merge:
|
|||||||
# This enables us to request reviews from both eng and tech writers, or multiple eng folks, and prevents accidental merges.
|
# This enables us to request reviews from both eng and tech writers, or multiple eng folks, and prevents accidental merges.
|
||||||
# Rather than merging PRs with pending reviews, if all PullApprove requirements are satisfied and additional reviews are not needed pending reviewers should be removed via GitHub UI (this also leaves an audit trail behind these decisions).
|
# Rather than merging PRs with pending reviews, if all PullApprove requirements are satisfied and additional reviews are not needed pending reviewers should be removed via GitHub UI (this also leaves an audit trail behind these decisions).
|
||||||
requireReviews: true,
|
requireReviews: true,
|
||||||
|
|
||||||
# whether the PR shouldn't have a conflict with the base branch
|
# whether the PR shouldn't have a conflict with the base branch
|
||||||
noConflict: true
|
noConflict: true
|
||||||
# list of labels that a PR needs to have, checked with a regexp (e.g. "PR target:" will work for the label "PR target: master")
|
# list of labels that a PR needs to have, checked with a regexp (e.g. "PR target:" will work for the label "PR target: master")
|
||||||
@ -127,3 +142,23 @@ triage:
|
|||||||
-
|
-
|
||||||
- "type: RFC / Discussion / question"
|
- "type: RFC / Discussion / question"
|
||||||
- "comp: *"
|
- "comp: *"
|
||||||
|
|
||||||
|
# options for the triage PR plugin
|
||||||
|
triagePR:
|
||||||
|
# set to true to disable
|
||||||
|
disabled: false
|
||||||
|
# number of the milestone to apply when the PR has not been triaged yet
|
||||||
|
needsTriageMilestone: 83,
|
||||||
|
# number of the milestone to apply when the PR is triaged
|
||||||
|
defaultMilestone: 82,
|
||||||
|
# arrays of labels that determine if a PR has been triaged by the caretaker
|
||||||
|
l1TriageLabels:
|
||||||
|
-
|
||||||
|
- "comp: *"
|
||||||
|
# arrays of labels that determine if a PR has been fully triaged
|
||||||
|
l2TriageLabels:
|
||||||
|
-
|
||||||
|
- "type: *"
|
||||||
|
- "effort*"
|
||||||
|
- "risk*"
|
||||||
|
- "comp: *"
|
||||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,7 +1,8 @@
|
|||||||
.DS_STORE
|
.DS_STORE
|
||||||
|
|
||||||
/dist/
|
/dist/
|
||||||
bazel-*
|
/bazel-*
|
||||||
|
/integration/bazel/bazel-*
|
||||||
e2e_test.*
|
e2e_test.*
|
||||||
node_modules
|
node_modules
|
||||||
bower_components
|
bower_components
|
||||||
@ -14,7 +15,6 @@ pubspec.lock
|
|||||||
.settings/
|
.settings/
|
||||||
*.swo
|
*.swo
|
||||||
modules/.settings
|
modules/.settings
|
||||||
.bazelrc
|
|
||||||
.vscode
|
.vscode
|
||||||
modules/.vscode
|
modules/.vscode
|
||||||
|
|
||||||
@ -30,3 +30,7 @@ yarn-error.log
|
|||||||
|
|
||||||
# rollup-test output
|
# rollup-test output
|
||||||
/modules/rollup-test/dist/
|
/modules/rollup-test/dist/
|
||||||
|
|
||||||
|
# User specific bazel settings
|
||||||
|
.bazelrc.user
|
||||||
|
|
||||||
|
@ -87,10 +87,10 @@ groups:
|
|||||||
files:
|
files:
|
||||||
include:
|
include:
|
||||||
- "WORKSPACE"
|
- "WORKSPACE"
|
||||||
|
- ".bazel*"
|
||||||
- "*.bazel"
|
- "*.bazel"
|
||||||
- "*.bzl"
|
- "*.bzl"
|
||||||
- "packages/bazel/*"
|
- "packages/bazel/*"
|
||||||
- "tools/bazel.rc"
|
|
||||||
- "/docs/BAZEL.md"
|
- "/docs/BAZEL.md"
|
||||||
users:
|
users:
|
||||||
- alexeagle #primary
|
- alexeagle #primary
|
||||||
@ -98,6 +98,7 @@ groups:
|
|||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery
|
- mhevery
|
||||||
- vikerman #fallback
|
- vikerman #fallback
|
||||||
|
- kara
|
||||||
|
|
||||||
build-and-ci:
|
build-and-ci:
|
||||||
conditions:
|
conditions:
|
||||||
@ -108,9 +109,9 @@ groups:
|
|||||||
- "*.lock"
|
- "*.lock"
|
||||||
- "tools/*"
|
- "tools/*"
|
||||||
exclude:
|
exclude:
|
||||||
- "tools/bazel.rc"
|
|
||||||
- "tools/public_api_guard/*"
|
|
||||||
- "aio/*"
|
- "aio/*"
|
||||||
|
- "packages/core/test/bundling/*"
|
||||||
|
- "tools/public_api_guard/*"
|
||||||
users:
|
users:
|
||||||
- IgorMinar #primary
|
- IgorMinar #primary
|
||||||
- alexeagle
|
- alexeagle
|
||||||
@ -211,6 +212,7 @@ groups:
|
|||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
|
- kara
|
||||||
|
|
||||||
compiler/i18n:
|
compiler/i18n:
|
||||||
conditions:
|
conditions:
|
||||||
@ -223,6 +225,7 @@ groups:
|
|||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
|
- kara
|
||||||
|
|
||||||
compiler:
|
compiler:
|
||||||
conditions:
|
conditions:
|
||||||
@ -234,6 +237,7 @@ groups:
|
|||||||
- mhevery
|
- mhevery
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
|
- kara
|
||||||
|
|
||||||
compiler-cli/ngtools:
|
compiler-cli/ngtools:
|
||||||
conditions:
|
conditions:
|
||||||
@ -243,13 +247,13 @@ groups:
|
|||||||
- hansl
|
- hansl
|
||||||
- filipesilva #fallback
|
- filipesilva #fallback
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
|
- kara
|
||||||
|
|
||||||
compiler-cli:
|
compiler-cli:
|
||||||
conditions:
|
conditions:
|
||||||
files:
|
files:
|
||||||
include:
|
include:
|
||||||
- "packages/compiler-cli/*"
|
- "packages/compiler-cli/*"
|
||||||
- "packages/bazel/*"
|
|
||||||
exclude:
|
exclude:
|
||||||
- "packages/compiler-cli/src/ngtools*"
|
- "packages/compiler-cli/src/ngtools*"
|
||||||
users:
|
users:
|
||||||
@ -257,6 +261,7 @@ groups:
|
|||||||
- alxhub
|
- alxhub
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
- kara
|
||||||
|
|
||||||
common:
|
common:
|
||||||
conditions:
|
conditions:
|
||||||
@ -269,6 +274,7 @@ groups:
|
|||||||
- pkozlowski-opensource #primary
|
- pkozlowski-opensource #primary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
- kara
|
||||||
|
|
||||||
forms:
|
forms:
|
||||||
conditions:
|
conditions:
|
||||||
@ -277,6 +283,9 @@ groups:
|
|||||||
- "aio/content/guide/forms.md"
|
- "aio/content/guide/forms.md"
|
||||||
- "aio/content/examples/forms/*"
|
- "aio/content/examples/forms/*"
|
||||||
- "aio/content/images/guide/forms/*"
|
- "aio/content/images/guide/forms/*"
|
||||||
|
- "aio/content/guide/forms-overview.md"
|
||||||
|
- "aio/content/examples/forms-overview/*"
|
||||||
|
- "aio/content/images/guide/forms-overview/*"
|
||||||
- "aio/content/guide/form-validation.md"
|
- "aio/content/guide/form-validation.md"
|
||||||
- "aio/content/examples/form-validation/*"
|
- "aio/content/examples/form-validation/*"
|
||||||
- "aio/content/images/guide/form-validation/*"
|
- "aio/content/images/guide/form-validation/*"
|
||||||
@ -331,6 +340,7 @@ groups:
|
|||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
|
- kara
|
||||||
|
|
||||||
testing:
|
testing:
|
||||||
conditions:
|
conditions:
|
||||||
@ -364,6 +374,7 @@ groups:
|
|||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
|
- kara
|
||||||
|
|
||||||
platform-browser:
|
platform-browser:
|
||||||
conditions:
|
conditions:
|
||||||
@ -373,6 +384,7 @@ groups:
|
|||||||
- mhevery #primary
|
- mhevery #primary
|
||||||
# needs secondary
|
# needs secondary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
|
- kara
|
||||||
|
|
||||||
platform-server:
|
platform-server:
|
||||||
conditions:
|
conditions:
|
||||||
@ -395,6 +407,7 @@ groups:
|
|||||||
- mhevery #primary
|
- mhevery #primary
|
||||||
# needs secondary
|
# needs secondary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
|
- kara
|
||||||
|
|
||||||
service-worker:
|
service-worker:
|
||||||
conditions:
|
conditions:
|
||||||
@ -428,6 +441,7 @@ groups:
|
|||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
- jenniferfell #docs only
|
- jenniferfell #docs only
|
||||||
|
- kara
|
||||||
|
|
||||||
benchpress:
|
benchpress:
|
||||||
conditions:
|
conditions:
|
||||||
|
24
.travis.yml
24
.travis.yml
@ -2,7 +2,7 @@ language: node_js
|
|||||||
sudo: false
|
sudo: false
|
||||||
dist: trusty
|
dist: trusty
|
||||||
node_js:
|
node_js:
|
||||||
- '8.9.1'
|
- '10.9.0'
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
# firefox: "38.0"
|
# firefox: "38.0"
|
||||||
@ -30,14 +30,6 @@ env:
|
|||||||
# GITHUB_TOKEN_ANGULAR=<github token, a personal access token of the angular-builds account, account access in valentine>
|
# GITHUB_TOKEN_ANGULAR=<github token, a personal access token of the angular-builds account, account access in valentine>
|
||||||
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
|
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
|
||||||
- secure: "aCdHveZuY8AT4Jr1JoJB4LxZsnGWRe/KseZh1YXYe5UtufFCtTVHvUcLn0j2aLBF0KpdyS+hWf0i4np9jthKu2xPKriefoPgCMpisYeC0MFkwbmv+XlgkUbgkgVZMGiVyX7DCYXVahxIoOUjVMEDCbNiHTIrfEuyq24U3ok2tHc="
|
- secure: "aCdHveZuY8AT4Jr1JoJB4LxZsnGWRe/KseZh1YXYe5UtufFCtTVHvUcLn0j2aLBF0KpdyS+hWf0i4np9jthKu2xPKriefoPgCMpisYeC0MFkwbmv+XlgkUbgkgVZMGiVyX7DCYXVahxIoOUjVMEDCbNiHTIrfEuyq24U3ok2tHc="
|
||||||
# FIREBASE_TOKEN
|
|
||||||
# This is needed for publishing builds to the "aio-staging" and "angular-io" firebase projects.
|
|
||||||
# This token was generated using the aio-deploy@angular.io account using `firebase login:ci` and password from valentine
|
|
||||||
- secure: "L5CyQmpwWtoR4Qi4xlWQh/cL1M6ZeJL4W4QAr4HdKFMgYt9h+Whqkymyh2NxwmCbPvWa7yUd+OiLQUDCY7L2VIg16hTwoe2CgYDyQA0BEwLzxtRrJXl93TfwMlrUx5JSIzAccD6D4sjtz8kSFMomK2Nls33xOXOukwyhVMjd0Cg="
|
|
||||||
# ANGULAR_PAYLOAD_FIREBASE_TOKEN
|
|
||||||
# This is for payload size data to "angular-payload-size" firebase project
|
|
||||||
# This token was generated using the payload@angular.io account using `firebase login:ci` and password from valentine
|
|
||||||
- secure: "SxotP/ymNy6uWAVbfwM9BlwETPEBpkRvU/F7fCtQDDic99WfQHzzUSQqHTk8eKk3GrGAOSL09vT0WfStQYEIGEoS5UHWNgOnelxhw+d5EnaoB8vQ0dKQBTK092hQg4feFprr+B/tCasyMV6mVwpUzZMbIJNn/Rx7H5g1bp+Gkfg="
|
|
||||||
matrix:
|
matrix:
|
||||||
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
|
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
|
||||||
- CI_MODE=e2e
|
- CI_MODE=e2e
|
||||||
@ -45,12 +37,12 @@ env:
|
|||||||
- CI_MODE=saucelabs_required
|
- CI_MODE=saucelabs_required
|
||||||
# deactivated, see #19768
|
# deactivated, see #19768
|
||||||
# - CI_MODE=browserstack_required
|
# - CI_MODE=browserstack_required
|
||||||
- CI_MODE=saucelabs_optional
|
|
||||||
- CI_MODE=browserstack_optional
|
# We disable these optional jobs because those acquire tunnel and browser instances which
|
||||||
- CI_MODE=aio_tools_test
|
# could lead to rate limit excess while those are failing most of the time and nobody pays
|
||||||
- CI_MODE=aio
|
# attention anyway.
|
||||||
- CI_MODE=aio_e2e AIO_SHARD=0
|
# - CI_MODE=saucelabs_optional
|
||||||
- CI_MODE=aio_e2e AIO_SHARD=1
|
# - CI_MODE=browserstack_optional
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
@ -68,8 +60,6 @@ install:
|
|||||||
script:
|
script:
|
||||||
- ./scripts/ci/build.sh
|
- ./scripts/ci/build.sh
|
||||||
- ./scripts/ci/test.sh
|
- ./scripts/ci/test.sh
|
||||||
# deploy is part of 'script' and not 'after_success' so that we fail the build if the deployment fails
|
|
||||||
- ./scripts/ci/deploy.sh
|
|
||||||
- ./scripts/ci/angular.sh
|
- ./scripts/ci/angular.sh
|
||||||
# all the scripts under this line will not quickly abort in case ${TRAVIS_TEST_RESULT} is 1 (job failure)
|
# all the scripts under this line will not quickly abort in case ${TRAVIS_TEST_RESULT} is 1 (job failure)
|
||||||
- ./scripts/ci/cleanup.sh
|
- ./scripts/ci/cleanup.sh
|
||||||
|
55
BUILD.bazel
55
BUILD.bazel
@ -8,26 +8,14 @@ exports_files([
|
|||||||
"protractor-perf.conf.js",
|
"protractor-perf.conf.js",
|
||||||
])
|
])
|
||||||
|
|
||||||
# Developers should always run `bazel run :install`
|
|
||||||
# This ensures that package.json in subdirectories get installed as well.
|
|
||||||
alias(
|
|
||||||
name = "install",
|
|
||||||
actual = "@nodejs//:yarn",
|
|
||||||
)
|
|
||||||
|
|
||||||
alias(
|
|
||||||
name = "node_modules",
|
|
||||||
actual = "@angular_deps//:node_modules",
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "web_test_bootstrap_scripts",
|
name = "web_test_bootstrap_scripts",
|
||||||
# do not sort
|
# do not sort
|
||||||
srcs = [
|
srcs = [
|
||||||
"@angular_deps//:node_modules/reflect-metadata/Reflect.js",
|
"@ngdeps//node_modules/reflect-metadata:Reflect.js",
|
||||||
"@angular_deps//:node_modules/zone.js/dist/zone.js",
|
"@ngdeps//node_modules/zone.js:dist/zone.js",
|
||||||
"@angular_deps//:node_modules/zone.js/dist/zone-testing.js",
|
"@ngdeps//node_modules/zone.js:dist/zone-testing.js",
|
||||||
"@angular_deps//:node_modules/zone.js/dist/task-tracking.js",
|
"@ngdeps//node_modules/zone.js:dist/task-tracking.js",
|
||||||
"//:test-events.js",
|
"//:test-events.js",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -35,11 +23,34 @@ filegroup(
|
|||||||
filegroup(
|
filegroup(
|
||||||
name = "angularjs_scripts",
|
name = "angularjs_scripts",
|
||||||
srcs = [
|
srcs = [
|
||||||
"@angular_deps//:node_modules/angular-1.5/angular.js",
|
# We also declare the unminfied AngularJS files since these can be used for
|
||||||
"@angular_deps//:node_modules/angular-1.6/angular.js",
|
# local debugging (e.g. see: packages/upgrade/test/common/test_helpers.ts)
|
||||||
"@angular_deps//:node_modules/angular-mocks-1.5/angular-mocks.js",
|
"@ngdeps//node_modules/angular:angular.js",
|
||||||
"@angular_deps//:node_modules/angular-mocks-1.6/angular-mocks.js",
|
"@ngdeps//node_modules/angular:angular.min.js",
|
||||||
"@angular_deps//:node_modules/angular-mocks/angular-mocks.js",
|
"@ngdeps//node_modules/angular-1.5:angular.js",
|
||||||
"@angular_deps//:node_modules/angular/angular.js",
|
"@ngdeps//node_modules/angular-1.5:angular.min.js",
|
||||||
|
"@ngdeps//node_modules/angular-1.6:angular.js",
|
||||||
|
"@ngdeps//node_modules/angular-1.6:angular.min.js",
|
||||||
|
"@ngdeps//node_modules/angular-mocks:angular-mocks.js",
|
||||||
|
"@ngdeps//node_modules/angular-mocks-1.5:angular-mocks.js",
|
||||||
|
"@ngdeps//node_modules/angular-mocks-1.6:angular-mocks.js",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
|
||||||
|
|
||||||
|
# A nodejs_binary for @angular/bazel/ngc-wrapped to use by default in
|
||||||
|
# ng_module that depends on @npm//@angular/bazel instead of the
|
||||||
|
# output of the //packages/bazel/src/ngc-wrapped ts_library rule. This
|
||||||
|
# default is for downstream users that depend on the @angular/bazel npm
|
||||||
|
# package. The generated @npm//@angular/bazel/ngc-wrapped target
|
||||||
|
# does not work because it does not have the node `--expose-gc` flag
|
||||||
|
# set which is required to support the call to `global.gc()`.
|
||||||
|
nodejs_binary(
|
||||||
|
name = "@angular/bazel/ngc-wrapped",
|
||||||
|
configuration_env_vars = ["compile"],
|
||||||
|
data = ["@npm//@angular/bazel"],
|
||||||
|
entry_point = "@angular/bazel/src/ngc-wrapped/index.js",
|
||||||
|
install_source_map_support = False,
|
||||||
|
templated_args = ["--node_options=--expose-gc"],
|
||||||
|
)
|
||||||
|
358
CHANGELOG.md
358
CHANGELOG.md
@ -1,13 +1,316 @@
|
|||||||
<a name="7.0.0-beta.4"></a>
|
<a name="7.1.4"></a>
|
||||||
# [7.0.0-beta.4](https://github.com/angular/angular/compare/7.0.0-beta.3...7.0.0-beta.4) (2018-08-29)
|
## [7.1.4](https://github.com/angular/angular/compare/7.1.3...7.1.4) (2018-12-18)
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **animations:** do not truncate decimals for delay ([#24455](https://github.com/angular/angular/issues/24455)) ([cd1e206](https://github.com/angular/angular/commit/cd1e206))
|
||||||
|
* **animations:** mark actual descendant node as disabled ([#26180](https://github.com/angular/angular/issues/26180)) ([453589f](https://github.com/angular/angular/commit/453589f))
|
||||||
|
* **bazel:** devserver entry_module should have underscore name ([#27719](https://github.com/angular/angular/issues/27719)) ([b108e9a](https://github.com/angular/angular/commit/b108e9a))
|
||||||
|
* **bazel:** emit full node stack traces when Angular compilation crashes ([#27678](https://github.com/angular/angular/issues/27678)) ([0d8528b](https://github.com/angular/angular/commit/0d8528b))
|
||||||
|
* **bazel:** fix major/minor semver check between [@angular](https://github.com/angular)/bazel npm packager version and angular bazel repo version ([#27635](https://github.com/angular/angular/issues/27635)) ([3ed1e84](https://github.com/angular/angular/commit/3ed1e84))
|
||||||
|
* **bazel:** Load http_archive and rules_nodejs dependencies ([#27609](https://github.com/angular/angular/issues/27609)) ([89ace1a](https://github.com/angular/angular/commit/89ace1a))
|
||||||
|
* **bazel:** ng_package writes unrelevant definitions to bazel out ([#27519](https://github.com/angular/angular/issues/27519)) ([ef056c5](https://github.com/angular/angular/commit/ef056c5)), closes [/github.com/angular/angular/blob/4f9374951d67c75f67a31c110bd61ab72563db7d/packages/bazel/src/ng_package/packager.ts#L105-L124](https://github.com//github.com/angular/angular/blob/4f9374951d67c75f67a31c110bd61ab72563db7d/packages/bazel/src/ng_package/packager.ts/issues/L105-L124)
|
||||||
|
* **bazel:** Read latest versions from latest-versions.ts & use semver check ([#27591](https://github.com/angular/angular/issues/27591)) ([93078e3](https://github.com/angular/angular/commit/93078e3))
|
||||||
|
* **bazel:** Set module_name and enable ng test ([#27715](https://github.com/angular/angular/issues/27715)) ([183f278](https://github.com/angular/angular/commit/183f278))
|
||||||
|
* **common:** KeyValuePipe should return empty array for empty objects ([#27258](https://github.com/angular/angular/issues/27258)) ([fa3af8b](https://github.com/angular/angular/commit/fa3af8b))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.1.3"></a>
|
||||||
|
## [7.1.3](https://github.com/angular/angular/compare/7.1.2...7.1.3) (2018-12-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **bazel:** tsickle dependency not working with typescript 3.1.x ([#27402](https://github.com/angular/angular/issues/27402)) ([a9f39a4](https://github.com/angular/angular/commit/a9f39a4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.1.2"></a>
|
||||||
|
## [7.1.2](https://github.com/angular/angular/compare/7.1.1...7.1.2) (2018-12-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **bazel:** do not throw error when writing tsickle externs ([#27200](https://github.com/angular/angular/issues/27200)) ([079c4b3](https://github.com/angular/angular/commit/079c4b3))
|
||||||
|
* **bazel:** do not throw if ts compile action does not create esm5 outputs ([#27401](https://github.com/angular/angular/issues/27401)) ([9b4d959](https://github.com/angular/angular/commit/9b4d959))
|
||||||
|
* **bazel:** ng_package cannot be run multiple times without clean ([#27200](https://github.com/angular/angular/issues/27200)) ([1ca2923](https://github.com/angular/angular/commit/1ca2923))
|
||||||
|
* **bazel:** ng_package not generating UMD bundles on windows ([#27200](https://github.com/angular/angular/issues/27200)) ([e476c38](https://github.com/angular/angular/commit/e476c38))
|
||||||
|
* **bazel:** ng_package should correctly map to source maps in secondary entry-points ([#27313](https://github.com/angular/angular/issues/27313)) ([fc2c23e](https://github.com/angular/angular/commit/fc2c23e)), closes [#25510](https://github.com/angular/angular/issues/25510)
|
||||||
|
* **compiler-cli:** flatModuleIndex files not generated on windows with multiple input files ([#27200](https://github.com/angular/angular/issues/27200)) ([8087b6b](https://github.com/angular/angular/commit/8087b6b))
|
||||||
|
* **compiler-cli:** ngtsc shim files not being generated on case-insensitive platforms ([#27466](https://github.com/angular/angular/issues/27466)) ([84f2928](https://github.com/angular/angular/commit/84f2928)), closes [/github.com/Microsoft/TypeScript/blob/3e4c5c95abd515eb9713b881d27ab3a93cc00461/src/compiler/sys.ts#L681-L682](https://github.com//github.com/Microsoft/TypeScript/blob/3e4c5c95abd515eb9713b881d27ab3a93cc00461/src/compiler/sys.ts/issues/L681-L682)
|
||||||
|
* **platform-server:** add [@angular](https://github.com/angular)/http to the list of peerDependencies ([#27307](https://github.com/angular/angular/issues/27307)) ([236ac06](https://github.com/angular/angular/commit/236ac06)), closes [#26154](https://github.com/angular/angular/issues/26154)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.1.1"></a>
|
||||||
|
## [7.1.1](https://github.com/angular/angular/compare/7.1.0...7.1.1) (2018-11-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **core:** export a value for InjectFlags ([#27279](https://github.com/angular/angular/issues/27279)) ([bdf5f3e](https://github.com/angular/angular/commit/bdf5f3e)), closes [#27251](https://github.com/angular/angular/issues/27251)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.1.0"></a>
|
||||||
|
# [7.1.0](https://github.com/angular/angular/compare/7.1.0-rc.0...7.1.0) (2018-11-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **core:** allow null value for renderer setElement(…) ([#17065](https://github.com/angular/angular/issues/17065)) ([ff15043](https://github.com/angular/angular/commit/ff15043)), closes [#13686](https://github.com/angular/angular/issues/13686)
|
||||||
|
* **router:** fix regression where navigateByUrl promise didn't resolve on CanLoad failure ([#26455](https://github.com/angular/angular/issues/26455)) ([1c9b065](https://github.com/angular/angular/commit/1c9b065)), closes [#26284](https://github.com/angular/angular/issues/26284)
|
||||||
|
* **service-worker:** clean up caches from old SW versions ([#26319](https://github.com/angular/angular/issues/26319)) ([2326b9c](https://github.com/angular/angular/commit/2326b9c))
|
||||||
|
* **upgrade:** properly destroy upgraded component elements and descendants ([#26209](https://github.com/angular/angular/issues/26209)) ([071934e](https://github.com/angular/angular/commit/071934e)), closes [#26208](https://github.com/angular/angular/issues/26208)
|
||||||
|
* **compiler:** generate inputs with aliases properly ([#26774](https://github.com/angular/angular/issues/26774)) ([19fcfc3](https://github.com/angular/angular/commit/19fcfc3))
|
||||||
|
* **compiler:** generate relative paths only in summary file errors ([#26759](https://github.com/angular/angular/issues/26759)) ([56f44be](https://github.com/angular/angular/commit/56f44be))
|
||||||
|
* **core:** ignore comment nodes under unsafe elements ([#25879](https://github.com/angular/angular/issues/25879)) ([d5cbcef](https://github.com/angular/angular/commit/d5cbcef))
|
||||||
|
* **core:** Remove static dependency from [@angular](https://github.com/angular)/core to [@angular](https://github.com/angular)/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([d042c4a](https://github.com/angular/angular/commit/d042c4a))
|
||||||
|
* **core:** support computed base class in metadata inheritance ([#24014](https://github.com/angular/angular/issues/24014)) ([95743e3](https://github.com/angular/angular/commit/95743e3))
|
||||||
|
* **bazel:** unknown replay compiler error in windows ([#26711](https://github.com/angular/angular/issues/26711)) ([aed95fd](https://github.com/angular/angular/commit/aed95fd))
|
||||||
|
* **core:** ensure that `ɵdefineNgModule` is available in flat-file formats ([#26403](https://github.com/angular/angular/issues/26403)) ([a64859b](https://github.com/angular/angular/commit/a64859b))
|
||||||
|
* **router:** remove type bludgeoning of context and outlet when running CanDeactivate ([#26496](https://github.com/angular/angular/issues/26496)) ([496372d](https://github.com/angular/angular/commit/496372d)), closes [#18253](https://github.com/angular/angular/issues/18253)
|
||||||
|
* **service-worker:** add typing to public api guard and fix lint errors ([#25860](https://github.com/angular/angular/issues/25860)) ([1061875](https://github.com/angular/angular/commit/1061875))
|
||||||
|
* **upgrade:** improve downgrading-related error messages ([#26217](https://github.com/angular/angular/issues/26217)) ([7dbc103](https://github.com/angular/angular/commit/7dbc103))
|
||||||
|
* **upgrade:** make typings compatible with older AngularJS typings ([#26880](https://github.com/angular/angular/issues/26880)) ([64647af](https://github.com/angular/angular/commit/64647af)), closes [#26420](https://github.com/angular/angular/issues/26420)
|
||||||
|
* **compiler-cli:** add missing tslib dependency ([#27063](https://github.com/angular/angular/issues/27063)) ([c31e78f](https://github.com/angular/angular/commit/c31e78f))
|
||||||
|
* **compiler-cli:** only pass canonical genfile paths to compiler host ([#27062](https://github.com/angular/angular/issues/27062)) ([0ada23a](https://github.com/angular/angular/commit/0ada23a))
|
||||||
|
* **router:** add `relativeLinkResolution` to `recognize` operator ([#26990](https://github.com/angular/angular/issues/26990)) ([a752971](https://github.com/angular/angular/commit/a752971)), closes [#26983](https://github.com/angular/angular/issues/26983)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **bazel:** Bazel workspace schematics ([#26971](https://github.com/angular/angular/issues/26971)) ([b07bd30](https://github.com/angular/angular/commit/b07bd30))
|
||||||
|
* **router:** add prioritizedGuardValue operator optimization and allowing UrlTree return from guard ([#26478](https://github.com/angular/angular/issues/26478)) ([fdfedce](https://github.com/angular/angular/commit/fdfedce))
|
||||||
|
* **compiler:** ability to mark an InvokeFunctionExpr as pure ([#26860](https://github.com/angular/angular/issues/26860)) ([4dfa71f](https://github.com/angular/angular/commit/4dfa71f))
|
||||||
|
* **forms:** add updateOn option to FormBuilder ([#24599](https://github.com/angular/angular/issues/24599)) ([e9e804f](https://github.com/angular/angular/commit/e9e804f))
|
||||||
|
* **router:** allow guards to return UrlTree as well as boolean ([#26521](https://github.com/angular/angular/issues/26521)) ([081f95c](https://github.com/angular/angular/commit/081f95c))
|
||||||
|
* **router:** allow redirect from guards by returning UrlTree ([#26521](https://github.com/angular/angular/issues/26521)) ([152ca66](https://github.com/angular/angular/commit/152ca66))
|
||||||
|
* **router:** guard returning UrlTree cancels current navigation and redirects ([#26521](https://github.com/angular/angular/issues/26521)) ([4e9f2e5](https://github.com/angular/angular/commit/4e9f2e5)), closes [#24618](https://github.com/angular/angular/issues/24618)
|
||||||
|
* **service-worker:** add typing for messagesClicked in SwPush service ([#25860](https://github.com/angular/angular/issues/25860)) ([c78c221](https://github.com/angular/angular/commit/c78c221))
|
||||||
|
* **service-worker:** close notifications and focus window on click ([#25860](https://github.com/angular/angular/issues/25860)) ([f5d5a3d](https://github.com/angular/angular/commit/f5d5a3d))
|
||||||
|
* **service-worker:** handle 'notificationclick' events ([#25860](https://github.com/angular/angular/issues/25860)) ([cf6ea28](https://github.com/angular/angular/commit/cf6ea28)), closes [#20956](https://github.com/angular/angular/issues/20956) [#22311](https://github.com/angular/angular/issues/22311)
|
||||||
|
* **upgrade:** support downgrading multiple modules ([#26217](https://github.com/angular/angular/issues/26217)) ([93837e9](https://github.com/angular/angular/commit/93837e9)), closes [#26062](https://github.com/angular/angular/issues/26062)
|
||||||
|
* **router:** add pathParamsChange mode for runGuardsAndResolvers ([#26861](https://github.com/angular/angular/issues/26861)) ([bf6ac6c](https://github.com/angular/angular/commit/bf6ac6c)), closes [#18253](https://github.com/angular/angular/issues/18253)
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.1.0-rc.0"></a>
|
||||||
|
# [7.1.0-rc.0](https://github.com/angular/angular/compare/7.1.0-beta.2...7.1.0-rc.0) (2018-11-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler-cli:** add missing tslib dependency ([#27063](https://github.com/angular/angular/issues/27063)) ([c31e78f](https://github.com/angular/angular/commit/c31e78f))
|
||||||
|
* **compiler-cli:** only pass canonical genfile paths to compiler host ([#27062](https://github.com/angular/angular/issues/27062)) ([0ada23a](https://github.com/angular/angular/commit/0ada23a))
|
||||||
|
* **router:** add `relativeLinkResolution` to `recognize` operator ([#26990](https://github.com/angular/angular/issues/26990)) ([a752971](https://github.com/angular/angular/commit/a752971)), closes [#26983](https://github.com/angular/angular/issues/26983)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **router:** add pathParamsChange mode for runGuardsAndResolvers ([#26861](https://github.com/angular/angular/issues/26861)) ([bf6ac6c](https://github.com/angular/angular/commit/bf6ac6c)), closes [#18253](https://github.com/angular/angular/issues/18253)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.0.4"></a>
|
||||||
|
## [7.0.4](https://github.com/angular/angular/compare/7.0.3...7.0.4) (2018-11-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler-cli:** add missing tslib dependency ([#27063](https://github.com/angular/angular/issues/27063)) ([4348c47](https://github.com/angular/angular/commit/4348c47))
|
||||||
|
* **compiler-cli:** only pass canonical genfile paths to compiler host ([#27062](https://github.com/angular/angular/issues/27062)) ([188e9ce](https://github.com/angular/angular/commit/188e9ce))
|
||||||
|
* **router:** add `relativeLinkResolution` to `recognize` operator ([#26990](https://github.com/angular/angular/issues/26990)) ([d304427](https://github.com/angular/angular/commit/d304427)), closes [#26983](https://github.com/angular/angular/issues/26983)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.1.0-beta.2"></a>
|
||||||
|
# [7.1.0-beta.2](https://github.com/angular/angular/compare/7.1.0-beta.1...7.1.0-beta.2) (2018-11-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **bazel:** unknown replay compiler error in windows ([#26711](https://github.com/angular/angular/issues/26711)) ([aed95fd](https://github.com/angular/angular/commit/aed95fd))
|
||||||
|
* **core:** ensure that `ɵdefineNgModule` is available in flat-file formats ([#26403](https://github.com/angular/angular/issues/26403)) ([a64859b](https://github.com/angular/angular/commit/a64859b))
|
||||||
|
* **router:** remove type bludgeoning of context and outlet when running CanDeactivate ([#26496](https://github.com/angular/angular/issues/26496)) ([496372d](https://github.com/angular/angular/commit/496372d)), closes [#18253](https://github.com/angular/angular/issues/18253)
|
||||||
|
* **service-worker:** add typing to public api guard and fix lint errors ([#25860](https://github.com/angular/angular/issues/25860)) ([1061875](https://github.com/angular/angular/commit/1061875))
|
||||||
|
* **upgrade:** improve downgrading-related error messages ([#26217](https://github.com/angular/angular/issues/26217)) ([7dbc103](https://github.com/angular/angular/commit/7dbc103))
|
||||||
|
* **upgrade:** make typings compatible with older AngularJS typings ([#26880](https://github.com/angular/angular/issues/26880)) ([64647af](https://github.com/angular/angular/commit/64647af)), closes [#26420](https://github.com/angular/angular/issues/26420)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **compiler:** ability to mark an InvokeFunctionExpr as pure ([#26860](https://github.com/angular/angular/issues/26860)) ([4dfa71f](https://github.com/angular/angular/commit/4dfa71f))
|
||||||
|
* **forms:** add updateOn option to FormBuilder ([#24599](https://github.com/angular/angular/issues/24599)) ([e9e804f](https://github.com/angular/angular/commit/e9e804f))
|
||||||
|
* **router:** allow guards to return UrlTree as well as boolean ([#26521](https://github.com/angular/angular/issues/26521)) ([081f95c](https://github.com/angular/angular/commit/081f95c))
|
||||||
|
* **router:** allow redirect from guards by returning UrlTree ([#26521](https://github.com/angular/angular/issues/26521)) ([152ca66](https://github.com/angular/angular/commit/152ca66))
|
||||||
|
* **router:** guard returning UrlTree cancels current navigation and redirects ([#26521](https://github.com/angular/angular/issues/26521)) ([4e9f2e5](https://github.com/angular/angular/commit/4e9f2e5)), closes [#24618](https://github.com/angular/angular/issues/24618)
|
||||||
|
* **service-worker:** add typing for messagesClicked in SwPush service ([#25860](https://github.com/angular/angular/issues/25860)) ([c78c221](https://github.com/angular/angular/commit/c78c221))
|
||||||
|
* **service-worker:** close notifications and focus window on click ([#25860](https://github.com/angular/angular/issues/25860)) ([f5d5a3d](https://github.com/angular/angular/commit/f5d5a3d))
|
||||||
|
* **service-worker:** handle 'notificationclick' events ([#25860](https://github.com/angular/angular/issues/25860)) ([cf6ea28](https://github.com/angular/angular/commit/cf6ea28)), closes [#20956](https://github.com/angular/angular/issues/20956) [#22311](https://github.com/angular/angular/issues/22311)
|
||||||
|
* **upgrade:** support downgrading multiple modules ([#26217](https://github.com/angular/angular/issues/26217)) ([93837e9](https://github.com/angular/angular/commit/93837e9)), closes [#26062](https://github.com/angular/angular/issues/26062)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.0.3"></a>
|
||||||
|
## [7.0.3](https://github.com/angular/angular/compare/7.0.2...7.0.3) (2018-11-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **bazel:** unknown replay compiler error in windows ([#26711](https://github.com/angular/angular/issues/26711)) ([4d532df](https://github.com/angular/angular/commit/4d532df))
|
||||||
|
* **router:** remove type bludgeoning of context and outlet when running CanDeactivate ([#26496](https://github.com/angular/angular/issues/26496)) ([dc05385](https://github.com/angular/angular/commit/dc05385)), closes [#18253](https://github.com/angular/angular/issues/18253)
|
||||||
|
* **upgrade:** make typings compatible with older AngularJS typings ([#26880](https://github.com/angular/angular/issues/26880)) ([315d95c](https://github.com/angular/angular/commit/315d95c)), closes [#26420](https://github.com/angular/angular/issues/26420)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.1.0-beta.1"></a>
|
||||||
|
# [7.1.0-beta.1](https://github.com/angular/angular/compare/7.1.0-beta.0...7.1.0-beta.1) (2018-10-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler:** generate inputs with aliases properly ([#26774](https://github.com/angular/angular/issues/26774)) ([19fcfc3](https://github.com/angular/angular/commit/19fcfc3))
|
||||||
|
* **compiler:** generate relative paths only in summary file errors ([#26759](https://github.com/angular/angular/issues/26759)) ([56f44be](https://github.com/angular/angular/commit/56f44be))
|
||||||
|
* **core:** ignore comment nodes under unsafe elements ([#25879](https://github.com/angular/angular/issues/25879)) ([d5cbcef](https://github.com/angular/angular/commit/d5cbcef))
|
||||||
|
* **core:** Remove static dependency from [@angular](https://github.com/angular)/core to [@angular](https://github.com/angular)/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([d042c4a](https://github.com/angular/angular/commit/d042c4a))
|
||||||
|
* **core:** support computed base class in metadata inheritance ([#24014](https://github.com/angular/angular/issues/24014)) ([95743e3](https://github.com/angular/angular/commit/95743e3))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.0.2"></a>
|
||||||
|
## [7.0.2](https://github.com/angular/angular/compare/7.0.1...7.0.2) (2018-10-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler:** generate relative paths only in summary file errors ([#26759](https://github.com/angular/angular/issues/26759)) ([c01f340](https://github.com/angular/angular/commit/c01f340))
|
||||||
|
* **core:** Remove static dependency from [@angular](https://github.com/angular)/core to [@angular](https://github.com/angular)/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([#26879](https://github.com/angular/angular/issues/26879)) ([257ac83](https://github.com/angular/angular/commit/257ac83))
|
||||||
|
* **core:** support computed base class in metadata inheritance ([#24014](https://github.com/angular/angular/issues/24014)) ([b3c6409](https://github.com/angular/angular/commit/b3c6409))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.1.0-beta.0"></a>
|
||||||
|
# [7.1.0-beta.0](https://github.com/angular/angular/compare/7.0.0-rc.1...7.1.0-beta.0) (2018-10-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
* **core:** allow null value for renderer setElement(…) ([#17065](https://github.com/angular/angular/issues/17065)) ([ff15043](https://github.com/angular/angular/commit/ff15043)), closes [#13686](https://github.com/angular/angular/issues/13686)
|
||||||
|
* **router:** fix regression where navigateByUrl promise didn't resolve on CanLoad failure ([#26455](https://github.com/angular/angular/issues/26455)) ([1c9b065](https://github.com/angular/angular/commit/1c9b065)), closes [#26284](https://github.com/angular/angular/issues/26284)
|
||||||
|
* **service-worker:** clean up caches from old SW versions ([#26319](https://github.com/angular/angular/issues/26319)) ([2326b9c](https://github.com/angular/angular/commit/2326b9c))
|
||||||
|
* **upgrade:** properly destroy upgraded component elements and descendants ([#26209](https://github.com/angular/angular/issues/26209)) ([071934e](https://github.com/angular/angular/commit/071934e)), closes [#26208](https://github.com/angular/angular/issues/26208)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **router:** add prioritizedGuardValue operator optimization and allowing UrlTree return from guard ([#26478](https://github.com/angular/angular/issues/26478)) ([fdfedce](https://github.com/angular/angular/commit/fdfedce))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.0.1"></a>
|
||||||
|
## [7.0.1](https://github.com/angular/angular/compare/7.0.0...7.0.1) (2018-10-24)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="7.0.0"></a>
|
||||||
|
# [7.0.0](https://github.com/angular/angular/compare/7.0.0-rc.1...7.0.0) (2018-10-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Release Highlights & Update instructions
|
||||||
|
|
||||||
|
To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v7 release announcement](https://blog.angular.io/version-7-of-angular-cli-prompts-virtual-scroll-drag-and-drop-and-more-c594e22e7b8c).
|
||||||
|
|
||||||
|
|
||||||
|
### Dependency updates
|
||||||
|
|
||||||
|
* @angular/core now depends on
|
||||||
|
* TypeScript 3.1
|
||||||
|
* RxJS 6.3
|
||||||
|
* @angular/platform-server now depends on Domino 2.1
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **core:** add DoBootstrap interface. ([#24558](https://github.com/angular/angular/issues/24558)) ([732026c](https://github.com/angular/angular/commit/732026c)), closes [#24557](https://github.com/angular/angular/issues/24557)
|
||||||
|
* **compiler:** add "original" placeholder value on extracted XMB ([#25079](https://github.com/angular/angular/issues/25079)) ([e99d860](https://github.com/angular/angular/commit/e99d860))
|
||||||
|
* **compiler-cli:** add support to extend `angularCompilerOptions` ([#22717](https://github.com/angular/angular/issues/22717)) ([d7e5bbf](https://github.com/angular/angular/commit/d7e5bbf)), closes [#22684](https://github.com/angular/angular/issues/22684)
|
||||||
|
* **bazel:** add additional parameters to `ts_api_guardian_test` def ([#25694](https://github.com/angular/angular/issues/25694)) ([2a21ca0](https://github.com/angular/angular/commit/2a21ca0))
|
||||||
|
* **elements:** enable Shadow DOM v1 and slots ([#24861](https://github.com/angular/angular/issues/24861)) ([c9844a2](https://github.com/angular/angular/commit/c9844a2))
|
||||||
|
* **platform-server:** update domino to v2.1.0 ([#25564](https://github.com/angular/angular/issues/25564)) ([3fb0da2](https://github.com/angular/angular/commit/3fb0da2))
|
||||||
|
* **router:** warn if navigation triggered outside Angular zone ([#24959](https://github.com/angular/angular/issues/24959)) ([010e35d](https://github.com/angular/angular/commit/010e35d)), closes [#15770](https://github.com/angular/angular/issues/15770) [#15946](https://github.com/angular/angular/issues/15946) [#24728](https://github.com/angular/angular/issues/24728)
|
||||||
|
* **router:** add UrlSegment[] to CanLoad interface ([#13127](https://github.com/angular/angular/issues/13127)) ([07d8d39](https://github.com/angular/angular/commit/07d8d39)), closes [#12411](https://github.com/angular/angular/issues/12411)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add mappings for ngfactory & ngsummary files to their module names in aot summary resolver ([#25335](https://github.com/angular/angular/issues/25335)) ([02e201a](https://github.com/angular/angular/commit/02e201a))
|
||||||
* **bazel:** Cache fileNameToModuleName lookups ([#25731](https://github.com/angular/angular/issues/25731)) ([f394ba0](https://github.com/angular/angular/commit/f394ba0))
|
* **bazel:** Cache fileNameToModuleName lookups ([#25731](https://github.com/angular/angular/issues/25731)) ([f394ba0](https://github.com/angular/angular/commit/f394ba0))
|
||||||
|
* **bazel:** allow compile_strategy to be (privately) imported ([#25080](https://github.com/angular/angular/issues/25080)) ([0d1d589](https://github.com/angular/angular/commit/0d1d589))
|
||||||
|
* **bazel:** correct type concatenated to devmode_js ([#25467](https://github.com/angular/angular/issues/25467)) ([fb2c524](https://github.com/angular/angular/commit/fb2c524))
|
||||||
* **bazel:** move bazel managed runtime deps for downstream usage ([#25690](https://github.com/angular/angular/issues/25690)) ([6ed7993](https://github.com/angular/angular/commit/6ed7993))
|
* **bazel:** move bazel managed runtime deps for downstream usage ([#25690](https://github.com/angular/angular/issues/25690)) ([6ed7993](https://github.com/angular/angular/commit/6ed7993))
|
||||||
* **bazel:** only lookup amd module-name tags in .d.ts files ([#25710](https://github.com/angular/angular/issues/25710)) ([42072c4](https://github.com/angular/angular/commit/42072c4))
|
* **bazel:** only lookup amd module-name tags in .d.ts files ([#25710](https://github.com/angular/angular/issues/25710)) ([42072c4](https://github.com/angular/angular/commit/42072c4))
|
||||||
|
* **bazel:** protractor rule should include *.e2e-spec.js ([#25701](https://github.com/angular/angular/issues/25701)) ([3809e0f](https://github.com/angular/angular/commit/3809e0f))
|
||||||
|
* **bazel:** specify the package and lock files using the workspace ([#25694](https://github.com/angular/angular/issues/25694)) ([ddc1335](https://github.com/angular/angular/commit/ddc1335))
|
||||||
|
* **benchpress:** Use performance.mark() instead of console.time() ([#24114](https://github.com/angular/angular/issues/24114)) ([06d0400](https://github.com/angular/angular/commit/06d0400))
|
||||||
|
* **common:** register locale data for all equivalent closure locales ([#25867](https://github.com/angular/angular/issues/25867)) ([d83f9d4](https://github.com/angular/angular/commit/d83f9d4))
|
||||||
|
* **compiler-cli:** correct realPath to realpath. ([#25023](https://github.com/angular/angular/issues/25023)) ([01e6dab](https://github.com/angular/angular/commit/01e6dab))
|
||||||
|
* **compiler-cli:** use the oldProgram option in watch mode ([#21364](https://github.com/angular/angular/issues/21364)) ([c6e5b97](https://github.com/angular/angular/commit/c6e5b97)), closes [#21361](https://github.com/angular/angular/issues/21361)
|
||||||
|
* **compiler:** Fix look up of entryComponents in AOT Summaries ([#24892](https://github.com/angular/angular/issues/24892)) ([00d3666](https://github.com/angular/angular/commit/00d3666))
|
||||||
|
* **compiler:** add hostVars and support pure functions in host bindings ([#25626](https://github.com/angular/angular/issues/25626)) ([b424b31](https://github.com/angular/angular/commit/b424b31))
|
||||||
|
* **compiler:** update compiler to flatten nested template fns ([#24943](https://github.com/angular/angular/issues/24943)) ([fe14f18](https://github.com/angular/angular/commit/fe14f18))
|
||||||
* **compiler:** update compiler to generate new slot allocations ([#25607](https://github.com/angular/angular/issues/25607)) ([27e2039](https://github.com/angular/angular/commit/27e2039))
|
* **compiler:** update compiler to generate new slot allocations ([#25607](https://github.com/angular/angular/issues/25607)) ([27e2039](https://github.com/angular/angular/commit/27e2039))
|
||||||
|
* **core:** In Testability.whenStable update callback, pass more complete ([#25010](https://github.com/angular/angular/issues/25010)) ([16c03c0](https://github.com/angular/angular/commit/16c03c0))
|
||||||
|
* **core:** add missing `peerDependency ` to `[@angular](https://github.com/angular)/compiler` ([#26033](https://github.com/angular/angular/issues/26033)) ([549de1e](https://github.com/angular/angular/commit/549de1e)), closes [/github.com/angular/angular/commit/919f42fea1df4b9e38b7d688aef5f2de668e9d3e#diff-58563046c4439699f2e6a89187099a54](https://github.com//github.com/angular/angular/commit/919f42fea1df4b9e38b7d688aef5f2de668e9d3e/issues/diff-58563046c4439699f2e6a89187099a54)
|
||||||
|
* **core:** allow null value for renderer setElement(…) ([#17065](https://github.com/angular/angular/issues/17065)) ([ff15043](https://github.com/angular/angular/commit/ff15043)), closes [#13686](https://github.com/angular/angular/issues/13686)
|
||||||
|
* **core:** do not clear element content when using shadow dom ([#24861](https://github.com/angular/angular/issues/24861)) ([6e828bb](https://github.com/angular/angular/commit/6e828bb))
|
||||||
|
* **core:** size regression with closure compiler ([#25531](https://github.com/angular/angular/issues/25531)) ([1f59f2f](https://github.com/angular/angular/commit/1f59f2f))
|
||||||
|
* **core:** throw error message when @Output not initialized ([#19116](https://github.com/angular/angular/issues/19116)) ([adf510f](https://github.com/angular/angular/commit/adf510f)), closes [#3664](https://github.com/angular/angular/issues/3664)
|
||||||
|
* **elements:** add compiler dependency ([#24861](https://github.com/angular/angular/issues/24861)) ([6143da6](https://github.com/angular/angular/commit/6143da6))
|
||||||
|
* **elements:** add compiler to integration ([#24861](https://github.com/angular/angular/issues/24861)) ([a080ffc](https://github.com/angular/angular/commit/a080ffc))
|
||||||
|
* **elements:** strict null checks ([#24861](https://github.com/angular/angular/issues/24861)) ([a8210d0](https://github.com/angular/angular/commit/a8210d0))
|
||||||
|
* **router:** fix regression where navigateByUrl promise didn't resolve on CanLoad failure ([#26455](https://github.com/angular/angular/issues/26455)) ([1c9b065](https://github.com/angular/angular/commit/1c9b065)), closes [#26284](https://github.com/angular/angular/issues/26284)
|
||||||
|
* **router:** mount correct component if router outlet was not instantiated and if using a route reuse strategy ([#25313](https://github.com/angular/angular/issues/25313)) ([#25314](https://github.com/angular/angular/issues/25314)) ([8dc2b11](https://github.com/angular/angular/commit/8dc2b11))
|
||||||
|
* **router:** take base uri into account in `setUpLocationSync()` ([#20244](https://github.com/angular/angular/issues/20244)) ([ba1e25f](https://github.com/angular/angular/commit/ba1e25f)), closes [#20061](https://github.com/angular/angular/issues/20061)
|
||||||
|
* **service-worker:** clean up caches from old SW versions ([#26319](https://github.com/angular/angular/issues/26319)) ([00b5c7b](https://github.com/angular/angular/commit/00b5c7b))
|
||||||
|
* **service-worker:** do not blow up when caches are unwritable ([#26042](https://github.com/angular/angular/issues/26042)) ([2bd767c](https://github.com/angular/angular/commit/2bd767c))
|
||||||
|
* **upgrade:** properly destroy upgraded component elements and descendants ([#26209](https://github.com/angular/angular/issues/26209)) ([071934e](https://github.com/angular/angular/commit/071934e)), closes [#26208](https://github.com/angular/angular/issues/26208)
|
||||||
|
* **upgrade:** trigger `$destroy` event on upgraded component element ([#25357](https://github.com/angular/angular/issues/25357)) ([2a672a9](https://github.com/angular/angular/commit/2a672a9)), closes [#25334](https://github.com/angular/angular/issues/25334)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="6.1.10"></a>
|
||||||
|
## [6.1.10](https://github.com/angular/angular/compare/6.1.9...6.1.10) (2018-10-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **platform-browser:** fix [#22155](https://github.com/angular/angular/issues/22155), destroy hammer manager when `HammerInstance.off()` is run ([#22156](https://github.com/angular/angular/issues/22156)) ([3b4d9dc](https://github.com/angular/angular/commit/3b4d9dc))
|
||||||
|
* **upgrade:** properly destroy upgraded component elements and descendants ([#26209](https://github.com/angular/angular/issues/26209)) ([623adbb](https://github.com/angular/angular/commit/623adbb)), closes [#26208](https://github.com/angular/angular/issues/26208)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="6.1.9"></a>
|
||||||
|
## [6.1.9](https://github.com/angular/angular/compare/6.1.8...6.1.9) (2018-09-26)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="6.1.7"></a>
|
||||||
|
## [6.1.7](https://github.com/angular/angular/compare/6.1.6...6.1.7) (2018-09-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **bazel:** protractor rule should include *.e2e-spec.js ([#25701](https://github.com/angular/angular/issues/25701)) ([ed6b68b](https://github.com/angular/angular/commit/ed6b68b))
|
||||||
|
* **core:** size regression with closure compiler ([#25531](https://github.com/angular/angular/issues/25531)) ([ebcf762](https://github.com/angular/angular/commit/ebcf762))
|
||||||
|
* **docs-infra:** show "suggest edits" only for /guide and /tutorial dirs ([#24378](https://github.com/angular/angular/issues/24378)) ([66b7870](https://github.com/angular/angular/commit/66b7870))
|
||||||
|
* **upgrade:** trigger `$destroy` event on upgraded component element ([#25357](https://github.com/angular/angular/issues/25357)) ([82e0676](https://github.com/angular/angular/commit/82e0676)), closes [#25334](https://github.com/angular/angular/issues/25334)
|
||||||
|
* **router:** warn if navigation triggered outside Angular zone ([#24959](https://github.com/angular/angular/issues/24959)) ([23a96dc](https://github.com/angular/angular/commit/23a96dc)), closes [#15770](https://github.com/angular/angular/issues/15770) [#15946](https://github.com/angular/angular/issues/15946) [#24728](https://github.com/angular/angular/issues/24728)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -23,14 +326,6 @@
|
|||||||
|
|
||||||
Note: the 6.1.5 release on npm accidentally glitched-out midway, so we cut 6.1.6 instead. sorry! :-)
|
Note: the 6.1.5 release on npm accidentally glitched-out midway, so we cut 6.1.6 instead. sorry! :-)
|
||||||
|
|
||||||
<a name="7.0.0-beta.3"></a>
|
|
||||||
# [7.0.0-beta.3](https://github.com/angular/angular/compare/7.0.0-beta.2...7.0.0-beta.3) (2018-08-22)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **router:** add UrlSegment[] to CanLoad interface ([#13127](https://github.com/angular/angular/issues/13127)) ([07d8d39](https://github.com/angular/angular/commit/07d8d39)), closes [#12411](https://github.com/angular/angular/issues/12411)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="6.1.4"></a>
|
<a name="6.1.4"></a>
|
||||||
@ -43,15 +338,6 @@ Note: the 6.1.5 release on npm accidentally glitched-out midway, so we cut 6.1.6
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.0.0-beta.2"></a>
|
|
||||||
# [7.0.0-beta.2](https://github.com/angular/angular/compare/7.0.0-beta.1...7.0.0-beta.2) (2018-08-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **bazel:** correct type concatenated to devmode_js ([#25467](https://github.com/angular/angular/issues/25467)) ([fb2c524](https://github.com/angular/angular/commit/fb2c524))
|
|
||||||
|
|
||||||
|
|
||||||
<a name="6.1.3"></a>
|
<a name="6.1.3"></a>
|
||||||
## [6.1.3](https://github.com/angular/angular/compare/6.1.2...6.1.3) (2018-08-15)
|
## [6.1.3](https://github.com/angular/angular/compare/6.1.2...6.1.3) (2018-08-15)
|
||||||
|
|
||||||
@ -62,24 +348,6 @@ Note: the 6.1.5 release on npm accidentally glitched-out midway, so we cut 6.1.6
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="7.0.0-beta.1"></a>
|
|
||||||
# [7.0.0-beta.1](https://github.com/angular/angular/compare/7.0.0-beta.0...7.0.0-beta.1) (2018-08-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler-cli:** use the oldProgram option in watch mode ([#21364](https://github.com/angular/angular/issues/21364)) ([c6e5b97](https://github.com/angular/angular/commit/c6e5b97)), closes [#21361](https://github.com/angular/angular/issues/21361)
|
|
||||||
* **core:** In Testability.whenStable update callback, pass more complete ([#25010](https://github.com/angular/angular/issues/25010)) ([16c03c0](https://github.com/angular/angular/commit/16c03c0))
|
|
||||||
* add mappings for ngfactory & ngsummary files to their module names in aot summary resolver ([#25335](https://github.com/angular/angular/issues/25335)) ([02e201a](https://github.com/angular/angular/commit/02e201a))
|
|
||||||
* **router:** take base uri into account in `setUpLocationSync()` ([#20244](https://github.com/angular/angular/issues/20244)) ([ba1e25f](https://github.com/angular/angular/commit/ba1e25f)), closes [#20061](https://github.com/angular/angular/issues/20061)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **core:** add DoBootstrap interface. ([#24558](https://github.com/angular/angular/issues/24558)) ([732026c](https://github.com/angular/angular/commit/732026c)), closes [#24557](https://github.com/angular/angular/issues/24557)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="6.1.2"></a>
|
<a name="6.1.2"></a>
|
||||||
## [6.1.2](https://github.com/angular/angular/compare/6.1.1...6.1.2) (2018-08-08)
|
## [6.1.2](https://github.com/angular/angular/compare/6.1.1...6.1.2) (2018-08-08)
|
||||||
|
|
||||||
@ -90,22 +358,6 @@ Note: the 6.1.5 release on npm accidentally glitched-out midway, so we cut 6.1.6
|
|||||||
* add mappings for ngfactory & ngsummary files to their module names in aot summary resolver ([#25335](https://github.com/angular/angular/issues/25335)) ([054fbbe](https://github.com/angular/angular/commit/054fbbe))
|
* add mappings for ngfactory & ngsummary files to their module names in aot summary resolver ([#25335](https://github.com/angular/angular/issues/25335)) ([054fbbe](https://github.com/angular/angular/commit/054fbbe))
|
||||||
|
|
||||||
|
|
||||||
<a name="7.0.0-beta.0"></a>
|
|
||||||
# [7.0.0-beta.0](https://github.com/angular/angular/compare/6.1.0...7.0.0-beta.0) (2018-08-02)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **bazel:** allow compile_strategy to be (privately) imported ([#25080](https://github.com/angular/angular/issues/25080)) ([0d1d589](https://github.com/angular/angular/commit/0d1d589))
|
|
||||||
* **compiler:** update compiler to flatten nested template fns ([#24943](https://github.com/angular/angular/issues/24943)) ([fe14f18](https://github.com/angular/angular/commit/fe14f18))
|
|
||||||
* **compiler-cli:** correct realPath to realpath. ([#25023](https://github.com/angular/angular/issues/25023)) ([01e6dab](https://github.com/angular/angular/commit/01e6dab))
|
|
||||||
* **core:** throw error message when @Output not initialized ([#19116](https://github.com/angular/angular/issues/19116)) ([adf510f](https://github.com/angular/angular/commit/adf510f)), closes [#3664](https://github.com/angular/angular/issues/3664)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **compiler:** add "original" placeholder value on extracted XMB ([#25079](https://github.com/angular/angular/issues/25079)) ([e99d860](https://github.com/angular/angular/commit/e99d860))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="6.1.1"></a>
|
<a name="6.1.1"></a>
|
||||||
## [6.1.1](https://github.com/angular/angular/compare/6.1.0...6.1.1) (2018-08-02)
|
## [6.1.1](https://github.com/angular/angular/compare/6.1.0...6.1.1) (2018-08-02)
|
||||||
|
@ -51,19 +51,15 @@ and help you to craft the change so that it is successfully accepted into the pr
|
|||||||
|
|
||||||
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
|
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
|
||||||
|
|
||||||
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions like:
|
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we will systematically ask you to provide a minimal reproduction. Having a minimal reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions.
|
||||||
|
|
||||||
- version of Angular used
|
A minimal reproduction allows us to quickly confirm a bug (or point out a coding problem) as well as confirm that we are fixing the right problem.
|
||||||
- 3rd-party libraries and their versions
|
|
||||||
- and most importantly - a use-case that fails
|
|
||||||
|
|
||||||
A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem. If plunker is not a suitable way to demonstrate the problem (for example for issues related to our npm packaging), please create a standalone git repository demonstrating the problem.
|
We will be insisting on a minimal reproduction scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal reproduction. We understand that sometimes it might be hard to extract essential bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
|
||||||
|
|
||||||
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
|
|
||||||
|
|
||||||
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.
|
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.
|
||||||
|
|
||||||
You can file new issues by filling out our [new issue form](https://github.com/angular/angular/issues/new).
|
You can file new issues by selecting from our [new issue templates](https://github.com/angular/angular/issues/new/choose) and filling out the issue template.
|
||||||
|
|
||||||
|
|
||||||
### <a name="submit-pr"></a> Submitting a Pull Request (PR)
|
### <a name="submit-pr"></a> Submitting a Pull Request (PR)
|
||||||
@ -71,6 +67,8 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
|||||||
|
|
||||||
1. Search [GitHub](https://github.com/angular/angular/pulls) for an open or closed PR
|
1. Search [GitHub](https://github.com/angular/angular/pulls) for an open or closed PR
|
||||||
that relates to your submission. You don't want to duplicate effort.
|
that relates to your submission. You don't want to duplicate effort.
|
||||||
|
1. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add.
|
||||||
|
Discussing the design up front helps to ensure that we're ready to accept your work.
|
||||||
1. Please sign our [Contributor License Agreement (CLA)](#cla) before sending PRs.
|
1. Please sign our [Contributor License Agreement (CLA)](#cla) before sending PRs.
|
||||||
We cannot accept code without this. Make sure you sign with the primary email address of the Git identity that has been granted access to the Angular repository.
|
We cannot accept code without this. Make sure you sign with the primary email address of the Git identity that has been granted access to the Angular repository.
|
||||||
1. Fork the angular/angular repo.
|
1. Fork the angular/angular repo.
|
||||||
|
@ -13,12 +13,10 @@ Angular is a development platform for building mobile and desktop web applicatio
|
|||||||
|
|
||||||
[Get started in 5 minutes][quickstart].
|
[Get started in 5 minutes][quickstart].
|
||||||
|
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
[Learn about the latest improvements][changelog].
|
[Learn about the latest improvements][changelog].
|
||||||
|
|
||||||
|
|
||||||
## Want to help?
|
## Want to help?
|
||||||
|
|
||||||
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our
|
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our
|
||||||
|
145
WORKSPACE
145
WORKSPACE
@ -1,96 +1,52 @@
|
|||||||
workspace(name = "angular")
|
workspace(name = "angular")
|
||||||
|
|
||||||
#
|
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||||
# Download Bazel toolchain dependencies as needed by build actions
|
load(
|
||||||
#
|
"//packages/bazel:package.bzl",
|
||||||
http_archive(
|
"rules_angular_dependencies",
|
||||||
name = "build_bazel_rules_nodejs",
|
"rules_angular_dev_dependencies",
|
||||||
urls = ["https://github.com/bazelbuild/rules_nodejs/archive/0.12.0.zip"],
|
|
||||||
strip_prefix = "rules_nodejs-0.12.0",
|
|
||||||
sha256 = "2977cdbc8ae0eed7d4186385af56a50a3321a549e2136a959998bba89d2edb6e",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "build_bazel_rules_typescript",
|
name = "io_bazel_rules_go",
|
||||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.16.2.zip",
|
sha256 = "b7a62250a3a73277ade0ce306d22f122365b513f5402222403e507f2f997d421",
|
||||||
strip_prefix = "rules_typescript-0.16.2",
|
url = "https://github.com/bazelbuild/rules_go/releases/download/0.16.3/rules_go-0.16.3.tar.gz",
|
||||||
sha256 = "31601b777840fbf600dbd1893ade0d1de37166e7ba52b90735b107cfb67e38c7",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Uncomment for local bazel rules development
|
||||||
|
#local_repository(
|
||||||
|
# name = "build_bazel_rules_nodejs",
|
||||||
|
# path = "../rules_nodejs",
|
||||||
|
#)
|
||||||
|
#local_repository(
|
||||||
|
# name = "build_bazel_rules_typescript",
|
||||||
|
# path = "../rules_typescript",
|
||||||
|
#)
|
||||||
|
|
||||||
|
# Angular Bazel users will call this function
|
||||||
|
rules_angular_dependencies()
|
||||||
|
|
||||||
|
# Install transitive deps of rules_nodejs
|
||||||
|
load("@build_bazel_rules_nodejs//:package.bzl", "rules_nodejs_dependencies")
|
||||||
|
|
||||||
|
rules_nodejs_dependencies()
|
||||||
|
|
||||||
|
# These are the dependencies only for us
|
||||||
|
rules_angular_dev_dependencies()
|
||||||
|
|
||||||
|
# Install transitive deps of rules_typescript
|
||||||
load("@build_bazel_rules_typescript//:package.bzl", "rules_typescript_dependencies")
|
load("@build_bazel_rules_typescript//:package.bzl", "rules_typescript_dependencies")
|
||||||
|
|
||||||
rules_typescript_dependencies()
|
rules_typescript_dependencies()
|
||||||
|
|
||||||
http_archive(
|
|
||||||
name = "bazel_toolchains",
|
|
||||||
urls = [
|
|
||||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/5124557861ebf4c0b67f98180bff1f8551e0b421.tar.gz",
|
|
||||||
"https://github.com/bazelbuild/bazel-toolchains/archive/5124557861ebf4c0b67f98180bff1f8551e0b421.tar.gz",
|
|
||||||
],
|
|
||||||
strip_prefix = "bazel-toolchains-5124557861ebf4c0b67f98180bff1f8551e0b421",
|
|
||||||
sha256 = "c3b08805602cd1d2b67ebe96407c1e8c6ed3d4ce55236ae2efe2f1948f38168d",
|
|
||||||
)
|
|
||||||
|
|
||||||
http_archive(
|
|
||||||
name = "io_bazel_rules_sass",
|
|
||||||
url = "https://github.com/bazelbuild/rules_sass/archive/1.11.0.zip",
|
|
||||||
strip_prefix = "rules_sass-1.11.0",
|
|
||||||
sha256 = "dbe9fb97d5a7833b2a733eebc78c9c1e3880f676ac8af16e58ccf2139cbcad03",
|
|
||||||
)
|
|
||||||
|
|
||||||
# This commit matches the version of buildifier in angular/ngcontainer
|
|
||||||
# If you change this, also check if it matches the version in the angular/ngcontainer
|
|
||||||
# version in /.circleci/config.yml
|
|
||||||
BAZEL_BUILDTOOLS_VERSION = "82b21607e00913b16fe1c51bec80232d9d6de31c"
|
|
||||||
|
|
||||||
http_archive(
|
|
||||||
name = "com_github_bazelbuild_buildtools",
|
|
||||||
url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION,
|
|
||||||
strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION,
|
|
||||||
sha256 = "edb24c2f9c55b10a820ec74db0564415c0cf553fa55e9fc709a6332fb6685eff",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fetching the Bazel source code allows us to compile the Skylark linter
|
|
||||||
http_archive(
|
|
||||||
name = "io_bazel",
|
|
||||||
url = "https://github.com/bazelbuild/bazel/archive/968f87900dce45a7af749a965b72dbac51b176b3.zip",
|
|
||||||
strip_prefix = "bazel-968f87900dce45a7af749a965b72dbac51b176b3",
|
|
||||||
sha256 = "e373d2ae24955c1254c495c9c421c009d88966565c35e4e8444c082cb1f0f48f",
|
|
||||||
)
|
|
||||||
|
|
||||||
http_archive(
|
|
||||||
name = "io_bazel_skydoc",
|
|
||||||
# TODO: switch to upstream when https://github.com/bazelbuild/skydoc/pull/103 is merged
|
|
||||||
url = "https://github.com/alexeagle/skydoc/archive/fe2e9f888d28e567fef62ec9d4a93c425526d701.zip",
|
|
||||||
strip_prefix = "skydoc-fe2e9f888d28e567fef62ec9d4a93c425526d701",
|
|
||||||
sha256 = "7bfb5545f59792a2745f2523b9eef363f9c3e7274791c030885e7069f8116016",
|
|
||||||
)
|
|
||||||
|
|
||||||
# We have a source dependency on the Devkit repository, because it's built with
|
|
||||||
# Bazel.
|
|
||||||
# This allows us to edit sources and have the effect appear immediately without
|
|
||||||
# re-packaging or "npm link"ing.
|
|
||||||
# Even better, things like aspects will visit the entire graph including
|
|
||||||
# ts_library rules in the devkit repository.
|
|
||||||
http_archive(
|
|
||||||
name = "angular_cli",
|
|
||||||
url = "https://github.com/angular/angular-cli/archive/v6.1.0-rc.0.zip",
|
|
||||||
strip_prefix = "angular-cli-6.1.0-rc.0",
|
|
||||||
sha256 = "8cf320ea58c321e103f39087376feea502f20eaf79c61a4fdb05c7286c8684fd",
|
|
||||||
)
|
|
||||||
|
|
||||||
http_archive(
|
|
||||||
name = "org_brotli",
|
|
||||||
url = "https://github.com/google/brotli/archive/v1.0.5.zip",
|
|
||||||
strip_prefix = "brotli-1.0.5",
|
|
||||||
sha256 = "774b893a0700b0692a76e2e5b7e7610dbbe330ffbe3fe864b4b52ca718061d5a",
|
|
||||||
)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Point Bazel to WORKSPACEs that live in subdirectories
|
# Point Bazel to WORKSPACEs that live in subdirectories
|
||||||
#
|
#
|
||||||
|
http_archive(
|
||||||
local_repository(
|
|
||||||
name = "rxjs",
|
name = "rxjs",
|
||||||
path = "node_modules/rxjs/src",
|
sha256 = "72b0b4e517f43358f554c125e40e39f67688cd2738a8998b4a266981ed32f403",
|
||||||
|
strip_prefix = "package/src",
|
||||||
|
url = "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Point to the integration test workspace just so that Bazel doesn't descend into it
|
# Point to the integration test workspace just so that Bazel doesn't descend into it
|
||||||
@ -103,27 +59,38 @@ local_repository(
|
|||||||
#
|
#
|
||||||
# Load and install our dependencies downloaded above.
|
# Load and install our dependencies downloaded above.
|
||||||
#
|
#
|
||||||
|
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
|
||||||
|
|
||||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
|
check_bazel_version("0.20.0", """
|
||||||
|
You no longer need to install Bazel on your machine.
|
||||||
check_bazel_version("0.16.0", """
|
Angular has a dependency on the @bazel/bazel package which supplies it.
|
||||||
If you are on a Mac and using Homebrew, there is a breaking change to the installation in Bazel 0.16
|
Try running `yarn bazel` instead.
|
||||||
See https://blog.bazel.build/2018/08/22/bazel-homebrew.html
|
(If you did run that, check that you've got a fresh `yarn install`)
|
||||||
|
|
||||||
""")
|
""")
|
||||||
|
|
||||||
node_repositories(
|
node_repositories(
|
||||||
package_json = ["//:package.json"],
|
node_version = "10.9.0",
|
||||||
preserve_symlinks = True,
|
package_json = ["//:package.json"],
|
||||||
|
preserve_symlinks = True,
|
||||||
|
yarn_version = "1.12.1",
|
||||||
)
|
)
|
||||||
|
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
|
local_repository(
|
||||||
|
name = "npm",
|
||||||
|
path = "tools/npm_workspace",
|
||||||
|
)
|
||||||
|
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_register_toolchains", "go_rules_dependencies")
|
||||||
|
|
||||||
go_rules_dependencies()
|
go_rules_dependencies()
|
||||||
|
|
||||||
go_register_toolchains()
|
go_register_toolchains()
|
||||||
|
|
||||||
load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories")
|
load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories")
|
||||||
|
|
||||||
web_test_repositories()
|
web_test_repositories()
|
||||||
|
|
||||||
browser_repositories(
|
browser_repositories(
|
||||||
chromium = True,
|
chromium = True,
|
||||||
firefox = True,
|
firefox = True,
|
||||||
@ -141,7 +108,9 @@ ng_setup_workspace()
|
|||||||
# Skylark documentation generation
|
# Skylark documentation generation
|
||||||
|
|
||||||
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
|
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
|
||||||
|
|
||||||
sass_repositories()
|
sass_repositories()
|
||||||
|
|
||||||
load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories")
|
load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories")
|
||||||
|
|
||||||
skydoc_repositories()
|
skydoc_repositories()
|
||||||
|
3
aio/.gitignore
vendored
3
aio/.gitignore
vendored
@ -44,6 +44,3 @@ protractor-results*.txt
|
|||||||
# System Files
|
# System Files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# copied dependencies
|
|
||||||
src/assets/js/lunr*
|
|
||||||
|
@ -41,8 +41,6 @@ Here are the most important tasks you might need to use:
|
|||||||
- `yarn example-e2e --filter=foo` - limit e2e tests to those containing the word "foo"
|
- `yarn example-e2e --filter=foo` - limit e2e tests to those containing the word "foo"
|
||||||
- `yarn example-e2e --setup --local` - run e2e tests with the local version of Angular contained in the "dist" folder
|
- `yarn example-e2e --setup --local` - run e2e tests with the local version of Angular contained in the "dist" folder
|
||||||
|
|
||||||
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.
|
|
||||||
|
|
||||||
## Developing on Windows
|
## Developing on Windows
|
||||||
The `packages/` directory may contain Linux-specific symlinks, which are not recognized by Windows.
|
The `packages/` directory may contain Linux-specific symlinks, which are not recognized by Windows.
|
||||||
These unresolved links cause the docs generation process to fail because it cannot locate certain files.
|
These unresolved links cause the docs generation process to fail because it cannot locate certain files.
|
||||||
@ -50,7 +48,7 @@ These unresolved links cause the docs generation process to fail because it cann
|
|||||||
> Hint: The following steps require administration rights or [Windows Developer Mode](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development) enabled!
|
> Hint: The following steps require administration rights or [Windows Developer Mode](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development) enabled!
|
||||||
|
|
||||||
To fix this problem, run `scripts/windows/create-symlinks.sh`. This command creates temporary files where the symlinks used to be. Make sure not to commit those files with your documentation changes.
|
To fix this problem, run `scripts/windows/create-symlinks.sh`. This command creates temporary files where the symlinks used to be. Make sure not to commit those files with your documentation changes.
|
||||||
When you are done making and testing your documentation changes, you can restore the original symlinks and delete the temporary files by running `scripts/windows/remove-symlinks.sh`.
|
When you are done making and testing your documentation changes, you can restore the original symlinks and delete the temporary files by running `scripts/windows/remove-symlinks.sh`.
|
||||||
|
|
||||||
It's necessary to remove the temporary files, because otherwise they're displayed as local changes in your git working copy and certain operations are blocked.
|
It's necessary to remove the temporary files, because otherwise they're displayed as local changes in your git working copy and certain operations are blocked.
|
||||||
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
# Periodically clean up builds that do not correspond to currently open PRs
|
# Periodically clean up builds that do not correspond to currently open PRs
|
||||||
0 12 * * * root /usr/local/bin/aio-clean-up >> /var/log/cron.log 2>&1
|
0 12 * * * /usr/local/bin/aio-clean-up >> /var/log/cron.log 2>&1
|
||||||
|
@ -36,6 +36,11 @@ server {
|
|||||||
access_log {{$AIO_NGINX_LOGS_DIR}}/access.log;
|
access_log {{$AIO_NGINX_LOGS_DIR}}/access.log;
|
||||||
error_log {{$AIO_NGINX_LOGS_DIR}}/error.log;
|
error_log {{$AIO_NGINX_LOGS_DIR}}/error.log;
|
||||||
|
|
||||||
|
error_page 404 /404.html;
|
||||||
|
location "=/404.html" {
|
||||||
|
internal;
|
||||||
|
}
|
||||||
|
|
||||||
location "~/[^/]+\.[^/]+$" {
|
location "~/[^/]+\.[^/]+$" {
|
||||||
try_files $uri $uri/ =404;
|
try_files $uri $uri/ =404;
|
||||||
}
|
}
|
||||||
@ -66,6 +71,21 @@ server {
|
|||||||
return 200 '';
|
return 200 '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check PRs previewability
|
||||||
|
location "~^/can-have-public-preview/\d+/?$" {
|
||||||
|
if ($request_method != "GET") {
|
||||||
|
add_header Allow "GET";
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy_pass_request_headers on;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_method GET;
|
||||||
|
proxy_pass http://{{$AIO_PREVIEW_SERVER_HOSTNAME}}:{{$AIO_PREVIEW_SERVER_PORT}}$request_uri;
|
||||||
|
|
||||||
|
resolver 127.0.0.1;
|
||||||
|
}
|
||||||
|
|
||||||
# Notify about CircleCI builds
|
# Notify about CircleCI builds
|
||||||
location "~^/circle-build/?$" {
|
location "~^/circle-build/?$" {
|
||||||
if ($request_method != "POST") {
|
if ($request_method != "POST") {
|
||||||
|
@ -5,12 +5,12 @@ import * as shell from 'shelljs';
|
|||||||
import {HIDDEN_DIR_PREFIX} from '../common/constants';
|
import {HIDDEN_DIR_PREFIX} from '../common/constants';
|
||||||
import {GithubApi} from '../common/github-api';
|
import {GithubApi} from '../common/github-api';
|
||||||
import {GithubPullRequests} from '../common/github-pull-requests';
|
import {GithubPullRequests} from '../common/github-pull-requests';
|
||||||
import {assertNotMissingOrEmpty, createLogger, getPrInfoFromDownloadPath} from '../common/utils';
|
import {assertNotMissingOrEmpty, getPrInfoFromDownloadPath, Logger} from '../common/utils';
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
export class BuildCleaner {
|
export class BuildCleaner {
|
||||||
|
|
||||||
private logger = createLogger('BuildCleaner');
|
private logger = new Logger('BuildCleaner');
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
constructor(protected buildsDir: string, protected githubOrg: string, protected githubRepo: string,
|
constructor(protected buildsDir: string, protected githubOrg: string, protected githubRepo: string,
|
||||||
@ -122,6 +122,6 @@ export class BuildCleaner {
|
|||||||
this.logger.log(`Existing downloads: ${existingDownloads.length}`);
|
this.logger.log(`Existing downloads: ${existingDownloads.length}`);
|
||||||
this.logger.log(`Removing ${toRemove.length} download(s): ${toRemove.join(', ')}`);
|
this.logger.log(`Removing ${toRemove.length} download(s): ${toRemove.join(', ')}`);
|
||||||
|
|
||||||
toRemove.forEach(filePath => shell.rm(filePath));
|
toRemove.forEach(filePath => shell.rm(path.join(this.downloadsDir, filePath)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ export class CircleCiApi {
|
|||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw new Error(`${baseUrl}: ${response.status} - ${response.statusText}`);
|
throw new Error(`${baseUrl}: ${response.status} - ${response.statusText}`);
|
||||||
}
|
}
|
||||||
return response.json<BuildInfo>();
|
return response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`CircleCI build info request failed (${error.message})`);
|
throw new Error(`CircleCI build info request failed (${error.message})`);
|
||||||
}
|
}
|
||||||
@ -77,7 +77,7 @@ export class CircleCiApi {
|
|||||||
const baseUrl = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`;
|
const baseUrl = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${baseUrl}/artifacts?${this.tokenParam}`);
|
const response = await fetch(`${baseUrl}/artifacts?${this.tokenParam}`);
|
||||||
const artifacts = await response.json<ArtifactResponse>();
|
const artifacts = await response.json() as ArtifactResponse;
|
||||||
const artifact = artifacts.find(item => item.path === artifactPath);
|
const artifact = artifacts.find(item => item.path === artifactPath);
|
||||||
if (!artifact) {
|
if (!artifact) {
|
||||||
throw new Error(`Missing artifact (${artifactPath}) for CircleCI build: ${buildNumber}`);
|
throw new Error(`Missing artifact (${artifactPath}) for CircleCI build: ${buildNumber}`);
|
||||||
|
@ -38,7 +38,8 @@ export class GithubApi {
|
|||||||
return this.request<T>('post', path, data);
|
return this.request<T>('post', path, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPaginated<T>(pathname: string, baseParams: RequestParams = {}, currentPage: number = 0): Promise<T[]> {
|
// In GitHub API paginated requests, page numbering is 1-based. (https://developer.github.com/v3/#pagination)
|
||||||
|
public getPaginated<T>(pathname: string, baseParams: RequestParams = {}, currentPage: number = 1): Promise<T[]> {
|
||||||
const perPage = 100;
|
const perPage = 100;
|
||||||
const params = {
|
const params = {
|
||||||
...baseParams,
|
...baseParams,
|
||||||
|
@ -74,6 +74,6 @@ export class GithubPullRequests {
|
|||||||
*/
|
*/
|
||||||
public fetchFiles(pr: number): Promise<FileInfo[]> {
|
public fetchFiles(pr: number): Promise<FileInfo[]> {
|
||||||
assert(pr > 0, `Invalid PR number: ${pr}`);
|
assert(pr > 0, `Invalid PR number: ${pr}`);
|
||||||
return this.api.get<FileInfo[]>(`/repos/${this.repoSlug}/pulls/${pr}/files`);
|
return this.api.getPaginated<FileInfo>(`/repos/${this.repoSlug}/pulls/${pr}/files`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
export const runTests = (specFiles: string[], helpers?: string[]) => {
|
// We can't use `import...from` here, because of the following mess:
|
||||||
// We can't use `import` here, because of the following mess:
|
// - GitHub project `jasmine/jasmine` is `jasmine-core` on npm and its typings `@types/jasmine`.
|
||||||
// - GitHub project `jasmine/jasmine` is `jasmine-core` on npm and its typings `@types/jasmine`.
|
// - GitHub project `jasmine/jasmine-npm` is `jasmine` on npm and has no typings.
|
||||||
// - GitHub project `jasmine/jasmine-npm` is `jasmine` on npm and has no typings.
|
//
|
||||||
//
|
// Using `import...from 'jasmine'` here, would import from `@types/jasmine` (which refers to the
|
||||||
// Using `import...from 'jasmine'` here, would import from `@types/jasmine` (which refers to the
|
// `jasmine-core` module and the `jasmine` module).
|
||||||
// `jasmine-core` module and the `jasmine` module).
|
import Jasmine = require('jasmine');
|
||||||
// tslint:disable-next-line: no-var-requires variable-name
|
import 'source-map-support/register';
|
||||||
const Jasmine = require('jasmine');
|
|
||||||
|
export const runTests = (specFiles: string[]) => {
|
||||||
const config = {
|
const config = {
|
||||||
helpers,
|
|
||||||
random: true,
|
random: true,
|
||||||
spec_files: specFiles,
|
spec_files: specFiles,
|
||||||
stopSpecOnExpectationFailure: true,
|
stopSpecOnExpectationFailure: true,
|
||||||
@ -16,7 +16,7 @@ export const runTests = (specFiles: string[], helpers?: string[]) => {
|
|||||||
|
|
||||||
process.on('unhandledRejection', (reason: any) => console.log('Unhandled rejection:', reason));
|
process.on('unhandledRejection', (reason: any) => console.log('Unhandled rejection:', reason));
|
||||||
|
|
||||||
const runner = new Jasmine();
|
const runner = new Jasmine({});
|
||||||
runner.loadConfig(config);
|
runner.loadConfig(config);
|
||||||
runner.onComplete((passed: boolean) => process.exit(passed ? 0 : 1));
|
runner.onComplete((passed: boolean) => process.exit(passed ? 0 : 1));
|
||||||
runner.execute();
|
runner.execute();
|
||||||
|
@ -74,12 +74,25 @@ export const getEnvVar = (name: string, isOptional = false): string => {
|
|||||||
return value || '';
|
return value || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createLogger(scope: string) {
|
/**
|
||||||
const padding = ' '.repeat(20 - scope.length);
|
* A basic logger implementation.
|
||||||
return {
|
* Delegates to `console`, but prepends each message with the current date and specified scope (i.e caller).
|
||||||
error: (...args: any[]) => console.error(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
*/
|
||||||
info: (...args: any[]) => console.info(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
export class Logger {
|
||||||
log: (...args: any[]) => console.log(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
private padding = ' '.repeat(20 - this.scope.length);
|
||||||
warn: (...args: any[]) => console.warn(`[${new Date()}]`, `${scope}:${padding}`, ...args),
|
|
||||||
};
|
/**
|
||||||
|
* Create a new `Logger` instance for the specified `scope`.
|
||||||
|
* @param scope The logger's scope (added to all messages).
|
||||||
|
*/
|
||||||
|
constructor(private scope: string) {}
|
||||||
|
|
||||||
|
public error(...args: any[]) { this.callMethod('error', args); }
|
||||||
|
public info(...args: any[]) { this.callMethod('info', args); }
|
||||||
|
public log(...args: any[]) { this.callMethod('log', args); }
|
||||||
|
public warn(...args: any[]) { this.callMethod('warn', args); }
|
||||||
|
|
||||||
|
private callMethod(method: 'error' | 'info' | 'log' | 'warn', args: any[]) {
|
||||||
|
console[method](`[${new Date()}]`, `${this.scope}:${this.padding}`, ...args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,14 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as shell from 'shelljs';
|
import * as shell from 'shelljs';
|
||||||
import {HIDDEN_DIR_PREFIX} from '../common/constants';
|
import {HIDDEN_DIR_PREFIX} from '../common/constants';
|
||||||
import {assertNotMissingOrEmpty, computeShortSha, createLogger} from '../common/utils';
|
import {assertNotMissingOrEmpty, computeShortSha, Logger} from '../common/utils';
|
||||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
|
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
|
||||||
import {PreviewServerError} from './preview-error';
|
import {PreviewServerError} from './preview-error';
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
export class BuildCreator extends EventEmitter {
|
export class BuildCreator extends EventEmitter {
|
||||||
|
|
||||||
private logger = createLogger('BuildCreator');
|
private logger = new Logger('BuildCreator');
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
constructor(protected buildsDir: string) {
|
constructor(protected buildsDir: string) {
|
||||||
|
@ -4,7 +4,7 @@ import {dirname} from 'path';
|
|||||||
import {mkdir} from 'shelljs';
|
import {mkdir} from 'shelljs';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
import {CircleCiApi} from '../common/circle-ci-api';
|
import {CircleCiApi} from '../common/circle-ci-api';
|
||||||
import {assert, assertNotMissingOrEmpty, computeArtifactDownloadPath, createLogger} from '../common/utils';
|
import {assert, assertNotMissingOrEmpty, computeArtifactDownloadPath, Logger} from '../common/utils';
|
||||||
import {PreviewServerError} from './preview-error';
|
import {PreviewServerError} from './preview-error';
|
||||||
|
|
||||||
export interface GithubInfo {
|
export interface GithubInfo {
|
||||||
@ -19,7 +19,7 @@ export interface GithubInfo {
|
|||||||
* A helper that can get information about builds and download build artifacts.
|
* A helper that can get information about builds and download build artifacts.
|
||||||
*/
|
*/
|
||||||
export class BuildRetriever {
|
export class BuildRetriever {
|
||||||
private logger = createLogger('BuildRetriever');
|
private logger = new Logger('BuildRetriever');
|
||||||
constructor(private api: CircleCiApi, private downloadSizeLimit: number, private downloadDir: string) {
|
constructor(private api: CircleCiApi, private downloadSizeLimit: number, private downloadDir: string) {
|
||||||
assert(downloadSizeLimit > 0, 'Invalid parameter "downloadSizeLimit" should be a number greater than 0.');
|
assert(downloadSizeLimit > 0, 'Invalid parameter "downloadSizeLimit" should be a number greater than 0.');
|
||||||
assertNotMissingOrEmpty('downloadDir', downloadDir);
|
assertNotMissingOrEmpty('downloadDir', downloadDir);
|
||||||
@ -34,7 +34,7 @@ export class BuildRetriever {
|
|||||||
const buildInfo = await this.api.getBuildInfo(buildNum);
|
const buildInfo = await this.api.getBuildInfo(buildNum);
|
||||||
const githubInfo: GithubInfo = {
|
const githubInfo: GithubInfo = {
|
||||||
org: buildInfo.username,
|
org: buildInfo.username,
|
||||||
pr: getPrfromBranch(buildInfo.branch),
|
pr: getPrFromBranch(buildInfo.branch),
|
||||||
repo: buildInfo.reponame,
|
repo: buildInfo.reponame,
|
||||||
sha: buildInfo.vcs_revision,
|
sha: buildInfo.vcs_revision,
|
||||||
success: !buildInfo.failed,
|
success: !buildInfo.failed,
|
||||||
@ -73,7 +73,7 @@ export class BuildRetriever {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPrfromBranch(branch: string): number {
|
function getPrFromBranch(branch: string): number {
|
||||||
// CircleCI only exposes PR numbers via the `branch` field :-(
|
// CircleCI only exposes PR numbers via the `branch` field :-(
|
||||||
const match = /^pull\/(\d+)$/.exec(branch);
|
const match = /^pull\/(\d+)$/.exec(branch);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
import * as bodyParser from 'body-parser';
|
import * as bodyParser from 'body-parser';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
|
import {AddressInfo} from 'net';
|
||||||
import {CircleCiApi} from '../common/circle-ci-api';
|
import {CircleCiApi} from '../common/circle-ci-api';
|
||||||
import {GithubApi} from '../common/github-api';
|
import {GithubApi} from '../common/github-api';
|
||||||
import {GithubPullRequests} from '../common/github-pull-requests';
|
import {GithubPullRequests} from '../common/github-pull-requests';
|
||||||
import {GithubTeams} from '../common/github-teams';
|
import {GithubTeams} from '../common/github-teams';
|
||||||
import {assert, assertNotMissingOrEmpty, createLogger} from '../common/utils';
|
import {assert, assertNotMissingOrEmpty, computeShortSha, Logger} from '../common/utils';
|
||||||
import {BuildCreator} from './build-creator';
|
import {BuildCreator} from './build-creator';
|
||||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
|
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
|
||||||
import {BuildRetriever} from './build-retriever';
|
import {BuildRetriever} from './build-retriever';
|
||||||
@ -31,7 +32,7 @@ export interface PreviewServerConfig {
|
|||||||
trustedPrLabel: string;
|
trustedPrLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = createLogger('PreviewServer');
|
const logger = new Logger('PreviewServer');
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
export class PreviewServerFactory {
|
export class PreviewServerFactory {
|
||||||
@ -52,7 +53,7 @@ export class PreviewServerFactory {
|
|||||||
const httpServer = http.createServer(middleware as any);
|
const httpServer = http.createServer(middleware as any);
|
||||||
|
|
||||||
httpServer.on('listening', () => {
|
httpServer.on('listening', () => {
|
||||||
const info = httpServer.address();
|
const info = httpServer.address() as AddressInfo;
|
||||||
logger.info(`Up and running (and listening on ${info.address}:${info.port})...`);
|
logger.info(`Up and running (and listening on ${info.address}:${info.port})...`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -63,10 +64,36 @@ export class PreviewServerFactory {
|
|||||||
buildCreator: BuildCreator, cfg: PreviewServerConfig): express.Express {
|
buildCreator: BuildCreator, cfg: PreviewServerConfig): express.Express {
|
||||||
const middleware = express();
|
const middleware = express();
|
||||||
const jsonParser = bodyParser.json();
|
const jsonParser = bodyParser.json();
|
||||||
|
const significantFilesRe = new RegExp(cfg.significantFilesPattern);
|
||||||
|
|
||||||
// RESPOND TO IS-ALIVE PING
|
// RESPOND TO IS-ALIVE PING
|
||||||
middleware.get(/^\/health-check\/?$/, (_req, res) => res.sendStatus(200));
|
middleware.get(/^\/health-check\/?$/, (_req, res) => res.sendStatus(200));
|
||||||
|
|
||||||
|
// RESPOND TO CAN-HAVE-PUBLIC-PREVIEW CHECK
|
||||||
|
const canHavePublicPreviewRe = /^\/can-have-public-preview\/(\d+)\/?$/;
|
||||||
|
middleware.get(canHavePublicPreviewRe, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const pr = +canHavePublicPreviewRe.exec(req.url)![1];
|
||||||
|
|
||||||
|
if (!await buildVerifier.getSignificantFilesChanged(pr, significantFilesRe)) {
|
||||||
|
// Cannot have preview: PR did not touch relevant files: `aio/` or `packages/` (except for spec files).
|
||||||
|
res.send({canHavePublicPreview: false, reason: 'No significant files touched.'});
|
||||||
|
logger.log(`PR:${pr} - Cannot have a public preview, because it did not touch any significant files.`);
|
||||||
|
} else if (!await buildVerifier.getPrIsTrusted(pr)) {
|
||||||
|
// Cannot have preview: PR not automatically verifiable as "trusted".
|
||||||
|
res.send({canHavePublicPreview: false, reason: 'Not automatically verifiable as "trusted".'});
|
||||||
|
logger.log(`PR:${pr} - Cannot have a public preview, because not automatically verifiable as "trusted".`);
|
||||||
|
} else {
|
||||||
|
// Can have preview.
|
||||||
|
res.send({canHavePublicPreview: true, reason: null});
|
||||||
|
logger.log(`PR:${pr} - Can have a public preview.`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Previewability check error', err);
|
||||||
|
respondWithError(res, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// CIRCLE_CI BUILD COMPLETE WEBHOOK
|
// CIRCLE_CI BUILD COMPLETE WEBHOOK
|
||||||
middleware.post(/^\/circle-build\/?$/, jsonParser, async (req, res) => {
|
middleware.post(/^\/circle-build\/?$/, jsonParser, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@ -107,7 +134,7 @@ export class PreviewServerFactory {
|
|||||||
`Invalid webhook: expected "githubRepo" property to equal "${cfg.githubRepo}" but got "${repo}".`);
|
`Invalid webhook: expected "githubRepo" property to equal "${cfg.githubRepo}" but got "${repo}".`);
|
||||||
|
|
||||||
// Do not deploy unless this PR has touched relevant files: `aio/` or `packages/` (except for spec files)
|
// Do not deploy unless this PR has touched relevant files: `aio/` or `packages/` (except for spec files)
|
||||||
if (!await buildVerifier.getSignificantFilesChanged(pr, new RegExp(cfg.significantFilesPattern))) {
|
if (!await buildVerifier.getSignificantFilesChanged(pr, significantFilesRe)) {
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
logger.log(`PR:${pr}, Build:${buildNum} - ` +
|
logger.log(`PR:${pr}, Build:${buildNum} - ` +
|
||||||
`Skipping preview processing because this PR did not touch any significant files.`);
|
`Skipping preview processing because this PR did not touch any significant files.`);
|
||||||
@ -117,7 +144,10 @@ export class PreviewServerFactory {
|
|||||||
const artifactPath = await buildRetriever.downloadBuildArtifact(buildNum, pr, sha, cfg.buildArtifactPath);
|
const artifactPath = await buildRetriever.downloadBuildArtifact(buildNum, pr, sha, cfg.buildArtifactPath);
|
||||||
const isPublic = await buildVerifier.getPrIsTrusted(pr);
|
const isPublic = await buildVerifier.getPrIsTrusted(pr);
|
||||||
await buildCreator.create(pr, sha, artifactPath, isPublic);
|
await buildCreator.create(pr, sha, artifactPath, isPublic);
|
||||||
|
|
||||||
res.sendStatus(isPublic ? 201 : 202);
|
res.sendStatus(isPublic ? 201 : 202);
|
||||||
|
logger.log(`PR:${pr}, SHA:${computeShortSha(sha)}, Build:${buildNum} - ` +
|
||||||
|
`Successfully created ${isPublic ? 'public' : 'non-public'} preview.`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('CircleCI webhook error', err);
|
logger.error('CircleCI webhook error', err);
|
||||||
respondWithError(res, err);
|
respondWithError(res, err);
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
AIO_NGINX_PORT_HTTPS,
|
AIO_NGINX_PORT_HTTPS,
|
||||||
AIO_WWW_USER,
|
AIO_WWW_USER,
|
||||||
} from '../common/env-variables';
|
} from '../common/env-variables';
|
||||||
import {computeShortSha, createLogger} from '../common/utils';
|
import {computeShortSha, Logger} from '../common/utils';
|
||||||
|
|
||||||
// Interfaces - Types
|
// Interfaces - Types
|
||||||
export interface CmdResult { success: boolean; err: Error | null; stdout: string; stderr: string; }
|
export interface CmdResult { success: boolean; err: Error | null; stdout: string; stderr: string; }
|
||||||
@ -31,7 +31,7 @@ class Helper {
|
|||||||
https: AIO_NGINX_PORT_HTTPS,
|
https: AIO_NGINX_PORT_HTTPS,
|
||||||
};
|
};
|
||||||
|
|
||||||
private logger = createLogger('TestHelper');
|
private logger = new Logger('TestHelper');
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -105,7 +105,7 @@ class Helper {
|
|||||||
Object.keys(this.portPerScheme).forEach(scheme => suiteFactory(scheme, this.portPerScheme[scheme]));
|
Object.keys(this.portPerScheme).forEach(scheme => suiteFactory(scheme, this.portPerScheme[scheme]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public verifyResponse(status: number | [number, string], regex = /^/): VerifyCmdResultFn {
|
public verifyResponse(status: number | [number, string], regex: string | RegExp = /^/): VerifyCmdResultFn {
|
||||||
let statusCode: number;
|
let statusCode: number;
|
||||||
let statusText: string;
|
let statusText: string;
|
||||||
|
|
||||||
@ -180,26 +180,42 @@ class Helper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DefaultCurlOptions {
|
||||||
|
defaultMethod?: CurlOptions['method'];
|
||||||
|
defaultOptions?: CurlOptions['options'];
|
||||||
|
defaultHeaders?: CurlOptions['headers'];
|
||||||
|
defaultData?: CurlOptions['data'];
|
||||||
|
defaultExtraPath?: CurlOptions['extraPath'];
|
||||||
|
}
|
||||||
|
|
||||||
interface CurlOptions {
|
interface CurlOptions {
|
||||||
method?: string;
|
method?: string;
|
||||||
options?: string;
|
options?: string;
|
||||||
|
headers?: string[];
|
||||||
data?: any;
|
data?: any;
|
||||||
url?: string;
|
url?: string;
|
||||||
extraPath?: string;
|
extraPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeCurl(baseUrl: string) {
|
export function makeCurl(baseUrl: string, {
|
||||||
|
defaultMethod = 'POST',
|
||||||
|
defaultOptions = '',
|
||||||
|
defaultHeaders = ['Content-Type: application/json'],
|
||||||
|
defaultData = {},
|
||||||
|
defaultExtraPath = '',
|
||||||
|
}: DefaultCurlOptions = {}) {
|
||||||
return function curl({
|
return function curl({
|
||||||
method = 'POST',
|
method = defaultMethod,
|
||||||
options = '',
|
options = defaultOptions,
|
||||||
data = {},
|
headers = defaultHeaders,
|
||||||
|
data = defaultData,
|
||||||
url = baseUrl,
|
url = baseUrl,
|
||||||
extraPath = '',
|
extraPath = defaultExtraPath,
|
||||||
}: CurlOptions) {
|
}: CurlOptions) {
|
||||||
const dataString = data ? JSON.stringify(data) : '';
|
const dataString = data ? JSON.stringify(data) : '';
|
||||||
const cmd = `curl -iLX ${method} ` +
|
const cmd = `curl -iLX ${method} ` +
|
||||||
`${options} ` +
|
`${options} ` +
|
||||||
`--header "Content-Type: application/json" ` +
|
headers.map(header => `--header "${header}" `).join('') +
|
||||||
`--data '${dataString}' ` +
|
`--data '${dataString}' ` +
|
||||||
`${url}${extraPath}`;
|
`${url}${extraPath}`;
|
||||||
return helper.runCmd(cmd);
|
return helper.runCmd(cmd);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import * as nock from 'nock';
|
import * as nock from 'nock';
|
||||||
import * as tar from 'tar-stream';
|
import * as tar from 'tar-stream';
|
||||||
import {gzipSync} from 'zlib';
|
import {gzipSync} from 'zlib';
|
||||||
import {createLogger, getEnvVar} from '../common/utils';
|
import {getEnvVar, Logger} from '../common/utils';
|
||||||
import {BuildNums, PrNums, SHA} from './constants';
|
import {BuildNums, PrNums, SHA} from './constants';
|
||||||
|
|
||||||
// We are using the `nock` library to fake responses from REST requests, when testing.
|
// We are using the `nock` library to fake responses from REST requests, when testing.
|
||||||
@ -14,7 +14,7 @@ import {BuildNums, PrNums, SHA} from './constants';
|
|||||||
// below and return a suitable response. This is quite complicated to setup since the
|
// below and return a suitable response. This is quite complicated to setup since the
|
||||||
// response from, say, CircleCI will affect what request is made to, say, Github.
|
// response from, say, CircleCI will affect what request is made to, say, Github.
|
||||||
|
|
||||||
const logger = createLogger('NOCK');
|
const logger = new Logger('mock-external-apis');
|
||||||
|
|
||||||
const log = (...args: any[]) => {
|
const log = (...args: any[]) => {
|
||||||
// Filter out non-matching URL checks
|
// Filter out non-matching URL checks
|
||||||
@ -76,7 +76,7 @@ const GITHUB_PULLS_URL = `/repos/${AIO_GITHUB_ORGANIZATION}/${AIO_GITHUB_REPO}/p
|
|||||||
const GITHUB_TEAMS_URL = `/orgs/${AIO_GITHUB_ORGANIZATION}/teams`;
|
const GITHUB_TEAMS_URL = `/orgs/${AIO_GITHUB_ORGANIZATION}/teams`;
|
||||||
|
|
||||||
const getIssueUrl = (prNum: number) => `${GITHUB_ISSUES_URL}/${prNum}`;
|
const getIssueUrl = (prNum: number) => `${GITHUB_ISSUES_URL}/${prNum}`;
|
||||||
const getFilesUrl = (prNum: number) => `${GITHUB_PULLS_URL}/${prNum}/files`;
|
const getFilesUrl = (prNum: number, pageNum = 1) => `${GITHUB_PULLS_URL}/${prNum}/files?page=${pageNum}&per_page=100`;
|
||||||
const getCommentUrl = (prNum: number) => `${getIssueUrl(prNum)}/comments`;
|
const getCommentUrl = (prNum: number) => `${getIssueUrl(prNum)}/comments`;
|
||||||
const getTeamMembershipUrl = (teamId: number, username: string) => `/teams/${teamId}/memberships/${username}`;
|
const getTeamMembershipUrl = (teamId: number, username: string) => `/teams/${teamId}/memberships/${username}`;
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ const githubApi = nock(GITHUB_API_HOST).log(log).persist().matchHeader('Authoriz
|
|||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
|
|
||||||
// GENERAL responses
|
// GENERAL responses
|
||||||
githubApi.get(GITHUB_TEAMS_URL + '?page=0&per_page=100').reply(200, TEST_TEAM_INFO);
|
githubApi.get(GITHUB_TEAMS_URL + '?page=1&per_page=100').reply(200, TEST_TEAM_INFO);
|
||||||
githubApi.post(getCommentUrl(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200);
|
githubApi.post(getCommentUrl(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200);
|
||||||
|
|
||||||
// BUILD_INFO errors
|
// BUILD_INFO errors
|
||||||
|
@ -3,6 +3,7 @@ import * as path from 'path';
|
|||||||
import {rm} from 'shelljs';
|
import {rm} from 'shelljs';
|
||||||
import {AIO_BUILDS_DIR, AIO_NGINX_HOSTNAME, AIO_NGINX_PORT_HTTP, AIO_NGINX_PORT_HTTPS} from '../common/env-variables';
|
import {AIO_BUILDS_DIR, AIO_NGINX_HOSTNAME, AIO_NGINX_PORT_HTTP, AIO_NGINX_PORT_HTTPS} from '../common/env-variables';
|
||||||
import {computeShortSha} from '../common/utils';
|
import {computeShortSha} from '../common/utils';
|
||||||
|
import {PrNums} from './constants';
|
||||||
import {helper as h} from './helper';
|
import {helper as h} from './helper';
|
||||||
import {customMatchers} from './jasmine-custom-matchers';
|
import {customMatchers} from './jasmine-custom-matchers';
|
||||||
|
|
||||||
@ -252,6 +253,42 @@ describe(`nginx`, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe(`${host}/can-have-public-preview`, () => {
|
||||||
|
const baseUrl = `${scheme}://${host}/can-have-public-preview`;
|
||||||
|
|
||||||
|
|
||||||
|
it('should disallow non-GET requests', async () => {
|
||||||
|
await Promise.all([
|
||||||
|
h.runCmd(`curl -iLX POST ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||||
|
h.runCmd(`curl -iLX PUT ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||||
|
h.runCmd(`curl -iLX PATCH ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||||
|
h.runCmd(`curl -iLX DELETE ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should pass requests through to the preview server', async () => {
|
||||||
|
await h.runCmd(`curl -iLX GET ${baseUrl}/${PrNums.CHANGED_FILES_ERROR}`).
|
||||||
|
then(h.verifyResponse(500, /CHANGED_FILES_ERROR/));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 404 for unknown paths', async () => {
|
||||||
|
const cmdPrefix = `curl -iLX GET ${baseUrl}`;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
h.runCmd(`${cmdPrefix}/foo/42`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}-foo/42`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}nfoo/42`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/42/foo`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/f00`).then(h.verifyResponse(404)),
|
||||||
|
h.runCmd(`${cmdPrefix}/`).then(h.verifyResponse(404)),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe(`${host}/circle-build`, () => {
|
describe(`${host}/circle-build`, () => {
|
||||||
|
|
||||||
it('should disallow non-POST requests', done => {
|
it('should disallow non-POST requests', done => {
|
||||||
@ -287,6 +324,7 @@ describe(`nginx`, () => {
|
|||||||
h.runCmd(`${cmdPrefix}/circle-build/42`).then(h.verifyResponse(404)),
|
h.runCmd(`${cmdPrefix}/circle-build/42`).then(h.verifyResponse(404)),
|
||||||
]).then(done);
|
]).then(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,6 +18,92 @@ describe('preview-server', () => {
|
|||||||
afterEach(() => h.cleanUp());
|
afterEach(() => h.cleanUp());
|
||||||
|
|
||||||
|
|
||||||
|
describe(`${host}/can-have-public-preview`, () => {
|
||||||
|
const curl = makeCurl(`${host}/can-have-public-preview`, {
|
||||||
|
defaultData: null,
|
||||||
|
defaultExtraPath: `/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`,
|
||||||
|
defaultHeaders: [],
|
||||||
|
defaultMethod: 'GET',
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should disallow non-GET requests', async () => {
|
||||||
|
const bodyRegex = /^Unknown resource in request/;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
curl({method: 'POST'}).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
curl({method: 'PUT'}).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
curl({method: 'PATCH'}).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
curl({method: 'DELETE'}).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 404 for unknown paths', async () => {
|
||||||
|
const bodyRegex = /^Unknown resource in request/;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
curl({extraPath: `/foo/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
curl({extraPath: `-foo/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
curl({extraPath: `nfoo/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
curl({extraPath: `/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}/foo`}).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
curl({extraPath: '/f00'}).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
curl({extraPath: '/'}).then(h.verifyResponse(404, bodyRegex)),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 500 if checking for significant file changes fails', async () => {
|
||||||
|
await Promise.all([
|
||||||
|
curl({extraPath: `/${PrNums.CHANGED_FILES_404}`}).then(h.verifyResponse(500, /CHANGED_FILES_404/)),
|
||||||
|
curl({extraPath: `/${PrNums.CHANGED_FILES_ERROR}`}).then(h.verifyResponse(500, /CHANGED_FILES_ERROR/)),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 200 (false) if no significant files were touched', async () => {
|
||||||
|
const expectedResponse = JSON.stringify({
|
||||||
|
canHavePublicPreview: false,
|
||||||
|
reason: 'No significant files touched.',
|
||||||
|
});
|
||||||
|
|
||||||
|
await curl({extraPath: `/${PrNums.CHANGED_FILES_NONE}`}).then(h.verifyResponse(200, expectedResponse));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 500 if checking "trusted" status fails', async () => {
|
||||||
|
await curl({extraPath: `/${PrNums.TRUST_CHECK_ERROR}`}).then(h.verifyResponse(500, 'TRUST_CHECK_ERROR'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 200 (false) if the PR is not automatically verifiable as "trusted"', async () => {
|
||||||
|
const expectedResponse = JSON.stringify({
|
||||||
|
canHavePublicPreview: false,
|
||||||
|
reason: 'Not automatically verifiable as \\"trusted\\".',
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
curl({extraPath: `/${PrNums.TRUST_CHECK_INACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(200, expectedResponse)),
|
||||||
|
curl({extraPath: `/${PrNums.TRUST_CHECK_UNTRUSTED}`}).then(h.verifyResponse(200, expectedResponse)),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 200 (true) if the PR can have a public preview', async () => {
|
||||||
|
const expectedResponse = JSON.stringify({
|
||||||
|
canHavePublicPreview: true,
|
||||||
|
reason: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
curl({extraPath: `/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`}).then(h.verifyResponse(200, expectedResponse)),
|
||||||
|
curl({extraPath: `/${PrNums.TRUST_CHECK_TRUSTED_LABEL}`}).then(h.verifyResponse(200, expectedResponse)),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe(`${host}/circle-build`, () => {
|
describe(`${host}/circle-build`, () => {
|
||||||
|
|
||||||
const curl = makeCurl(`${host}/circle-build`);
|
const curl = makeCurl(`${host}/circle-build`);
|
||||||
|
@ -7,43 +7,49 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "yarn clean-dist",
|
"prebuild": "yarn clean-dist",
|
||||||
"build": "tsc",
|
"build": "yarn ~~build",
|
||||||
"build-watch": "yarn build --watch",
|
"prebuild-watch": "yarn prebuild",
|
||||||
|
"build-watch": "yarn ~~build-watch",
|
||||||
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
|
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
|
||||||
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"",
|
"predev": "yarn build || true",
|
||||||
|
"dev": "run-p ~~build-watch ~~test-watch",
|
||||||
"lint": "tslint --project tsconfig.json",
|
"lint": "tslint --project tsconfig.json",
|
||||||
"pre~~test-only": "yarn lint",
|
|
||||||
"~~test-only": "node dist/test",
|
|
||||||
"pretest": "yarn build",
|
"pretest": "yarn build",
|
||||||
"test": "yarn ~~test-only",
|
"test": "yarn ~~test-only",
|
||||||
"pretest-watch": "yarn build",
|
"pretest-watch": "yarn pretest",
|
||||||
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist"
|
"test-watch": "yarn ~~test-watch",
|
||||||
|
"~~build": "tsc",
|
||||||
|
"~~build-watch": "yarn ~~build --watch",
|
||||||
|
"pre~~test-only": "yarn lint",
|
||||||
|
"~~test-only": "node dist/test",
|
||||||
|
"~~test-watch": "nodemon --delay 1 --exec \"yarn ~~test-only\" --watch dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.18.2",
|
"body-parser": "^1.18.3",
|
||||||
"delete-empty": "^2.0.0",
|
"delete-empty": "^2.0.0",
|
||||||
"express": "^4.15.4",
|
"express": "^4.16.3",
|
||||||
"jasmine": "^2.8.0",
|
"jasmine": "^3.2.0",
|
||||||
"nock": "^9.2.5",
|
"nock": "^9.6.1",
|
||||||
"node-fetch": "^2.1.2",
|
"node-fetch": "^2.2.0",
|
||||||
"shelljs": "^0.8.1",
|
"shelljs": "^0.8.2",
|
||||||
"tar-stream": "^1.6.0",
|
"source-map-support": "^0.5.9",
|
||||||
"tslib": "^1.7.1"
|
"tar-stream": "^1.6.1",
|
||||||
|
"tslib": "^1.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/body-parser": "^1.16.5",
|
"@types/body-parser": "^1.17.0",
|
||||||
"@types/express": "^4.0.37",
|
"@types/express": "^4.16.0",
|
||||||
"@types/jasmine": "^2.6.0",
|
"@types/jasmine": "^2.8.8",
|
||||||
"@types/nock": "^9.1.3",
|
"@types/nock": "^9.3.0",
|
||||||
"@types/node": "^8.0.30",
|
"@types/node": "^10.9.2",
|
||||||
"@types/node-fetch": "^1.6.8",
|
"@types/node-fetch": "^2.1.2",
|
||||||
"@types/shelljs": "^0.8.0",
|
"@types/shelljs": "^0.8.0",
|
||||||
"@types/supertest": "^2.0.3",
|
"@types/supertest": "^2.0.5",
|
||||||
"concurrently": "^3.5.0",
|
"nodemon": "^1.18.3",
|
||||||
"nodemon": "^1.12.1",
|
"npm-run-all": "^4.1.5",
|
||||||
"supertest": "^3.0.0",
|
"supertest": "^3.1.0",
|
||||||
"tslint": "^5.7.0",
|
"tslint": "^5.11.0",
|
||||||
"tslint-jasmine-noSkipOrFocus": "^1.0.8",
|
"tslint-jasmine-noSkipOrFocus": "^1.0.9",
|
||||||
"typescript": "^2.5.2"
|
"typescript": "^3.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,25 +5,28 @@ import * as shell from 'shelljs';
|
|||||||
import {BuildCleaner} from '../../lib/clean-up/build-cleaner';
|
import {BuildCleaner} from '../../lib/clean-up/build-cleaner';
|
||||||
import {HIDDEN_DIR_PREFIX} from '../../lib/common/constants';
|
import {HIDDEN_DIR_PREFIX} from '../../lib/common/constants';
|
||||||
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
||||||
|
import {Logger} from '../../lib/common/utils';
|
||||||
|
|
||||||
const EXISTING_BUILDS = [10, 20, 30, 40];
|
const EXISTING_BUILDS = [10, 20, 30, 40];
|
||||||
const EXISTING_DOWNLOADS = [
|
const EXISTING_DOWNLOADS = [
|
||||||
'downloads/10-ABCDEF0-build.zip',
|
'10-ABCDEF0-build.zip',
|
||||||
'downloads/10-1234567-build.zip',
|
'10-1234567-build.zip',
|
||||||
'downloads/20-ABCDEF0-build.zip',
|
'20-ABCDEF0-build.zip',
|
||||||
'downloads/20-1234567-build.zip',
|
'20-1234567-build.zip',
|
||||||
];
|
];
|
||||||
const OPEN_PRS = [10, 40];
|
const OPEN_PRS = [10, 40];
|
||||||
const ANY_DATE = jasmine.any(String);
|
const ANY_DATE = jasmine.any(String);
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
describe('BuildCleaner', () => {
|
describe('BuildCleaner', () => {
|
||||||
|
let loggerErrorSpy: jasmine.Spy;
|
||||||
|
let loggerLogSpy: jasmine.Spy;
|
||||||
let cleaner: BuildCleaner;
|
let cleaner: BuildCleaner;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(console, 'error');
|
loggerErrorSpy = spyOn(Logger.prototype, 'error');
|
||||||
spyOn(console, 'log');
|
loggerLogSpy = spyOn(Logger.prototype, 'log');
|
||||||
cleaner = new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', 'downloads', 'build.zip');
|
cleaner = new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', '/downloads', 'build.zip');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('constructor()', () => {
|
describe('constructor()', () => {
|
||||||
@ -51,11 +54,13 @@ describe('BuildCleaner', () => {
|
|||||||
toThrowError('Missing or empty required parameter \'githubToken\'!');
|
toThrowError('Missing or empty required parameter \'githubToken\'!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should throw if \'downloadsDir\' is empty', () => {
|
it('should throw if \'downloadsDir\' is empty', () => {
|
||||||
expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', '', 'build.zip')).
|
expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', '', 'build.zip')).
|
||||||
toThrowError('Missing or empty required parameter \'downloadsDir\'!');
|
toThrowError('Missing or empty required parameter \'downloadsDir\'!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should throw if \'artifactPath\' is empty', () => {
|
it('should throw if \'artifactPath\' is empty', () => {
|
||||||
expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', 'downloads', '')).
|
expect(() => new BuildCleaner('/foo/bar', 'baz', 'qux', '12345', 'downloads', '')).
|
||||||
toThrowError('Missing or empty required parameter \'artifactPath\'!');
|
toThrowError('Missing or empty required parameter \'artifactPath\'!');
|
||||||
@ -85,9 +90,12 @@ describe('BuildCleaner', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should return a promise', () => {
|
it('should return a promise', async () => {
|
||||||
const promise = cleaner.cleanUp();
|
const promise = cleaner.cleanUp();
|
||||||
expect(promise).toEqual(jasmine.any(Promise));
|
expect(promise).toEqual(jasmine.any(Promise));
|
||||||
|
|
||||||
|
// Do not complete the test and release the spies synchronously, to avoid running the actual implementations.
|
||||||
|
await promise;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -160,6 +168,7 @@ describe('BuildCleaner', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should reject if \'removeUnnecessaryDownloads()\' rejects', async () => {
|
it('should reject if \'removeUnnecessaryDownloads()\' rejects', async () => {
|
||||||
try {
|
try {
|
||||||
cleanerRemoveUnnecessaryDownloadsSpy.and.callFake(() => Promise.reject('Test'));
|
cleanerRemoveUnnecessaryDownloadsSpy.and.callFake(() => Promise.reject('Test'));
|
||||||
@ -168,6 +177,7 @@ describe('BuildCleaner', () => {
|
|||||||
expect(err).toBe('Test');
|
expect(err).toBe('Test');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -277,11 +287,14 @@ describe('BuildCleaner', () => {
|
|||||||
prDeferred.resolve([{id: 0, number: 1}, {id: 1, number: 2}, {id: 2, number: 3}]);
|
prDeferred.resolve([{id: 0, number: 1}, {id: 1, number: 2}, {id: 2, number: 3}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should log the number of open PRs', () => {
|
it('should log the number of open PRs', () => {
|
||||||
promise.then(prNumbers => {
|
promise.then(prNumbers => {
|
||||||
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', `Open pull requests: ${prNumbers}`);
|
expect(loggerLogSpy).toHaveBeenCalledWith(
|
||||||
|
ANY_DATE, 'BuildCleaner: ', `Open pull requests: ${prNumbers}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -301,9 +314,9 @@ describe('BuildCleaner', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should get the contents of the builds directory', () => {
|
it('should get the contents of the downloads directory', () => {
|
||||||
expect(fsReaddirSpy).toHaveBeenCalled();
|
expect(fsReaddirSpy).toHaveBeenCalled();
|
||||||
expect(fsReaddirSpy.calls.argsFor(0)[0]).toBe('downloads');
|
expect(fsReaddirSpy.calls.argsFor(0)[0]).toBe('/downloads');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -317,7 +330,7 @@ describe('BuildCleaner', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should resolve with the returned files (as numbers)', done => {
|
it('should resolve with the returned file names', done => {
|
||||||
promise.then(result => {
|
promise.then(result => {
|
||||||
expect(result).toEqual(EXISTING_DOWNLOADS);
|
expect(result).toEqual(EXISTING_DOWNLOADS);
|
||||||
done();
|
done();
|
||||||
@ -383,8 +396,7 @@ describe('BuildCleaner', () => {
|
|||||||
|
|
||||||
cleaner.removeDir('/foo/bar');
|
cleaner.removeDir('/foo/bar');
|
||||||
|
|
||||||
expect(console.error).toHaveBeenCalledWith(
|
expect(loggerErrorSpy).toHaveBeenCalledWith('ERROR: Unable to remove \'/foo/bar\' due to:', 'Test');
|
||||||
jasmine.any(String), 'BuildCleaner: ', 'ERROR: Unable to remove \'/foo/bar\' due to:', 'Test');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -401,8 +413,8 @@ describe('BuildCleaner', () => {
|
|||||||
it('should log the number of existing builds and builds to be removed', () => {
|
it('should log the number of existing builds and builds to be removed', () => {
|
||||||
cleaner.removeUnnecessaryBuilds([1, 2, 3], [3, 4, 5, 6]);
|
cleaner.removeUnnecessaryBuilds([1, 2, 3], [3, 4, 5, 6]);
|
||||||
|
|
||||||
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', 'Existing builds: 3');
|
expect(loggerLogSpy).toHaveBeenCalledWith('Existing builds: 3');
|
||||||
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', 'Removing 2 build(s): 1, 2');
|
expect(loggerLogSpy).toHaveBeenCalledWith('Removing 2 build(s): 1, 2');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -454,25 +466,36 @@ describe('BuildCleaner', () => {
|
|||||||
|
|
||||||
|
|
||||||
describe('removeUnnecessaryDownloads()', () => {
|
describe('removeUnnecessaryDownloads()', () => {
|
||||||
|
let shellRmSpy: jasmine.Spy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(shell, 'rm');
|
shellRmSpy = spyOn(shell, 'rm');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should log the number of existing downloads and downloads to be removed', () => {
|
||||||
|
cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS);
|
||||||
|
|
||||||
|
expect(loggerLogSpy).toHaveBeenCalledWith('Existing downloads: 4');
|
||||||
|
expect(loggerLogSpy).toHaveBeenCalledWith('Removing 2 download(s): 20-ABCDEF0-build.zip, 20-1234567-build.zip');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should construct full paths to directories (by prepending \'downloadsDir\')', () => {
|
||||||
|
cleaner.removeUnnecessaryDownloads(['dl-1', 'dl-2', 'dl-3'], []);
|
||||||
|
|
||||||
|
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/dl-1'));
|
||||||
|
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/dl-2'));
|
||||||
|
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/dl-3'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should remove the downloads that do not correspond to open PRs', () => {
|
it('should remove the downloads that do not correspond to open PRs', () => {
|
||||||
cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS);
|
cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS);
|
||||||
expect(shell.rm).toHaveBeenCalledTimes(2);
|
expect(shellRmSpy).toHaveBeenCalledTimes(2);
|
||||||
expect(shell.rm).toHaveBeenCalledWith('downloads/20-ABCDEF0-build.zip');
|
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/20-ABCDEF0-build.zip'));
|
||||||
expect(shell.rm).toHaveBeenCalledWith('downloads/20-1234567-build.zip');
|
expect(shellRmSpy).toHaveBeenCalledWith(normalize('/downloads/20-1234567-build.zip'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should log the number of existing builds and builds to be removed', () => {
|
|
||||||
cleaner.removeUnnecessaryDownloads(EXISTING_DOWNLOADS, OPEN_PRS);
|
|
||||||
|
|
||||||
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ', 'Existing downloads: 4');
|
|
||||||
expect(console.log).toHaveBeenCalledWith(ANY_DATE, 'BuildCleaner: ',
|
|
||||||
'Removing 2 download(s): downloads/20-ABCDEF0-build.zip, downloads/20-1234567-build.zip');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -126,8 +126,8 @@ describe('GithubApi', () => {
|
|||||||
(api as any).getPaginated('/foo/bar');
|
(api as any).getPaginated('/foo/bar');
|
||||||
(api as any).getPaginated('/foo/bar', {baz: 'qux'});
|
(api as any).getPaginated('/foo/bar', {baz: 'qux'});
|
||||||
|
|
||||||
expect(api.get).toHaveBeenCalledWith('/foo/bar', {page: 0, per_page: 100});
|
expect(api.get).toHaveBeenCalledWith('/foo/bar', {page: 1, per_page: 100});
|
||||||
expect(api.get).toHaveBeenCalledWith('/foo/bar', {baz: 'qux', page: 0, per_page: 100});
|
expect(api.get).toHaveBeenCalledWith('/foo/bar', {baz: 'qux', page: 1, per_page: 100});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -162,9 +162,9 @@ describe('GithubApi', () => {
|
|||||||
const paramsForPage = (page: number) => ({baz: 'qux', page, per_page: 100});
|
const paramsForPage = (page: number) => ({baz: 'qux', page, per_page: 100});
|
||||||
|
|
||||||
expect(apiGetSpy).toHaveBeenCalledTimes(3);
|
expect(apiGetSpy).toHaveBeenCalledTimes(3);
|
||||||
expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(0)]);
|
expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(1)]);
|
||||||
expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(1)]);
|
expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(2)]);
|
||||||
expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(2)]);
|
expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(3)]);
|
||||||
|
|
||||||
expect(data).toEqual(allItems);
|
expect(data).toEqual(allItems);
|
||||||
|
|
||||||
|
@ -4,13 +4,13 @@ import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
|||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
describe('GithubPullRequests', () => {
|
describe('GithubPullRequests', () => {
|
||||||
|
|
||||||
let githubApi: jasmine.SpyObj<GithubApi>;
|
let githubApi: jasmine.SpyObj<GithubApi>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
githubApi = jasmine.createSpyObj('githubApi', ['post', 'get', 'getPaginated']);
|
githubApi = jasmine.createSpyObj('githubApi', ['post', 'get', 'getPaginated']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('constructor()', () => {
|
describe('constructor()', () => {
|
||||||
|
|
||||||
it('should throw if \'githubOrg\' is missing or empty', () => {
|
it('should throw if \'githubOrg\' is missing or empty', () => {
|
||||||
@ -95,16 +95,14 @@ describe('GithubPullRequests', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('fetchAll()', () => {
|
describe('fetchAll()', () => {
|
||||||
let prs: GithubPullRequests;
|
let prs: GithubPullRequests;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => prs = new GithubPullRequests(githubApi, 'foo', 'bar'));
|
||||||
prs = new GithubPullRequests(githubApi, 'foo', 'bar');
|
|
||||||
spyOn(console, 'log');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should call \'getPaginated()\' with the correct pathname and params', () => {
|
it('should call \'getPaginated()\' with the correct pathname and params', () => {
|
||||||
@ -131,8 +129,10 @@ describe('GithubPullRequests', () => {
|
|||||||
githubApi.getPaginated.and.returnValue('Test');
|
githubApi.getPaginated.and.returnValue('Test');
|
||||||
expect(prs.fetchAll() as any).toBe('Test');
|
expect(prs.fetchAll() as any).toBe('Test');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('fetchFiles()', () => {
|
describe('fetchFiles()', () => {
|
||||||
let prs: GithubPullRequests;
|
let prs: GithubPullRequests;
|
||||||
|
|
||||||
@ -141,21 +141,21 @@ describe('GithubPullRequests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should make a GET request to GitHub with the correct pathname', () => {
|
it('should make a paginated GET request to GitHub with the correct pathname', () => {
|
||||||
prs.fetchFiles(42);
|
prs.fetchFiles(42);
|
||||||
expect(githubApi.get).toHaveBeenCalledWith('/repos/foo/bar/pulls/42/files');
|
expect(githubApi.getPaginated).toHaveBeenCalledWith('/repos/foo/bar/pulls/42/files');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should resolve with the data returned from GitHub', done => {
|
it('should resolve with the data returned from GitHub', done => {
|
||||||
const expected: any = [{ sha: 'ABCDE', filename: 'a/b/c'}, { sha: '12345', filename: 'x/y/z' }];
|
const expected: any = [{sha: 'ABCDE', filename: 'a/b/c'}, {sha: '12345', filename: 'x/y/z'}];
|
||||||
githubApi.get.and.callFake(() => Promise.resolve(expected));
|
githubApi.getPaginated.and.callFake(() => Promise.resolve(expected));
|
||||||
prs.fetch(42).then(data => {
|
prs.fetchFiles(42).then(data => {
|
||||||
expect(data).toEqual(expected);
|
expect(data).toEqual(expected);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Imports
|
// Imports
|
||||||
|
import {resolve as resolvePath} from 'path';
|
||||||
import {
|
import {
|
||||||
assert,
|
assert,
|
||||||
assertNotMissingOrEmpty,
|
assertNotMissingOrEmpty,
|
||||||
@ -6,6 +7,7 @@ import {
|
|||||||
computeShortSha,
|
computeShortSha,
|
||||||
getEnvVar,
|
getEnvVar,
|
||||||
getPrInfoFromDownloadPath,
|
getPrInfoFromDownloadPath,
|
||||||
|
Logger,
|
||||||
} from '../../lib/common/utils';
|
} from '../../lib/common/utils';
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
@ -19,6 +21,7 @@ describe('utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('assert', () => {
|
describe('assert', () => {
|
||||||
it('should throw if passed a false value', () => {
|
it('should throw if passed a false value', () => {
|
||||||
expect(() => assert(false, 'error message')).toThrowError('error message');
|
expect(() => assert(false, 'error message')).toThrowError('error message');
|
||||||
@ -29,6 +32,7 @@ describe('utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('computeArtifactDownloadPath', () => {
|
describe('computeArtifactDownloadPath', () => {
|
||||||
it('should compute an absolute path based on the artifact info provided', () => {
|
it('should compute an absolute path based on the artifact info provided', () => {
|
||||||
const downloadDir = '/a/b/c';
|
const downloadDir = '/a/b/c';
|
||||||
@ -36,10 +40,11 @@ describe('utils', () => {
|
|||||||
const sha = 'ABCDEF1234567';
|
const sha = 'ABCDEF1234567';
|
||||||
const artifactPath = 'a/path/to/file.zip';
|
const artifactPath = 'a/path/to/file.zip';
|
||||||
const path = computeArtifactDownloadPath(downloadDir, pr, sha, artifactPath);
|
const path = computeArtifactDownloadPath(downloadDir, pr, sha, artifactPath);
|
||||||
expect(path).toEqual('/a/b/c/123-ABCDEF1-file.zip');
|
expect(path).toBe(resolvePath('/a/b/c/123-ABCDEF1-file.zip'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('getPrInfoFromDownloadPath', () => {
|
describe('getPrInfoFromDownloadPath', () => {
|
||||||
it('should extract the PR and SHA from the file path', () => {
|
it('should extract the PR and SHA from the file path', () => {
|
||||||
const {pr, sha} = getPrInfoFromDownloadPath('a/b/c/12345-ABCDE-artifact.zip');
|
const {pr, sha} = getPrInfoFromDownloadPath('a/b/c/12345-ABCDE-artifact.zip');
|
||||||
@ -48,6 +53,7 @@ describe('utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('assertNotMissingOrEmpty()', () => {
|
describe('assertNotMissingOrEmpty()', () => {
|
||||||
|
|
||||||
it('should throw if passed an empty value', () => {
|
it('should throw if passed an empty value', () => {
|
||||||
@ -122,4 +128,79 @@ describe('utils', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Logger', () => {
|
||||||
|
let consoleErrorSpy: jasmine.Spy;
|
||||||
|
let consoleInfoSpy: jasmine.Spy;
|
||||||
|
let consoleLogSpy: jasmine.Spy;
|
||||||
|
let consoleWarnSpy: jasmine.Spy;
|
||||||
|
let logger: Logger;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
consoleErrorSpy = spyOn(console, 'error');
|
||||||
|
consoleInfoSpy = spyOn(console, 'info');
|
||||||
|
consoleLogSpy = spyOn(console, 'log');
|
||||||
|
consoleWarnSpy = spyOn(console, 'warn');
|
||||||
|
|
||||||
|
logger = new Logger('TestScope');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should delegate to `console`', () => {
|
||||||
|
logger.error('foo');
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(consoleErrorSpy.calls.argsFor(0)).toContain('foo');
|
||||||
|
|
||||||
|
logger.info('bar');
|
||||||
|
expect(consoleInfoSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(consoleInfoSpy.calls.argsFor(0)).toContain('bar');
|
||||||
|
|
||||||
|
logger.log('baz');
|
||||||
|
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(consoleLogSpy.calls.argsFor(0)).toContain('baz');
|
||||||
|
|
||||||
|
logger.warn('qux');
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(consoleWarnSpy.calls.argsFor(0)).toContain('qux');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should prepend messages with the current date and logger\'s scope', () => {
|
||||||
|
const mockDate = new Date(1337);
|
||||||
|
const expectedDateStr = `[${mockDate}]`;
|
||||||
|
const expectedScopeStr = 'TestScope: ';
|
||||||
|
|
||||||
|
jasmine.clock().mockDate(mockDate);
|
||||||
|
jasmine.clock().withMock(() => {
|
||||||
|
logger.error();
|
||||||
|
logger.info();
|
||||||
|
logger.log();
|
||||||
|
logger.warn();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
||||||
|
expect(consoleInfoSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
||||||
|
expect(consoleLogSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalledWith(expectedDateStr, expectedScopeStr);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should pass all arguments to `console`', () => {
|
||||||
|
const someString = jasmine.any(String);
|
||||||
|
|
||||||
|
logger.error('foo1', 'foo2');
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalledWith(someString, someString, 'foo1', 'foo2');
|
||||||
|
|
||||||
|
logger.info('bar1', 'bar2');
|
||||||
|
expect(consoleInfoSpy).toHaveBeenCalledWith(someString, someString, 'bar1', 'bar2');
|
||||||
|
|
||||||
|
logger.log('baz1', 'baz2');
|
||||||
|
expect(consoleLogSpy).toHaveBeenCalledWith(someString, someString, 'baz1', 'baz2');
|
||||||
|
|
||||||
|
logger.warn('qux1', 'qux2');
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalledWith(someString, someString, 'qux1', 'qux2');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
declare namespace jasmine {
|
|
||||||
export interface DoneFn extends Function {
|
|
||||||
(): void;
|
|
||||||
fail: (message: Error | string) => void;
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,5 +3,4 @@ import {runTests} from '../lib/common/run-tests';
|
|||||||
|
|
||||||
// Run
|
// Run
|
||||||
const specFiles = [`${__dirname}/**/*.spec.js`];
|
const specFiles = [`${__dirname}/**/*.spec.js`];
|
||||||
const helpers = [`${__dirname}/helpers.js`];
|
runTests(specFiles);
|
||||||
runTests(specFiles, helpers);
|
|
||||||
|
@ -5,6 +5,7 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as shell from 'shelljs';
|
import * as shell from 'shelljs';
|
||||||
import {SHORT_SHA_LEN} from '../../lib/common/constants';
|
import {SHORT_SHA_LEN} from '../../lib/common/constants';
|
||||||
|
import {Logger} from '../../lib/common/utils';
|
||||||
import {BuildCreator} from '../../lib/preview-server/build-creator';
|
import {BuildCreator} from '../../lib/preview-server/build-creator';
|
||||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
|
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
|
||||||
import {PreviewServerError} from '../../lib/preview-server/preview-error';
|
import {PreviewServerError} from '../../lib/preview-server/preview-error';
|
||||||
@ -491,7 +492,7 @@ describe('BuildCreator', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cpExecCbs = [];
|
cpExecCbs = [];
|
||||||
|
|
||||||
consoleWarnSpy = spyOn(console, 'warn');
|
consoleWarnSpy = spyOn(Logger.prototype, 'warn');
|
||||||
shellChmodSpy = spyOn(shell, 'chmod');
|
shellChmodSpy = spyOn(shell, 'chmod');
|
||||||
shellRmSpy = spyOn(shell, 'rm');
|
shellRmSpy = spyOn(shell, 'rm');
|
||||||
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: (...args: any[]) => void) => cpExecCbs.push(cb));
|
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: (...args: any[]) => void) => cpExecCbs.push(cb));
|
||||||
@ -513,8 +514,7 @@ describe('BuildCreator', () => {
|
|||||||
|
|
||||||
it('should log (as a warning) any stderr output if extracting succeeded', done => {
|
it('should log (as a warning) any stderr output if extracting succeeded', done => {
|
||||||
(bc as any).extractArchive('foo', 'bar').
|
(bc as any).extractArchive('foo', 'bar').
|
||||||
then(() => expect(consoleWarnSpy)
|
then(() => expect(consoleWarnSpy).toHaveBeenCalledWith('This is stderr')).
|
||||||
.toHaveBeenCalledWith(jasmine.any(String), 'BuildCreator: ', 'This is stderr')).
|
|
||||||
then(done);
|
then(done);
|
||||||
|
|
||||||
cpExecCbs[0](null, 'This is stdout', 'This is stderr');
|
cpExecCbs[0](null, 'This is stdout', 'This is stderr');
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as nock from 'nock';
|
import * as nock from 'nock';
|
||||||
|
import {resolve as resolvePath} from 'path';
|
||||||
import {BuildInfo, CircleCiApi} from '../../lib/common/circle-ci-api';
|
import {BuildInfo, CircleCiApi} from '../../lib/common/circle-ci-api';
|
||||||
|
import {Logger} from '../../lib/common/utils';
|
||||||
import {BuildRetriever} from '../../lib/preview-server/build-retriever';
|
import {BuildRetriever} from '../../lib/preview-server/build-retriever';
|
||||||
|
|
||||||
describe('BuildRetriever', () => {
|
describe('BuildRetriever', () => {
|
||||||
const MAX_DOWNLOAD_SIZE = 10000;
|
const MAX_DOWNLOAD_SIZE = 10000;
|
||||||
const DOWNLOAD_DIR = '/DOWNLOAD/DIR';
|
const DOWNLOAD_DIR = resolvePath('/DOWNLOAD/DIR');
|
||||||
const BASE_URL = 'http://test.com';
|
const BASE_URL = 'http://test.com';
|
||||||
const ARTIFACT_PATH = '/some/path/build.zip';
|
const ARTIFACT_PATH = '/some/path/build.zip';
|
||||||
|
|
||||||
@ -29,10 +31,6 @@ describe('BuildRetriever', () => {
|
|||||||
vcs_revision: 'COMMIT',
|
vcs_revision: 'COMMIT',
|
||||||
};
|
};
|
||||||
|
|
||||||
spyOn(console, 'log');
|
|
||||||
spyOn(console, 'warn');
|
|
||||||
spyOn(console, 'error');
|
|
||||||
|
|
||||||
api = new CircleCiApi('ORG', 'REPO', 'TOKEN');
|
api = new CircleCiApi('ORG', 'REPO', 'TOKEN');
|
||||||
spyOn(api, 'getBuildInfo').and.callFake(() => Promise.resolve(BUILD_INFO));
|
spyOn(api, 'getBuildInfo').and.callFake(() => Promise.resolve(BUILD_INFO));
|
||||||
getBuildArtifactUrlSpy = spyOn(api, 'getBuildArtifactUrl')
|
getBuildArtifactUrlSpy = spyOn(api, 'getBuildArtifactUrl')
|
||||||
@ -91,6 +89,7 @@ describe('BuildRetriever', () => {
|
|||||||
let retriever: BuildRetriever;
|
let retriever: BuildRetriever;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
spyOn(Logger.prototype, 'warn');
|
||||||
retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR);
|
retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -133,11 +132,14 @@ describe('BuildRetriever', () => {
|
|||||||
|
|
||||||
it('should write the artifact file to disk', async () => {
|
it('should write the artifact file to disk', async () => {
|
||||||
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS);
|
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS);
|
||||||
|
const downloadPath = resolvePath(`${DOWNLOAD_DIR}/777-COMMIT-build.zip`);
|
||||||
|
|
||||||
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH);
|
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH);
|
||||||
expect(writeFileSpy)
|
expect(writeFileSpy).toHaveBeenCalledWith(downloadPath, jasmine.any(Buffer), jasmine.any(Function));
|
||||||
.toHaveBeenCalledWith(`${DOWNLOAD_DIR}/777-COMMIT-build.zip`, jasmine.any(Buffer), jasmine.any(Function));
|
|
||||||
const buffer: Buffer = writeFileSpy.calls.mostRecent().args[1];
|
const buffer: Buffer = writeFileSpy.calls.mostRecent().args[1];
|
||||||
expect(buffer.toString()).toEqual(ARTIFACT_CONTENTS);
|
expect(buffer.toString()).toEqual(ARTIFACT_CONTENTS);
|
||||||
|
|
||||||
artifactRequest.done();
|
artifactRequest.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import * as supertest from 'supertest';
|
import * as supertest from 'supertest';
|
||||||
import {promisify} from 'util';
|
|
||||||
import {CircleCiApi} from '../../lib/common/circle-ci-api';
|
import {CircleCiApi} from '../../lib/common/circle-ci-api';
|
||||||
import {GithubApi} from '../../lib/common/github-api';
|
import {GithubApi} from '../../lib/common/github-api';
|
||||||
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
import {GithubPullRequests} from '../../lib/common/github-pull-requests';
|
||||||
import {GithubTeams} from '../../lib/common/github-teams';
|
import {GithubTeams} from '../../lib/common/github-teams';
|
||||||
|
import {Logger} from '../../lib/common/utils';
|
||||||
import {BuildCreator} from '../../lib/preview-server/build-creator';
|
import {BuildCreator} from '../../lib/preview-server/build-creator';
|
||||||
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
|
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
|
||||||
import {BuildRetriever, GithubInfo} from '../../lib/preview-server/build-retriever';
|
import {BuildRetriever, GithubInfo} from '../../lib/preview-server/build-retriever';
|
||||||
@ -38,15 +38,18 @@ describe('PreviewServerFactory', () => {
|
|||||||
significantFilesPattern: '^(?:aio|packages)\\/(?!.*[._]spec\\.[jt]s$)',
|
significantFilesPattern: '^(?:aio|packages)\\/(?!.*[._]spec\\.[jt]s$)',
|
||||||
trustedPrLabel: 'trusted: pr-label',
|
trustedPrLabel: 'trusted: pr-label',
|
||||||
};
|
};
|
||||||
|
let loggerErrorSpy: jasmine.Spy;
|
||||||
|
let loggerInfoSpy: jasmine.Spy;
|
||||||
|
let loggerLogSpy: jasmine.Spy;
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
const createPreviewServer = (partialConfig: Partial<PreviewServerConfig> = {}) =>
|
const createPreviewServer = (partialConfig: Partial<PreviewServerConfig> = {}) =>
|
||||||
PreviewServerFactory.create({...defaultConfig, ...partialConfig});
|
PreviewServerFactory.create({...defaultConfig, ...partialConfig});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(console, 'error');
|
loggerErrorSpy = spyOn(Logger.prototype, 'error');
|
||||||
spyOn(console, 'info');
|
loggerInfoSpy = spyOn(Logger.prototype, 'info');
|
||||||
spyOn(console, 'log');
|
loggerLogSpy = spyOn(Logger.prototype, 'log');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create()', () => {
|
describe('create()', () => {
|
||||||
@ -140,11 +143,10 @@ describe('PreviewServerFactory', () => {
|
|||||||
const server = createPreviewServer();
|
const server = createPreviewServer();
|
||||||
server.address = () => ({address: 'foo', family: '', port: 1337});
|
server.address = () => ({address: 'foo', family: '', port: 1337});
|
||||||
|
|
||||||
expect(console.info).not.toHaveBeenCalled();
|
expect(loggerInfoSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
server.emit('listening');
|
server.emit('listening');
|
||||||
expect(console.info).toHaveBeenCalledWith(
|
expect(loggerInfoSpy).toHaveBeenCalledWith('Up and running (and listening on foo:1337)...');
|
||||||
jasmine.any(String), 'PreviewServer: ', 'Up and running (and listening on foo:1337)...');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -241,10 +243,6 @@ describe('PreviewServerFactory', () => {
|
|||||||
let buildCreator: BuildCreator;
|
let buildCreator: BuildCreator;
|
||||||
let agent: supertest.SuperTest<supertest.Test>;
|
let agent: supertest.SuperTest<supertest.Test>;
|
||||||
|
|
||||||
// Helpers
|
|
||||||
const promisifyRequest = async (req: supertest.Request) => await promisify(req.end.bind(req))();
|
|
||||||
const verifyRequests = async (reqs: supertest.Request[]) => await Promise.all(reqs.map(promisifyRequest));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const circleCiApi = new CircleCiApi(defaultConfig.githubOrg, defaultConfig.githubRepo,
|
const circleCiApi = new CircleCiApi(defaultConfig.githubOrg, defaultConfig.githubRepo,
|
||||||
defaultConfig.circleCiToken);
|
defaultConfig.circleCiToken);
|
||||||
@ -257,14 +255,15 @@ describe('PreviewServerFactory', () => {
|
|||||||
buildCreator = new BuildCreator(defaultConfig.buildsDir);
|
buildCreator = new BuildCreator(defaultConfig.buildsDir);
|
||||||
|
|
||||||
const middleware = PreviewServerFactory.createMiddleware(buildRetriever, buildVerifier, buildCreator,
|
const middleware = PreviewServerFactory.createMiddleware(buildRetriever, buildVerifier, buildCreator,
|
||||||
defaultConfig);
|
defaultConfig);
|
||||||
agent = supertest.agent(middleware);
|
agent = supertest.agent(middleware);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('GET /health-check', () => {
|
describe('GET /health-check', () => {
|
||||||
|
|
||||||
it('should respond with 200', async () => {
|
it('should respond with 200', async () => {
|
||||||
await verifyRequests([
|
await Promise.all([
|
||||||
agent.get('/health-check').expect(200),
|
agent.get('/health-check').expect(200),
|
||||||
agent.get('/health-check/').expect(200),
|
agent.get('/health-check/').expect(200),
|
||||||
]);
|
]);
|
||||||
@ -272,7 +271,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should respond with 404 for non-GET requests', async () => {
|
it('should respond with 404 for non-GET requests', async () => {
|
||||||
await verifyRequests([
|
await Promise.all([
|
||||||
agent.put('/health-check').expect(404),
|
agent.put('/health-check').expect(404),
|
||||||
agent.post('/health-check').expect(404),
|
agent.post('/health-check').expect(404),
|
||||||
agent.patch('/health-check').expect(404),
|
agent.patch('/health-check').expect(404),
|
||||||
@ -282,7 +281,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should respond with 404 if the path does not match exactly', async () => {
|
it('should respond with 404 if the path does not match exactly', async () => {
|
||||||
await verifyRequests([
|
await Promise.all([
|
||||||
agent.get('/health-check/foo').expect(404),
|
agent.get('/health-check/foo').expect(404),
|
||||||
agent.get('/health-check-foo').expect(404),
|
agent.get('/health-check-foo').expect(404),
|
||||||
agent.get('/health-checknfoo').expect(404),
|
agent.get('/health-checknfoo').expect(404),
|
||||||
@ -294,7 +293,104 @@ describe('PreviewServerFactory', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('/circle-build', () => {
|
|
||||||
|
describe('GET /can-have-public-preview/<pr>', () => {
|
||||||
|
const baseUrl = '/can-have-public-preview';
|
||||||
|
const pr = 777;
|
||||||
|
const url = `${baseUrl}/${pr}`;
|
||||||
|
let bvGetPrIsTrustedSpy: jasmine.Spy;
|
||||||
|
let bvGetSignificantFilesChangedSpy: jasmine.Spy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted').and.returnValue(Promise.resolve(true));
|
||||||
|
bvGetSignificantFilesChangedSpy = spyOn(buildVerifier, 'getSignificantFilesChanged').
|
||||||
|
and.returnValue(Promise.resolve(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 404 for non-GET requests', async () => {
|
||||||
|
await Promise.all([
|
||||||
|
agent.put(url).expect(404),
|
||||||
|
agent.post(url).expect(404),
|
||||||
|
agent.patch(url).expect(404),
|
||||||
|
agent.delete(url).expect(404),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with 404 if the path does not match exactly', async () => {
|
||||||
|
await Promise.all([
|
||||||
|
agent.get('/can-have-public-preview/42/foo').expect(404),
|
||||||
|
agent.get('/can-have-public-preview-foo/42').expect(404),
|
||||||
|
agent.get('/can-have-public-previewnfoo/42').expect(404),
|
||||||
|
agent.get('/foo/can-have-public-preview/42').expect(404),
|
||||||
|
agent.get('/foo-can-have-public-preview/42').expect(404),
|
||||||
|
agent.get('/fooncan-have-public-preview/42').expect(404),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond appropriately if the PR did not touch any significant files', async () => {
|
||||||
|
bvGetSignificantFilesChangedSpy.and.returnValue(Promise.resolve(false));
|
||||||
|
|
||||||
|
const expectedResponse = {canHavePublicPreview: false, reason: 'No significant files touched.'};
|
||||||
|
const expectedLog = `PR:${pr} - Cannot have a public preview, because it did not touch any significant files.`;
|
||||||
|
|
||||||
|
await agent.get(url).expect(200, expectedResponse);
|
||||||
|
|
||||||
|
expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
|
||||||
|
expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||||
|
expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond appropriately if the PR is not automatically verifiable as "trusted"', async () => {
|
||||||
|
bvGetPrIsTrustedSpy.and.returnValue(Promise.resolve(false));
|
||||||
|
|
||||||
|
const expectedResponse = {canHavePublicPreview: false, reason: 'Not automatically verifiable as "trusted".'};
|
||||||
|
const expectedLog =
|
||||||
|
`PR:${pr} - Cannot have a public preview, because not automatically verifiable as "trusted".`;
|
||||||
|
|
||||||
|
await agent.get(url).expect(200, expectedResponse);
|
||||||
|
|
||||||
|
expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
|
||||||
|
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(pr);
|
||||||
|
expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond appropriately if the PR can have a preview', async () => {
|
||||||
|
const expectedResponse = {canHavePublicPreview: true, reason: null};
|
||||||
|
const expectedLog = `PR:${pr} - Can have a public preview.`;
|
||||||
|
|
||||||
|
await agent.get(url).expect(200, expectedResponse);
|
||||||
|
|
||||||
|
expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
|
||||||
|
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(pr);
|
||||||
|
expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with error if `getSignificantFilesChanged()` fails', async () => {
|
||||||
|
bvGetSignificantFilesChangedSpy.and.callFake(() => Promise.reject('getSignificantFilesChanged error'));
|
||||||
|
|
||||||
|
await agent.get(url).expect(500, 'getSignificantFilesChanged error');
|
||||||
|
expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', 'getSignificantFilesChanged error');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should respond with error if `getPrIsTrusted()` fails', async () => {
|
||||||
|
const error = new Error('getPrIsTrusted error');
|
||||||
|
bvGetPrIsTrustedSpy.and.callFake(() => { throw error; });
|
||||||
|
|
||||||
|
await agent.get(url).expect(500, 'getPrIsTrusted error');
|
||||||
|
expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('POST /circle-build', () => {
|
||||||
let getGithubInfoSpy: jasmine.Spy;
|
let getGithubInfoSpy: jasmine.Spy;
|
||||||
let getSignificantFilesChangedSpy: jasmine.Spy;
|
let getSignificantFilesChangedSpy: jasmine.Spy;
|
||||||
let downloadBuildArtifactSpy: jasmine.Spy;
|
let downloadBuildArtifactSpy: jasmine.Spy;
|
||||||
@ -359,8 +455,8 @@ describe('PreviewServerFactory', () => {
|
|||||||
await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
|
await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
|
||||||
expect(getGithubInfoSpy).not.toHaveBeenCalled();
|
expect(getGithubInfoSpy).not.toHaveBeenCalled();
|
||||||
expect(getSignificantFilesChangedSpy).not.toHaveBeenCalled();
|
expect(getSignificantFilesChangedSpy).not.toHaveBeenCalled();
|
||||||
expect(console.log).toHaveBeenCalledWith(jasmine.any(String), 'PreviewServer: ',
|
expect(loggerLogSpy).toHaveBeenCalledWith(
|
||||||
'Build:12345, Job:lint -', 'Skipping preview processing because this is not the "aio_preview" job.');
|
'Build:12345, Job:lint -', 'Skipping preview processing because this is not the "aio_preview" job.');
|
||||||
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
|
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
|
||||||
expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
|
expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||||
expect(createBuildSpy).not.toHaveBeenCalled();
|
expect(createBuildSpy).not.toHaveBeenCalled();
|
||||||
@ -371,7 +467,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
|
await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
|
||||||
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
|
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
|
||||||
expect(getSignificantFilesChangedSpy).toHaveBeenCalledWith(PR, jasmine.any(RegExp));
|
expect(getSignificantFilesChangedSpy).toHaveBeenCalledWith(PR, jasmine.any(RegExp));
|
||||||
expect(console.log).toHaveBeenCalledWith(jasmine.any(String), 'PreviewServer: ',
|
expect(loggerLogSpy).toHaveBeenCalledWith(
|
||||||
'PR:777, Build:12345 - Skipping preview processing because this PR did not touch any significant files.');
|
'PR:777, Build:12345 - Skipping preview processing because this PR did not touch any significant files.');
|
||||||
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
|
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
|
||||||
expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
|
expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||||
@ -467,7 +563,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should respond with 404 for non-POST requests', async () => {
|
it('should respond with 404 for non-POST requests', async () => {
|
||||||
await verifyRequests([
|
await Promise.all([
|
||||||
agent.get(url).expect(404),
|
agent.get(url).expect(404),
|
||||||
agent.put(url).expect(404),
|
agent.put(url).expect(404),
|
||||||
agent.patch(url).expect(404),
|
agent.patch(url).expect(404),
|
||||||
@ -482,7 +578,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
const request1 = agent.post(url);
|
const request1 = agent.post(url);
|
||||||
const request2 = agent.post(url).send();
|
const request2 = agent.post(url).send();
|
||||||
|
|
||||||
await verifyRequests([
|
await Promise.all([
|
||||||
request1.expect(400, responseBody),
|
request1.expect(400, responseBody),
|
||||||
request2.expect(400, responseBody),
|
request2.expect(400, responseBody),
|
||||||
]);
|
]);
|
||||||
@ -495,7 +591,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
const request1 = agent.post(url).send({});
|
const request1 = agent.post(url).send({});
|
||||||
const request2 = agent.post(url).send({number: null});
|
const request2 = agent.post(url).send({number: null});
|
||||||
|
|
||||||
await verifyRequests([
|
await Promise.all([
|
||||||
request1.expect(400, `${responseBodyPrefix} {}`),
|
request1.expect(400, `${responseBodyPrefix} {}`),
|
||||||
request2.expect(400, `${responseBodyPrefix} {"number":null}`),
|
request2.expect(400, `${responseBodyPrefix} {"number":null}`),
|
||||||
]);
|
]);
|
||||||
@ -503,7 +599,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should call \'BuildVerifier#gtPrIsTrusted()\' with the correct arguments', async () => {
|
it('should call \'BuildVerifier#gtPrIsTrusted()\' with the correct arguments', async () => {
|
||||||
await promisifyRequest(createRequest(+pr));
|
await createRequest(+pr);
|
||||||
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
|
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -511,9 +607,8 @@ describe('PreviewServerFactory', () => {
|
|||||||
it('should propagate errors from BuildVerifier', async () => {
|
it('should propagate errors from BuildVerifier', async () => {
|
||||||
bvGetPrIsTrustedSpy.and.callFake(() => Promise.reject('Test'));
|
bvGetPrIsTrustedSpy.and.callFake(() => Promise.reject('Test'));
|
||||||
|
|
||||||
const req = createRequest(+pr).expect(500, 'Test');
|
await createRequest(+pr).expect(500, 'Test');
|
||||||
|
|
||||||
await promisifyRequest(req);
|
|
||||||
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
|
expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
|
||||||
expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
|
expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -522,19 +617,17 @@ describe('PreviewServerFactory', () => {
|
|||||||
it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', async () => {
|
it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', async () => {
|
||||||
bvGetPrIsTrustedSpy.and.callFake((pr2: number) => Promise.resolve(pr2 === 42));
|
bvGetPrIsTrustedSpy.and.callFake((pr2: number) => Promise.resolve(pr2 === 42));
|
||||||
|
|
||||||
await promisifyRequest(createRequest(24));
|
await createRequest(24);
|
||||||
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(24, false);
|
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(24, false);
|
||||||
|
|
||||||
await promisifyRequest(createRequest(42));
|
await createRequest(42);
|
||||||
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(42, true);
|
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(42, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should propagate errors from BuildCreator', async () => {
|
it('should propagate errors from BuildCreator', async () => {
|
||||||
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject('Test'));
|
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject('Test'));
|
||||||
|
await createRequest(+pr).expect(500, 'Test');
|
||||||
const req = createRequest(+pr).expect(500, 'Test');
|
|
||||||
await verifyRequests([req]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -544,7 +637,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||||
|
|
||||||
const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200]));
|
const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200]));
|
||||||
await verifyRequests(reqs);
|
await Promise.all(reqs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -552,7 +645,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||||
|
|
||||||
const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200]));
|
const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200]));
|
||||||
await verifyRequests(reqs);
|
await Promise.all(reqs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -560,14 +653,13 @@ describe('PreviewServerFactory', () => {
|
|||||||
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
||||||
|
|
||||||
const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200]));
|
const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200]));
|
||||||
await verifyRequests(reqs);
|
await Promise.all(reqs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should respond with 200 (and do nothing) if \'action\' implies no visibility change', async () => {
|
it('should respond with 200 (and do nothing) if \'action\' implies no visibility change', async () => {
|
||||||
const promises = ['foo', 'notlabeled'].
|
const promises = ['foo', 'notlabeled'].
|
||||||
map(action => createRequest(+pr, action).expect(200, http.STATUS_CODES[200])).
|
map(action => createRequest(+pr, action).expect(200, http.STATUS_CODES[200]));
|
||||||
map(promisifyRequest);
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
|
expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||||
@ -584,7 +676,7 @@ describe('PreviewServerFactory', () => {
|
|||||||
it('should respond with 404', async () => {
|
it('should respond with 404', async () => {
|
||||||
const responseFor = (method: string) => `Unknown resource in request: ${method.toUpperCase()} /some/url`;
|
const responseFor = (method: string) => `Unknown resource in request: ${method.toUpperCase()} /some/url`;
|
||||||
|
|
||||||
await verifyRequests([
|
await Promise.all([
|
||||||
agent.get('/some/url').expect(404, responseFor('get')),
|
agent.get('/some/url').expect(404, responseFor('get')),
|
||||||
agent.put('/some/url').expect(404, responseFor('put')),
|
agent.put('/some/url').expect(404, responseFor('put')),
|
||||||
agent.post('/some/url').expect(404, responseFor('post')),
|
agent.post('/some/url').expect(404, responseFor('post')),
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
|||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
|
|
||||||
# Set up env variables
|
# Set up env variables
|
||||||
|
export AIO_CIRCLE_CI_TOKEN=UNUSED_CIRCLE_CI_TOKEN
|
||||||
export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null)
|
export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null)
|
||||||
|
|
||||||
# Run the clean-up
|
# Run the clean-up
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
TODO (gkalpak): Add docs. Mention:
|
TODO (gkalpak): Add docs. Mention:
|
||||||
- Testing on CI.
|
- Testing on CI.
|
||||||
Relevant files: `scripts/ci/test-aio.sh`, `aio/aio-builds-setup/scripts/test.sh`
|
Relevant files: `aio/aio-builds-setup/scripts/test.sh`
|
||||||
- Deploying from CI.
|
- Deploying from CI.
|
||||||
Relevant files: `scripts/ci/deploy.sh`, `aio/scripts/deploy-to-firebase.sh`
|
Relevant files: `.circleci/config.yml`, `scripts/ci/deploy.sh`, `aio/scripts/build-artifacts.sh`,
|
||||||
|
`aio/scripts/deploy-to-firebase.sh`
|
||||||
|
@ -34,34 +34,31 @@ container:
|
|||||||
|
|
||||||
|
|
||||||
### On CI (CircleCI)
|
### On CI (CircleCI)
|
||||||
- Build job completes successfully.
|
- The CI script builds the angular.io project.
|
||||||
- The CI script checks whether the build job was initiated by a PR against the angular/angular
|
|
||||||
master branch.
|
|
||||||
- The CI script checks whether the PR has touched any files that might affect the angular.io app
|
|
||||||
(currently the `aio/` or `packages/` directories, ignoring spec files).
|
|
||||||
- The CI script gzips and stores the build artifacts in the CI infrastructure.
|
- The CI script gzips and stores the build artifacts in the CI infrastructure.
|
||||||
- When the build completes CircleCI triggers a webhook on the preview-server.
|
- When the build completes, CircleCI triggers a webhook on the preview-server.
|
||||||
|
|
||||||
More info on how to set things up on CI can be found [here](misc--integrate-with-ci.md).
|
More info on how to set things up on CI can be found [here](misc--integrate-with-ci.md).
|
||||||
|
|
||||||
|
|
||||||
### Hosting build artifacts
|
### Hosting build artifacts
|
||||||
|
|
||||||
- nginx receives the webhook trigger and passes it through to the preview server.
|
- nginx receives the webhook trigger and passes it through to the preview server.
|
||||||
|
- The preview-server runs several preliminary checks to determine whether the request is valid and
|
||||||
|
whether the corresponding PR can have a (public or non-public) preview (more details can be found
|
||||||
|
[here](overview--security-model.md)).
|
||||||
- The preview-server makes a request to CircleCI for the URL of the AIO build artifacts.
|
- The preview-server makes a request to CircleCI for the URL of the AIO build artifacts.
|
||||||
- The preview-server makes a request to this URL to receive the artifact - failing if the size
|
- The preview-server makes a request to this URL to receive the artifact - failing if the size
|
||||||
exceeds the specified max file size - and stores it in a temporary location.
|
exceeds the specified max file size - and stores it in a temporary location.
|
||||||
- The preview-server runs several checks to determine whether the request should be accepted and
|
- The preview-server runs more checks to determine whether the preview should be publicly accessible
|
||||||
whether it should be publicly accessible or stored for later verification (more details can be
|
or stored for later verification (more details can be found [here](overview--security-model.md)).
|
||||||
found [here](overview--security-model.md)).
|
|
||||||
- The preview-server changes the "visibility" of the associated PR, if necessary. For example, if
|
- The preview-server changes the "visibility" of the associated PR, if necessary. For example, if
|
||||||
builds for the same PR had been previously deployed as non-public and the current build has been
|
builds for the same PR had been previously deployed as non-public and the current build has been
|
||||||
automatically verified, all previous builds are made public as well.
|
automatically verified, all previous builds are made public as well.
|
||||||
If the PR transitions from "non-public" to "public", the preview-server posts a comment on the
|
If the PR transitions from "non-public" to "public", the preview-server posts a comment on the
|
||||||
corresponding PR on GitHub mentioning the SHAs and the links where the previews can be found.
|
corresponding PR on GitHub mentioning the SHAs and the links where the previews can be found.
|
||||||
- The preview-server verifies that it is not trying to overwrite an existing build.
|
- The preview-server verifies that it is not trying to overwrite an existing build.
|
||||||
- The preview-server deploys the artifacts to a sub-directory named after the PR number and the first
|
- The preview-server deploys the artifacts to a sub-directory named after the PR number and the
|
||||||
few characters of the SHA: `<PR>/<SHA>/`
|
first few characters of the SHA: `<PR>/<SHA>/`
|
||||||
(Non-publicly accessible PRs will be stored in a different location, but again derived from the PR
|
(Non-publicly accessible PRs will be stored in a different location, but again derived from the PR
|
||||||
number and SHA.)
|
number and SHA.)
|
||||||
- If the PR is publicly accessible, the preview-server posts a comment on the corresponding PR on
|
- If the PR is publicly accessible, the preview-server posts a comment on the corresponding PR on
|
||||||
@ -101,8 +98,8 @@ More info on the possible HTTP status codes and their meaning can be found
|
|||||||
|
|
||||||
### Removing obsolete artifacts
|
### Removing obsolete artifacts
|
||||||
In order to avoid flooding the disk with unnecessary build artifacts, there is a cronjob that runs a
|
In order to avoid flooding the disk with unnecessary build artifacts, there is a cronjob that runs a
|
||||||
clean-up tasks once a day. The task retrieves all open PRs from GitHub and removes all directories
|
clean-up task once a day. The task retrieves all open PRs from GitHub and removes all directories
|
||||||
that do not correspond with an open PR.
|
that do not correspond to an open PR.
|
||||||
|
|
||||||
|
|
||||||
### Health-check
|
### Health-check
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# Overview - HTTP Status Codes
|
# Overview - HTTP Status Codes
|
||||||
|
|
||||||
|
|
||||||
This is a list of all the possible HTTP status codes returned by the nginx and preview servers, along
|
This is a list of all the possible HTTP status codes returned by the nginx and preview servers,
|
||||||
with a brief explanation of what they mean:
|
along with a brief explanation of what they mean:
|
||||||
|
|
||||||
|
|
||||||
## `http://*.ngbuilds.io/*`
|
## `http://*.ngbuilds.io/*`
|
||||||
@ -25,6 +25,23 @@ with a brief explanation of what they mean:
|
|||||||
File not found.
|
File not found.
|
||||||
|
|
||||||
|
|
||||||
|
## `https://ngbuilds.io/can-have-public-preview/<pr>`
|
||||||
|
|
||||||
|
- **200 (OK)**:
|
||||||
|
Whether the PR can have a public preview (based on its author, label, changed files).
|
||||||
|
_Response type:_ JSON
|
||||||
|
_Response format:_
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
canHavePublicPreview: boolean,
|
||||||
|
reason: string | null,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **405 (Method Not Allowed)**:
|
||||||
|
Request method other than GET.
|
||||||
|
|
||||||
|
|
||||||
## `https://ngbuilds.io/circle-build`
|
## `https://ngbuilds.io/circle-build`
|
||||||
|
|
||||||
- **201 (Created)**:
|
- **201 (Created)**:
|
||||||
|
@ -11,8 +11,8 @@ part of the CI process and serving them publicly.
|
|||||||
|
|
||||||
## Security objectives
|
## Security objectives
|
||||||
|
|
||||||
- **Prevent hosting arbitrary content to on servers.**
|
- **Prevent hosting arbitrary content on our servers.**
|
||||||
Since there is no restriction on who can submit a PR, we cannot allow arbitrary untrusted PRs'
|
Since there is no restriction on who can submit a PR, we cannot allow arbitrary, untrusted PRs'
|
||||||
build artifacts to be hosted.
|
build artifacts to be hosted.
|
||||||
|
|
||||||
- **Prevent overwriting other people's hosted build artifacts.**
|
- **Prevent overwriting other people's hosted build artifacts.**
|
||||||
@ -40,40 +40,49 @@ part of the CI process and serving them publicly.
|
|||||||
### In a nutshell
|
### In a nutshell
|
||||||
The implemented approach can be broken up to the following sub-tasks:
|
The implemented approach can be broken up to the following sub-tasks:
|
||||||
|
|
||||||
0. Receive notification from CircleCI of a completed build.
|
1. Receive notification from CircleCI of a completed build.
|
||||||
1. Verify that the build is valid and download the artifact.
|
2. Verify that the build is valid and can have a preview.
|
||||||
2. Fetch the PR's metadata, including author and labels.
|
3. Download the build artifact.
|
||||||
3. Check whether the PR can be automatically verified as "trusted" (based on its author or labels).
|
4. Fetch the PR's metadata, including author and labels.
|
||||||
4. If necessary, update the corresponding PR's verification status.
|
5. Check whether the PR can be automatically verified as "trusted" (based on its author or labels).
|
||||||
5. Deploy the artifacts to the corresponding PR's directory.
|
6. If necessary, update the corresponding PR's verification status.
|
||||||
6. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established
|
7. Deploy the artifacts to the corresponding PR's directory.
|
||||||
|
8. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established
|
||||||
during deployment will remain valid until the artifacts are removed).
|
during deployment will remain valid until the artifacts are removed).
|
||||||
7. Prevent hosted preview files from accessing anything outside their directory.
|
9. Prevent hosted preview files from accessing anything outside their directory.
|
||||||
|
|
||||||
|
|
||||||
### Implementation details
|
### Implementation details
|
||||||
This section describes how each of the aforementioned sub-tasks is accomplished:
|
This section describes how each of the aforementioned sub-tasks is accomplished:
|
||||||
|
|
||||||
0. **Receive notification from CircleCI of a completed build**
|
1. **Receive notification from CircleCI of a completed build**
|
||||||
|
|
||||||
CircleCI is configured to trigger a webhook on our preview-server whenever a build completes.
|
CircleCI is configured to trigger a webhook on our preview-server whenever a build completes.
|
||||||
The payload contains the number of the build that completed.
|
The payload contains the number of the build that completed.
|
||||||
|
|
||||||
1. **Verify that the build is valid and download the artifact.**
|
2. **Verify that the build is valid and can have a preview.**
|
||||||
|
|
||||||
We cannot trust that the data in the webhook trigger is authentic, so we only extract the build
|
We cannot trust that the data in the webhook trigger is authentic, so we only extract the build
|
||||||
number and then run a direct query against the CircleCI API to get hold of the real data for
|
number and then run a direct query against the CircleCI API to get hold of the real data for
|
||||||
the given build number.
|
the given build number.
|
||||||
|
|
||||||
If the build was not successful then we ignore this trigger. Otherwise we check that the
|
We perform a number of preliminary checks:
|
||||||
associated github organisation and repository are what we expect (e.g. angular/angular).
|
- Was the webhook triggered by the designated CircleCI job (currently `aio_preview`)?
|
||||||
|
- Was the build successful?
|
||||||
|
- Are the associated GitHub organisation and repository what we expect (e.g. `angular/angular`)?
|
||||||
|
- Has the PR touched any files that might affect the angular.io app (currently the `aio/` or
|
||||||
|
`packages/` directories, ignoring spec files)?
|
||||||
|
|
||||||
Next we make another call to the CircleCI API to get a list of the URLS for artifacts of that
|
If any of the preliminary checks fails, the process is aborted and not preview is generated.
|
||||||
|
|
||||||
|
3. **Download the build artifact.**
|
||||||
|
|
||||||
|
Next we make another call to the CircleCI API to get a list of the URLs for artifacts of that
|
||||||
build. If there is one that matches the configured artifact path, we download the contents of the
|
build. If there is one that matches the configured artifact path, we download the contents of the
|
||||||
build artifact and store it in a local folder. This download has a maximum size limit to prevent
|
build artifact and store it in a local folder. This download has a maximum size limit to prevent
|
||||||
PRs from producing artifacts that are so large they would cause the preview server to crash.
|
PRs from producing artifacts that are so large they would cause the preview server to crash.
|
||||||
|
|
||||||
2. **Fetch the PR's metadata, including author and labels**.
|
4. **Fetch the PR's metadata, including author and labels**.
|
||||||
|
|
||||||
Once we have securely downloaded the artifact for a build, we retrieve the PR's metadata -
|
Once we have securely downloaded the artifact for a build, we retrieve the PR's metadata -
|
||||||
including the author's username and the labels - using the
|
including the author's username and the labels - using the
|
||||||
@ -81,7 +90,7 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
|||||||
To avoid rate-limit restrictions, we use a Personal Access Token (issued by
|
To avoid rate-limit restrictions, we use a Personal Access Token (issued by
|
||||||
[@mary-poppins](https://github.com/mary-poppins)).
|
[@mary-poppins](https://github.com/mary-poppins)).
|
||||||
|
|
||||||
3. **Check whether the PR can be automatically verified as "trusted"**.
|
5. **Check whether the PR can be automatically verified as "trusted"**.
|
||||||
|
|
||||||
"Trusted" means that we are confident that the build artifacts are suitable for being deployed
|
"Trusted" means that we are confident that the build artifacts are suitable for being deployed
|
||||||
and publicly accessible on the preview server. There are two ways to check that:
|
and publicly accessible on the preview server. There are two ways to check that:
|
||||||
@ -93,31 +102,32 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
|||||||
`read:org` scope issued by a user that can "see" the specified GitHub organization.
|
`read:org` scope issued by a user that can "see" the specified GitHub organization.
|
||||||
Here too, we use the token by @mary-poppins.
|
Here too, we use the token by @mary-poppins.
|
||||||
|
|
||||||
4. **If necessary update the corresponding PR's verification status**.
|
6. **If necessary update the corresponding PR's verification status**.
|
||||||
|
|
||||||
Once we have determined whether the PR is considered "trusted", we update its "visibility" (i.e.
|
Once we have determined whether the PR is considered "trusted", we update its "visibility" (i.e.
|
||||||
whether it is publicly accessible or not), based on the new verification status. For example, if
|
whether it is publicly accessible or not), based on the new verification status. For example, if
|
||||||
a PR was initially considered "not trusted" but the check triggered by a new build determined
|
a PR was initially considered "not trusted" but the check triggered by a new build determined
|
||||||
otherwise, the PR (and all the previously hosted previews) are made public. It works the same
|
otherwise, the PR (and all the previously downloaded previews) are made public. It works the same
|
||||||
way if a PR has gone from "trusted" to "not trusted".
|
way if a PR has gone from "trusted" to "not trusted".
|
||||||
|
|
||||||
5. **Deploy the artifacts to the corresponding PR's directory.**
|
7. **Deploy the artifacts to the corresponding PR's directory.**
|
||||||
|
|
||||||
With the preceding steps, we have verified that the build artifacts are valid.
|
With the preceding steps, we have verified that the build artifacts are valid. Additionally, we
|
||||||
Additionally, we have determined whether the PR can be trusted to have its previews
|
have determined whether the PR can be trusted to have its previews publicly accessible or whether
|
||||||
publicly accessible or whether further verification is necessary. The artifacts will be stored to
|
further verification is necessary.
|
||||||
the PR's directory, but will not be publicly accessible unless the PR has been verified.
|
|
||||||
Essentially, as long as sub-tasks 1, 2 and 3 can be securely accomplished, it is possible to
|
|
||||||
"project" the trust we have in a team's members through the PR to the build artifacts.
|
|
||||||
|
|
||||||
6. **Prevent overwriting previously deployed artifacts**.
|
The artifacts will be stored to the PR's directory, but will not be publicly accessible unless
|
||||||
|
the PR has been verified. Essentially, as long as sub-tasks 2, 3, 4 and 5 can be securely
|
||||||
|
accomplished, it is possible to "project" the trust we have in a team's members through the PR to
|
||||||
|
the build artifacts.
|
||||||
|
|
||||||
|
8. **Prevent overwriting previously deployed artifacts**.
|
||||||
|
|
||||||
In order to enforce this restriction (and ensure that the deployed artifacts' validity is
|
In order to enforce this restriction (and ensure that the deployed artifacts' validity is
|
||||||
preserved throughout their "lifetime"), the server that handles the artifacts (currently a Node.js
|
preserved throughout their "lifetime"), the server that handles the artifacts (currently a Node.js Express server) rejects builds that have already been handled.
|
||||||
Express server) rejects builds that have already been handled.
|
|
||||||
_Note: A PR can contain multiple builds; one for each SHA that was built on CircleCI._
|
_Note: A PR can contain multiple builds; one for each SHA that was built on CircleCI._
|
||||||
|
|
||||||
7. **Prevent hosted preview files from accessing anything outside their directory.**
|
9. **Prevent hosted preview files from accessing anything outside their directory.**
|
||||||
|
|
||||||
Nginx (which is used to serve the hosted preview) has been configured to not follow symlinks
|
Nginx (which is used to serve the hosted preview) has been configured to not follow symlinks
|
||||||
outside of the directory where the preview files are stored.
|
outside of the directory where the preview files are stored.
|
||||||
@ -130,10 +140,10 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
|||||||
This means that any secret access keys need only be stored on the preview-server and not on any of
|
This means that any secret access keys need only be stored on the preview-server and not on any of
|
||||||
the CI build infrastructure (e.g. CircleCI).
|
the CI build infrastructure (e.g. CircleCI).
|
||||||
|
|
||||||
- Each trusted PR author has full control over the content that is hosted as a preview for their PRs.
|
- Each trusted PR author has full control over the content that is hosted as a preview for their
|
||||||
Part of the security model relies on the trustworthiness of these authors.
|
PRs. Part of the security model relies on the trustworthiness of these authors.
|
||||||
|
|
||||||
- Adding the specified label on a PR to mark it as trusted, gives the author full control over
|
- Adding the specified label on a PR to mark it as trusted, gives the author full control over the
|
||||||
the content that is hosted for the specific PR preview (e.g. by pushing more commits to it).
|
content that is hosted for the specific PR preview (e.g. by pushing more commits to it). The user
|
||||||
The user adding the label is responsible for ensuring that this control is not abused and that
|
adding the label is responsible for ensuring that this control is not abused and that the PR is
|
||||||
the PR is either closed (one way of another) or the access is revoked.
|
either closed (one way of another) or the access is revoked.
|
||||||
|
@ -8,7 +8,7 @@ Necessary secrets:
|
|||||||
1. `GITHUB_TOKEN`
|
1. `GITHUB_TOKEN`
|
||||||
- Used for:
|
- Used for:
|
||||||
- Retrieving open PRs without rate-limiting.
|
- Retrieving open PRs without rate-limiting.
|
||||||
- Retrieving PR author.
|
- Retrieving PR info, such as author, labels, changed files.
|
||||||
- Retrieving members of the trusted GitHub teams.
|
- Retrieving members of the trusted GitHub teams.
|
||||||
- Posting comments with preview links on PRs.
|
- Posting comments with preview links on PRs.
|
||||||
|
|
||||||
@ -25,8 +25,9 @@ Necessary secrets:
|
|||||||
- Generate new token with the `public_repo` scope.
|
- Generate new token with the `public_repo` scope.
|
||||||
|
|
||||||
2. `CIRCLE_CI_TOKEN`
|
2. `CIRCLE_CI_TOKEN`
|
||||||
- Visit https://circleci.com/gh/angular/angular/edit#api
|
- Visit https://circleci.com/gh/angular/angular/edit#api.
|
||||||
- Create an API token with `Build Artifacts` scope
|
- Create an API token with `Build Artifacts` scope.
|
||||||
|
|
||||||
|
|
||||||
## Save secrets on the VM
|
## Save secrets on the VM
|
||||||
|
|
||||||
|
3
aio/content/cli-src/.gitignore
vendored
Normal file
3
aio/content/cli-src/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/node_modules
|
||||||
|
package.json
|
||||||
|
yarn.lock
|
100
aio/content/cli/index.md
Normal file
100
aio/content/cli/index.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<h1 class="no-toc">CLI Command Reference</h1>
|
||||||
|
|
||||||
|
The Angular CLI is a command-line interface tool that you use to initialize, develop, scaffold, and maintain Angular applications. You can use the tool directly in a command shell, or indirectly through an interactive UI such as [Angular Console](https://angularconsole.com).
|
||||||
|
|
||||||
|
## Installing Angular CLI
|
||||||
|
|
||||||
|
Major versions of Angular CLI follow the supported major version of Angular, but minor versions can be released separately.
|
||||||
|
|
||||||
|
Install the CLI using the `npm` package manager:
|
||||||
|
<code-example format="." language="bash">
|
||||||
|
npm install -g @angular/cli
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
For details about changes between versions, and information about updating from previous releases,
|
||||||
|
see the Releases tab on GitHub: https://github.com/angular/angular-cli/releases
|
||||||
|
|
||||||
|
## Basic workflow
|
||||||
|
|
||||||
|
Invoke the tool on the command line through the `ng` executable.
|
||||||
|
Online help is available on the command line.
|
||||||
|
Enter the following to list commands or options for a given command (such as [generate](cli/generate)) with a short description.
|
||||||
|
|
||||||
|
<code-example format="." language="bash">
|
||||||
|
ng help
|
||||||
|
ng generate --help
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
To create, build, and serve a new, basic Angular project on a development server, go to the parent directory of your new workspace use the following commands:
|
||||||
|
|
||||||
|
<code-example format="." language="bash">
|
||||||
|
ng new my-first-project
|
||||||
|
cd my-first-project
|
||||||
|
ng serve
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
In your browser, open http://localhost:4200/ to see the new app run.
|
||||||
|
When you use the [ng serve](cli/serve) command to build an app and serve it locally, the server automatically rebuilds the app and reloads the page when you change any of the source files.
|
||||||
|
|
||||||
|
## Workspaces and project files
|
||||||
|
|
||||||
|
The [ng new](cli/new) command creates an *Angular workspace* folder and generates a new app skeleton.
|
||||||
|
A workspace can contain multiple apps and libraries.
|
||||||
|
The initial app created by the [ng new](cli/new) command is at the top level of the workspace.
|
||||||
|
When you generate an additional app or library in a workspace, it goes into a `projects/` subfolder.
|
||||||
|
|
||||||
|
A newly generated app contains the source files for a root module, with a root component and template.
|
||||||
|
Each app has a `src` folder that contains the logic, data, and assets.
|
||||||
|
|
||||||
|
You can edit the generated files directly, or add to and modify them using CLI commands.
|
||||||
|
Use the [ng generate](cli/generate) command to add new files for additional components and services, and code for new pipes, directives, and so on.
|
||||||
|
Commands such as [add](cli/add) and [generate](cli/generate), which create or operate on apps and libraries, must be executed from within a workspace or project folder.
|
||||||
|
|
||||||
|
* See more about the [Workspace file structure](guide/file-structure).
|
||||||
|
|
||||||
|
### Workspace and project configuration
|
||||||
|
|
||||||
|
A single workspace configuration file, `angular.json`, is created at the top level of the workspace.
|
||||||
|
This is where you can set per-project defaults for CLI command options, and specify configurations to use when the CLI builds a project for different targets.
|
||||||
|
|
||||||
|
The [ng config](cli/config) command lets you set and retrieve configuration values from the command line, or you can edit the `angular.json` file directly.
|
||||||
|
Note that option names in the configuration file must use [camelCase](guide/glossary#case-types), while option names supplied to commands can use either camelCase or dash-case.
|
||||||
|
|
||||||
|
* See more about [Workspace Configuration](guide/workspace-config).
|
||||||
|
* See the [complete schema](https://github.com/angular/angular-cli/wiki/angular-workspace) for `angular.json`.
|
||||||
|
|
||||||
|
## CLI command-language syntax
|
||||||
|
|
||||||
|
Command syntax is shown as follows:
|
||||||
|
|
||||||
|
`ng` *commandNameOrAlias* *requiredArg* [*optionalArg*] `[options]`
|
||||||
|
|
||||||
|
* Most commands, and some options, have aliases. Aliases are shown in the syntax statement for each command.
|
||||||
|
|
||||||
|
* Option names are prefixed with a double dash (--).
|
||||||
|
Option aliases are prefixed with a single dash (-).
|
||||||
|
Arguments are not prefixed.
|
||||||
|
For example: `ng build my-app -c production`
|
||||||
|
|
||||||
|
* Typically, the name of a generated artifact can be given as an argument to the command or specified with the --name option.
|
||||||
|
|
||||||
|
* Argument and option names can be given in either
|
||||||
|
[camelCase or dash-case](guide/glossary#case-types).
|
||||||
|
`--myOptionName` is equivalent to `--my-option-name`.
|
||||||
|
|
||||||
|
### Boolean and enumerated options
|
||||||
|
|
||||||
|
Boolean options have two forms: `--thisOption` sets the flag, `--noThisOption` clears it.
|
||||||
|
If neither option is supplied, the flag remains in its default state, as listed in the reference documentation.
|
||||||
|
|
||||||
|
Allowed values are given with each enumerated option description, with the default value in **bold**.
|
||||||
|
|
||||||
|
### Relative paths
|
||||||
|
|
||||||
|
Options that specify files can be given as absolute paths, or as paths relative to the current working directory, which is generally either the workspace or project root.
|
||||||
|
|
||||||
|
### Schematics
|
||||||
|
|
||||||
|
The [ng generate](cli/generate) and [ng add](cli/add) commands take as an argument the artifact or library to be generated or added to the current project.
|
||||||
|
In addition to any general options, each artifact or library defines its own options in a *schematic*.
|
||||||
|
Schematic options are supplied to the command in the same format as immediate command options.
|
@ -1,351 +1,251 @@
|
|||||||
'use strict'; // necessary for es6 output in node
|
'use strict'; // necessary for es6 output in node
|
||||||
|
|
||||||
import { browser, element, by, ElementFinder } from 'protractor';
|
import { browser, ExpectedConditions as EC } from 'protractor';
|
||||||
import { logging, promise } from 'selenium-webdriver';
|
import { logging } from 'selenium-webdriver';
|
||||||
|
import * as openClose from './open-close.po';
|
||||||
|
import * as statusSlider from './status-slider.po';
|
||||||
|
import * as toggle from './toggle.po';
|
||||||
|
import * as enterLeave from './enter-leave.po';
|
||||||
|
import * as auto from './auto.po';
|
||||||
|
import * as filterStagger from './filter-stagger.po';
|
||||||
|
import * as heroGroups from './hero-groups';
|
||||||
|
import { getLinkById, sleepFor } from './util';
|
||||||
|
|
||||||
/**
|
|
||||||
* The tests here basically just checking that the end styles
|
|
||||||
* of each animation are in effect.
|
|
||||||
*
|
|
||||||
* Relies on the Angular testability only becoming stable once
|
|
||||||
* animation(s) have finished.
|
|
||||||
*
|
|
||||||
* Ideally we'd use https://developer.mozilla.org/en-US/docs/Web/API/Document/getAnimations
|
|
||||||
* but they're not supported in Chrome at the moment. The upcoming nganimate polyfill
|
|
||||||
* may also add some introspection support.
|
|
||||||
*/
|
|
||||||
describe('Animation Tests', () => {
|
describe('Animation Tests', () => {
|
||||||
|
const openCloseHref = getLinkById('open-close');
|
||||||
|
const statusSliderHref = getLinkById('status');
|
||||||
|
const toggleHref = getLinkById('toggle');
|
||||||
|
const enterLeaveHref = getLinkById('enter-leave');
|
||||||
|
const autoHref = getLinkById('auto');
|
||||||
|
const filterHref = getLinkById('heroes');
|
||||||
|
const heroGroupsHref = getLinkById('hero-groups');
|
||||||
|
|
||||||
const INACTIVE_COLOR = 'rgba(238, 238, 238, 1)';
|
beforeAll(() => {
|
||||||
const ACTIVE_COLOR = 'rgba(207, 216, 220, 1)';
|
|
||||||
const NO_TRANSFORM_MATRIX_REGEX = /matrix\(1,\s*0,\s*0,\s*1,\s*0,\s*0\)/;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
browser.get('');
|
browser.get('');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('basic states', () => {
|
describe('Open/Close Component', () => {
|
||||||
|
const closedHeight = '100px';
|
||||||
|
const openHeight = '200px';
|
||||||
|
|
||||||
let host: ElementFinder;
|
beforeAll(async () => {
|
||||||
|
await openCloseHref.click();
|
||||||
beforeEach(() => {
|
sleepFor();
|
||||||
host = element(by.css('app-hero-list-basic'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('animates between active and inactive', () => {
|
it('should be open', async () => {
|
||||||
addInactiveHero();
|
const toggleButton = openClose.getToggleButton();
|
||||||
|
const container = openClose.getComponentContainer();
|
||||||
|
let text = await container.getText();
|
||||||
|
|
||||||
let li = host.element(by.css('li'));
|
if (text.includes('Closed')) {
|
||||||
|
await toggleButton.click();
|
||||||
|
await browser.wait(async () => await container.getCssValue('height') === openHeight, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
expect(getScaleX(li)).toBe(1.0);
|
text = await container.getText();
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
const containerHeight = await container.getCssValue('height');
|
||||||
|
|
||||||
li.click();
|
expect(text).toContain('The box is now Open!');
|
||||||
browser.driver.sleep(300);
|
expect(containerHeight).toBe(openHeight);
|
||||||
expect(getScaleX(li)).toBe(1.1);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
|
|
||||||
|
|
||||||
li.click();
|
|
||||||
browser.driver.sleep(300);
|
|
||||||
expect(getScaleX(li)).toBe(1.0);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
it('should be closed', async () => {
|
||||||
|
const toggleButton = openClose.getToggleButton();
|
||||||
|
const container = openClose.getComponentContainer();
|
||||||
|
let text = await container.getText();
|
||||||
|
|
||||||
describe('styles inline in transitions', () => {
|
if (text.includes('Open')) {
|
||||||
|
await toggleButton.click();
|
||||||
|
await browser.wait(async () => await container.getCssValue('height') === closedHeight, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
let host: ElementFinder;
|
text = await container.getText();
|
||||||
|
const containerHeight = await container.getCssValue('height');
|
||||||
|
|
||||||
beforeEach(function() {
|
expect(text).toContain('The box is now Closed!');
|
||||||
host = element(by.css('app-hero-list-inline-styles'));
|
expect(containerHeight).toBe(closedHeight);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('are not kept after animation', () => {
|
it('should log animation events', async () => {
|
||||||
addInactiveHero();
|
const toggleButton = openClose.getToggleButton();
|
||||||
|
const loggingCheckbox = openClose.getLoggingCheckbox();
|
||||||
|
await loggingCheckbox.click();
|
||||||
|
await toggleButton.click();
|
||||||
|
|
||||||
let li = host.element(by.css('li'));
|
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||||
|
const animationMessages = logs.filter(({ message }) => message.includes('Animation'));
|
||||||
|
|
||||||
li.click();
|
expect(animationMessages.length).toBeGreaterThan(0);
|
||||||
browser.driver.sleep(300);
|
|
||||||
expect(getScaleX(li)).toBe(1.0);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('combined transition syntax', () => {
|
|
||||||
|
|
||||||
let host: ElementFinder;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
host = element(by.css('app-hero-list-combined-transitions'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('animates between active and inactive', () => {
|
|
||||||
addInactiveHero();
|
|
||||||
|
|
||||||
let li = host.element(by.css('li'));
|
|
||||||
|
|
||||||
expect(getScaleX(li)).toBe(1.0);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
|
||||||
|
|
||||||
li.click();
|
|
||||||
browser.driver.sleep(300);
|
|
||||||
expect(getScaleX(li)).toBe(1.1);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
|
|
||||||
|
|
||||||
li.click();
|
|
||||||
browser.driver.sleep(300);
|
|
||||||
expect(getScaleX(li)).toBe(1.0);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('two-way transition syntax', () => {
|
|
||||||
|
|
||||||
let host: ElementFinder;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
host = element(by.css('app-hero-list-twoway'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('animates between active and inactive', () => {
|
|
||||||
addInactiveHero();
|
|
||||||
|
|
||||||
let li = host.element(by.css('li'));
|
|
||||||
|
|
||||||
expect(getScaleX(li)).toBe(1.0);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
|
||||||
|
|
||||||
li.click();
|
|
||||||
browser.driver.sleep(300);
|
|
||||||
expect(getScaleX(li)).toBe(1.1);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
|
|
||||||
|
|
||||||
li.click();
|
|
||||||
browser.driver.sleep(300);
|
|
||||||
expect(getScaleX(li)).toBe(1.0);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('enter & leave', () => {
|
|
||||||
|
|
||||||
let host: ElementFinder;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
host = element(by.css('app-hero-list-enter-leave'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds and removes element', () => {
|
|
||||||
addInactiveHero();
|
|
||||||
|
|
||||||
let li = host.element(by.css('li'));
|
|
||||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
|
||||||
|
|
||||||
removeHero();
|
|
||||||
expect(li.isPresent()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('enter & leave & states', () => {
|
|
||||||
|
|
||||||
let host: ElementFinder;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
host = element(by.css('app-hero-list-enter-leave-states'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds and removes and animates between active and inactive', () => {
|
|
||||||
addInactiveHero();
|
|
||||||
|
|
||||||
let li = host.element(by.css('li'));
|
|
||||||
|
|
||||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
|
||||||
|
|
||||||
li.click();
|
|
||||||
browser.driver.sleep(300);
|
|
||||||
expect(getScaleX(li)).toBe(1.1);
|
|
||||||
|
|
||||||
li.click();
|
|
||||||
browser.driver.sleep(300);
|
|
||||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
|
||||||
|
|
||||||
removeHero();
|
|
||||||
expect(li.isPresent()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('auto style calc', () => {
|
|
||||||
|
|
||||||
let host: ElementFinder;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
host = element(by.css('app-hero-list-auto'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds and removes element', () => {
|
|
||||||
addInactiveHero();
|
|
||||||
|
|
||||||
let li = host.element(by.css('li'));
|
|
||||||
expect(li.getCssValue('height')).toBe('50px');
|
|
||||||
|
|
||||||
removeHero();
|
|
||||||
expect(li.isPresent()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('different timings', () => {
|
|
||||||
|
|
||||||
let host: ElementFinder;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
host = element(by.css('app-hero-list-timings'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds and removes element', () => {
|
|
||||||
addInactiveHero();
|
|
||||||
|
|
||||||
let li = host.element(by.css('li'));
|
|
||||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
|
||||||
expect(li.getCssValue('opacity')).toMatch('1');
|
|
||||||
|
|
||||||
removeHero();
|
|
||||||
expect(li.isPresent()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('multiple keyframes', () => {
|
|
||||||
|
|
||||||
let host: ElementFinder;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
host = element(by.css('app-hero-list-multistep'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds and removes element', () => {
|
|
||||||
addInactiveHero();
|
|
||||||
|
|
||||||
let li = host.element(by.css('li'));
|
|
||||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
|
||||||
expect(li.getCssValue('opacity')).toMatch('1');
|
|
||||||
|
|
||||||
removeHero();
|
|
||||||
expect(li.isPresent()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('parallel groups', () => {
|
|
||||||
|
|
||||||
let host: ElementFinder;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
host = element(by.css('app-hero-list-groups'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds and removes element', () => {
|
|
||||||
addInactiveHero();
|
|
||||||
|
|
||||||
let li = host.element(by.css('li'));
|
|
||||||
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
|
|
||||||
expect(li.getCssValue('opacity')).toMatch('1');
|
|
||||||
|
|
||||||
removeHero(700);
|
|
||||||
expect(li.isPresent()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('adding active heroes', () => {
|
|
||||||
|
|
||||||
let host: ElementFinder;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
host = element(by.css('app-hero-list-basic'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('animates between active and inactive', () => {
|
|
||||||
addActiveHero();
|
|
||||||
|
|
||||||
let li = host.element(by.css('li'));
|
|
||||||
|
|
||||||
expect(getScaleX(li)).toBe(1.1);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
|
|
||||||
|
|
||||||
li.click();
|
|
||||||
browser.driver.sleep(300);
|
|
||||||
expect(getScaleX(li)).toBe(1.0);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
|
|
||||||
|
|
||||||
li.click();
|
|
||||||
browser.driver.sleep(300);
|
|
||||||
expect(getScaleX(li)).toBe(1.1);
|
|
||||||
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('callbacks', () => {
|
describe('Status Slider Component', () => {
|
||||||
it('fires a callback on start and done', () => {
|
const activeColor = 'rgba(255, 165, 0, 1)';
|
||||||
addActiveHero();
|
const inactiveColor = 'rgba(0, 0, 255, 1)';
|
||||||
browser.manage().logs().get(logging.Type.BROWSER)
|
|
||||||
.then((logs: logging.Entry[]) => {
|
|
||||||
const animationMessages = logs.filter((log) => {
|
|
||||||
return log.message.indexOf('Animation') !== -1 ? true : false;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(animationMessages.length).toBeGreaterThan(0);
|
beforeAll(async () => {
|
||||||
});
|
await statusSliderHref.click();
|
||||||
|
sleepFor(2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be inactive with an orange background', async () => {
|
||||||
|
const toggleButton = statusSlider.getToggleButton();
|
||||||
|
const container = statusSlider.getComponentContainer();
|
||||||
|
let text = await container.getText();
|
||||||
|
|
||||||
|
if (text === 'Active') {
|
||||||
|
await toggleButton.click();
|
||||||
|
await browser.wait(async () => await container.getCssValue('backgroundColor') === inactiveColor, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
text = await container.getText();
|
||||||
|
const bgColor = await container.getCssValue('backgroundColor');
|
||||||
|
|
||||||
|
expect(text).toBe('Inactive');
|
||||||
|
expect(bgColor).toBe(inactiveColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be active with a blue background', async () => {
|
||||||
|
const toggleButton = statusSlider.getToggleButton();
|
||||||
|
const container = statusSlider.getComponentContainer();
|
||||||
|
let text = await container.getText();
|
||||||
|
|
||||||
|
if (text === 'Inactive') {
|
||||||
|
await toggleButton.click();
|
||||||
|
await browser.wait(async () => await container.getCssValue('backgroundColor') === activeColor, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
text = await container.getText();
|
||||||
|
const bgColor = await container.getCssValue('backgroundColor');
|
||||||
|
|
||||||
|
expect(text).toBe('Active');
|
||||||
|
expect(bgColor).toBe(activeColor);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function addActiveHero(sleep?: number) {
|
describe('Toggle Animations Component', () => {
|
||||||
sleep = sleep || 500;
|
beforeAll(async () => {
|
||||||
element(by.buttonText('Add active hero')).click();
|
await toggleHref.click();
|
||||||
browser.driver.sleep(sleep);
|
sleepFor();
|
||||||
}
|
|
||||||
|
|
||||||
function addInactiveHero(sleep?: number) {
|
|
||||||
sleep = sleep || 500;
|
|
||||||
element(by.buttonText('Add inactive hero')).click();
|
|
||||||
browser.driver.sleep(sleep);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeHero(sleep?: number) {
|
|
||||||
sleep = sleep || 500;
|
|
||||||
element(by.buttonText('Remove hero')).click();
|
|
||||||
browser.driver.sleep(sleep);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScaleX(el: ElementFinder) {
|
|
||||||
return Promise.all([
|
|
||||||
getBoundingClientWidth(el),
|
|
||||||
getOffsetWidth(el)
|
|
||||||
]).then(function(promiseResolutions) {
|
|
||||||
let clientWidth = promiseResolutions[0];
|
|
||||||
let offsetWidth = promiseResolutions[1];
|
|
||||||
return clientWidth / offsetWidth;
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function getBoundingClientWidth(el: ElementFinder) {
|
it('should disabled animations on the child element', async () => {
|
||||||
return browser.executeScript(
|
const toggleButton = toggle.getToggleAnimationsButton();
|
||||||
'return arguments[0].getBoundingClientRect().width',
|
|
||||||
el.getWebElement()
|
|
||||||
) as PromiseLike<number>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOffsetWidth(el: ElementFinder) {
|
await toggleButton.click();
|
||||||
return browser.executeScript(
|
|
||||||
'return arguments[0].offsetWidth',
|
const container = toggle.getComponentContainer();
|
||||||
el.getWebElement()
|
const cssClasses = await container.getAttribute('class');
|
||||||
) as PromiseLike<number>;
|
|
||||||
}
|
expect(cssClasses).toContain('ng-animate-disabled');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Enter/Leave Component', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await enterLeaveHref.click();
|
||||||
|
sleepFor(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attach a flyInOut trigger to the list of items', async () => {
|
||||||
|
const heroesList = enterLeave.getHeroesList();
|
||||||
|
const hero = heroesList.get(0);
|
||||||
|
const cssClasses = await hero.getAttribute('class');
|
||||||
|
const transform = await hero.getCssValue('transform');
|
||||||
|
|
||||||
|
expect(cssClasses).toContain('ng-trigger-flyInOut');
|
||||||
|
expect(transform).toBe('matrix(1, 0, 0, 1, 0, 0)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the hero from the list when clicked', async () => {
|
||||||
|
const heroesList = enterLeave.getHeroesList();
|
||||||
|
const total = await heroesList.count();
|
||||||
|
const hero = heroesList.get(0);
|
||||||
|
|
||||||
|
await hero.click();
|
||||||
|
await browser.wait(async () => await heroesList.count() < total, 2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Auto Calculation Component', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await autoHref.click();
|
||||||
|
sleepFor(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attach a shrinkOut trigger to the list of items', async () => {
|
||||||
|
const heroesList = auto.getHeroesList();
|
||||||
|
const hero = heroesList.get(0);
|
||||||
|
const cssClasses = await hero.getAttribute('class');
|
||||||
|
|
||||||
|
expect(cssClasses).toContain('ng-trigger-shrinkOut');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the hero from the list when clicked', async () => {
|
||||||
|
const heroesList = auto.getHeroesList();
|
||||||
|
const total = await heroesList.count();
|
||||||
|
const hero = heroesList.get(0);
|
||||||
|
|
||||||
|
await hero.click();
|
||||||
|
await browser.wait(async () => await heroesList.count() < total, 2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Filter/Stagger Component', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await filterHref.click();
|
||||||
|
sleepFor();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attach a filterAnimations trigger to the list container', async () => {
|
||||||
|
const heroesList = filterStagger.getComponentContainer();
|
||||||
|
const cssClasses = await heroesList.getAttribute('class');
|
||||||
|
|
||||||
|
expect(cssClasses).toContain('ng-trigger-filterAnimation');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter down the list when a search is performed', async () => {
|
||||||
|
const heroesList = filterStagger.getHeroesList();
|
||||||
|
const total = await heroesList.count();
|
||||||
|
|
||||||
|
const formInput = filterStagger.getFormInput();
|
||||||
|
await formInput.sendKeys('Mag');
|
||||||
|
|
||||||
|
await browser.wait(async () => await heroesList.count() === 2, 2000);
|
||||||
|
|
||||||
|
const newTotal = await heroesList.count();
|
||||||
|
expect(newTotal).toBeLessThan(total);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Hero Groups Component', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await heroGroupsHref.click();
|
||||||
|
sleepFor(300);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attach a flyInOut trigger to the list of items', async () => {
|
||||||
|
const heroesList = heroGroups.getHeroesList();
|
||||||
|
const hero = heroesList.get(0);
|
||||||
|
const cssClasses = await hero.getAttribute('class');
|
||||||
|
const transform = await hero.getCssValue('transform');
|
||||||
|
const opacity = await hero.getCssValue('opacity');
|
||||||
|
|
||||||
|
expect(cssClasses).toContain('ng-trigger-flyInOut');
|
||||||
|
expect(transform).toBe('matrix(1, 0, 0, 1, 0, 0)');
|
||||||
|
expect(opacity).toBe('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the hero from the list when clicked', async () => {
|
||||||
|
const heroesList = heroGroups.getHeroesList();
|
||||||
|
const total = await heroesList.count();
|
||||||
|
const hero = heroesList.get(0);
|
||||||
|
|
||||||
|
await hero.click();
|
||||||
|
await browser.wait(async () => await heroesList.count() < total, 2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
19
aio/content/examples/animations/e2e/src/auto.po.ts
Normal file
19
aio/content/examples/animations/e2e/src/auto.po.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { by } from 'protractor';
|
||||||
|
import { locate } from './util';
|
||||||
|
|
||||||
|
export function getPage() {
|
||||||
|
return by.css('app-hero-list-auto-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponent() {
|
||||||
|
return by.css('app-hero-list-auto');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponentContainer() {
|
||||||
|
const findContainer = () => by.css('ul');
|
||||||
|
return locate(getComponent(), findContainer());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHeroesList() {
|
||||||
|
return getComponentContainer().all(by.css('li'));
|
||||||
|
}
|
19
aio/content/examples/animations/e2e/src/enter-leave.po.ts
Normal file
19
aio/content/examples/animations/e2e/src/enter-leave.po.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { by } from 'protractor';
|
||||||
|
import { locate } from './util';
|
||||||
|
|
||||||
|
export function getPage() {
|
||||||
|
return by.css('app-hero-list-enter-leave-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponent() {
|
||||||
|
return by.css('app-hero-list-enter-leave');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponentContainer() {
|
||||||
|
const findContainer = () => by.css('ul');
|
||||||
|
return locate(getComponent(), findContainer());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHeroesList() {
|
||||||
|
return getComponentContainer().all(by.css('li'));
|
||||||
|
}
|
20
aio/content/examples/animations/e2e/src/filter-stagger.po.ts
Normal file
20
aio/content/examples/animations/e2e/src/filter-stagger.po.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { by } from 'protractor';
|
||||||
|
import { locate } from './util';
|
||||||
|
|
||||||
|
export function getPage() {
|
||||||
|
return by.css('app-hero-list-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponentContainer() {
|
||||||
|
const findContainer = () => by.css('ul');
|
||||||
|
return locate(getPage(), findContainer());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHeroesList() {
|
||||||
|
return getComponentContainer().all(by.css('li'));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFormInput() {
|
||||||
|
const formInput = () => by.css('form > input');
|
||||||
|
return locate(getPage(), formInput());
|
||||||
|
}
|
19
aio/content/examples/animations/e2e/src/hero-groups.ts
Normal file
19
aio/content/examples/animations/e2e/src/hero-groups.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { by } from 'protractor';
|
||||||
|
import { locate } from './util';
|
||||||
|
|
||||||
|
export function getPage() {
|
||||||
|
return by.css('app-hero-list-groups-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponent() {
|
||||||
|
return by.css('app-hero-list-groups');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponentContainer() {
|
||||||
|
const findContainer = () => by.css('ul');
|
||||||
|
return locate(getComponent(), findContainer());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHeroesList() {
|
||||||
|
return getComponentContainer().all(by.css('li'));
|
||||||
|
}
|
25
aio/content/examples/animations/e2e/src/open-close.po.ts
Normal file
25
aio/content/examples/animations/e2e/src/open-close.po.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { by } from 'protractor';
|
||||||
|
import { locate } from './util';
|
||||||
|
|
||||||
|
export function getPage() {
|
||||||
|
return by.css('app-open-close-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponent() {
|
||||||
|
return by.css('app-open-close');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToggleButton() {
|
||||||
|
const toggleButton = () => by.buttonText('Toggle Open/Close');
|
||||||
|
return locate(getComponent(), toggleButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLoggingCheckbox() {
|
||||||
|
const loggingCheckbox = () => by.css('section > input[type="checkbox"]');
|
||||||
|
return locate(getPage(), loggingCheckbox());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponentContainer() {
|
||||||
|
const findContainer = () => by.css('div');
|
||||||
|
return locate(getComponent(), findContainer());
|
||||||
|
}
|
20
aio/content/examples/animations/e2e/src/status-slider.po.ts
Normal file
20
aio/content/examples/animations/e2e/src/status-slider.po.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { by } from 'protractor';
|
||||||
|
import { locate } from './util';
|
||||||
|
|
||||||
|
export function getPage() {
|
||||||
|
return by.css('app-status-slider-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponent() {
|
||||||
|
return by.css('app-status-slider');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToggleButton() {
|
||||||
|
const toggleButton = () => by.buttonText('Toggle Status');
|
||||||
|
return locate(getComponent(), toggleButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponentContainer() {
|
||||||
|
const findContainer = () => by.css('div');
|
||||||
|
return locate(getComponent(), findContainer());
|
||||||
|
}
|
25
aio/content/examples/animations/e2e/src/toggle.po.ts
Normal file
25
aio/content/examples/animations/e2e/src/toggle.po.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { by } from 'protractor';
|
||||||
|
import { locate } from './util';
|
||||||
|
|
||||||
|
export function getPage() {
|
||||||
|
return by.css('app-toggle-animations-child-page');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponent() {
|
||||||
|
return by.css('app-open-close-toggle');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToggleButton() {
|
||||||
|
const toggleButton = () => by.buttonText('Toggle Open/Closed');
|
||||||
|
return locate(getComponent(), toggleButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToggleAnimationsButton() {
|
||||||
|
const toggleAnimationsButton = () => by.buttonText('Toggle Animations');
|
||||||
|
return locate(getComponent(), toggleAnimationsButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponentContainer() {
|
||||||
|
const findContainer = () => by.css('div');
|
||||||
|
return locate(getComponent()).all(findContainer()).get(0);
|
||||||
|
}
|
19
aio/content/examples/animations/e2e/src/util.ts
Normal file
19
aio/content/examples/animations/e2e/src/util.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Locator, ElementFinder, browser, by, element } from 'protractor';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* locate(finder1, finder2) => element(finder1).element(finder2).element(finderN);
|
||||||
|
*/
|
||||||
|
export function locate(locator: Locator, ...locators: Locator[]) {
|
||||||
|
return locators.reduce((current: ElementFinder, next: Locator) => {
|
||||||
|
return current.element(next);
|
||||||
|
}, element(locator)) as ElementFinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sleepFor(time = 1000) {
|
||||||
|
return await browser.sleep(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLinkById(id: string) {
|
||||||
|
return element(by.css(`a[id=${id}]`));
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
<p>
|
||||||
|
Angular's animations library makes it easy to define and apply animation effects such as page and list transitions.
|
||||||
|
</p>
|
15
aio/content/examples/animations/src/app/about.component.ts
Normal file
15
aio/content/examples/animations/src/app/about.component.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-about',
|
||||||
|
templateUrl: './about.component.html',
|
||||||
|
styleUrls: ['./about.component.css']
|
||||||
|
})
|
||||||
|
export class AboutComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
aio/content/examples/animations/src/app/animations.1.ts
Normal file
11
aio/content/examples/animations/src/app/animations.1.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// #docregion
|
||||||
|
import { animation, style, animate } from '@angular/animations';
|
||||||
|
|
||||||
|
export const transAnimation = animation([
|
||||||
|
style({
|
||||||
|
height: '{{ height }}',
|
||||||
|
opacity: '{{ opacity }}',
|
||||||
|
backgroundColor: '{{ backgroundColor }}'
|
||||||
|
}),
|
||||||
|
animate('{{ time }}')
|
||||||
|
]);
|
74
aio/content/examples/animations/src/app/animations.ts
Normal file
74
aio/content/examples/animations/src/app/animations.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// #docregion reusable
|
||||||
|
import {
|
||||||
|
animation, trigger, animateChild, group,
|
||||||
|
transition, animate, style, query
|
||||||
|
} from '@angular/animations';
|
||||||
|
|
||||||
|
export const transAnimation = animation([
|
||||||
|
style({
|
||||||
|
height: '{{ height }}',
|
||||||
|
opacity: '{{ opacity }}',
|
||||||
|
backgroundColor: '{{ backgroundColor }}'
|
||||||
|
}),
|
||||||
|
animate('{{ time }}')
|
||||||
|
]);
|
||||||
|
// #enddocregion reusable
|
||||||
|
|
||||||
|
// Routable animations
|
||||||
|
// #docregion route-animations
|
||||||
|
export const slideInAnimation =
|
||||||
|
// #docregion style-view
|
||||||
|
trigger('routeAnimations', [
|
||||||
|
transition('HomePage <=> AboutPage', [
|
||||||
|
style({ position: 'relative' }),
|
||||||
|
query(':enter, :leave', [
|
||||||
|
style({
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
// #enddocregion style-view
|
||||||
|
// #docregion query
|
||||||
|
query(':enter', [
|
||||||
|
style({ left: '-100%'})
|
||||||
|
]),
|
||||||
|
query(':leave', animateChild()),
|
||||||
|
group([
|
||||||
|
query(':leave', [
|
||||||
|
animate('300ms ease-out', style({ left: '100%'}))
|
||||||
|
]),
|
||||||
|
query(':enter', [
|
||||||
|
animate('300ms ease-out', style({ left: '0%'}))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
query(':enter', animateChild()),
|
||||||
|
]),
|
||||||
|
transition('* <=> FilterPage', [
|
||||||
|
style({ position: 'relative' }),
|
||||||
|
query(':enter, :leave', [
|
||||||
|
style({
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
query(':enter', [
|
||||||
|
style({ left: '-100%'})
|
||||||
|
]),
|
||||||
|
query(':leave', animateChild()),
|
||||||
|
group([
|
||||||
|
query(':leave', [
|
||||||
|
animate('200ms ease-out', style({ left: '100%'}))
|
||||||
|
]),
|
||||||
|
query(':enter', [
|
||||||
|
animate('300ms ease-out', style({ left: '0%'}))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
query(':enter', animateChild()),
|
||||||
|
])
|
||||||
|
// #enddocregion query
|
||||||
|
]);
|
||||||
|
// #enddocregion route-animations
|
35
aio/content/examples/animations/src/app/app.component.1.ts
Normal file
35
aio/content/examples/animations/src/app/app.component.1.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// #docplaster
|
||||||
|
// #docregion imports
|
||||||
|
import { Component, HostBinding } from '@angular/core';
|
||||||
|
import {
|
||||||
|
trigger,
|
||||||
|
state,
|
||||||
|
style,
|
||||||
|
animate,
|
||||||
|
transition,
|
||||||
|
// ...
|
||||||
|
} from '@angular/animations';
|
||||||
|
|
||||||
|
// #enddocregion imports
|
||||||
|
|
||||||
|
// #docregion decorator, toggle-app-animations
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: 'app.component.html',
|
||||||
|
styleUrls: ['app.component.css'],
|
||||||
|
animations: [
|
||||||
|
// animation triggers go here
|
||||||
|
]
|
||||||
|
})
|
||||||
|
// #enddocregion decorator
|
||||||
|
export class AppComponent {
|
||||||
|
@HostBinding('@.disabled')
|
||||||
|
public animationsDisabled = false;
|
||||||
|
// #enddocregion toggle-app-animations
|
||||||
|
|
||||||
|
toggleAnimations() {
|
||||||
|
this.animationsDisabled = !this.animationsDisabled;
|
||||||
|
}
|
||||||
|
// #docregion toggle-app-animations
|
||||||
|
}
|
||||||
|
// #enddocregion toggle-app-animations
|
@ -0,0 +1,7 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-top: 100px;
|
||||||
|
}
|
21
aio/content/examples/animations/src/app/app.component.html
Normal file
21
aio/content/examples/animations/src/app/app.component.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<h1>Animations</h1>
|
||||||
|
|
||||||
|
Toggle All Animations <input type="checkbox" [checked]="!animationsDisabled" (click)="toggleAnimations()"/>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<a id="home" routerLink="/home" routerLinkActive="active">Home</a>
|
||||||
|
<a id="about" routerLink="/about" routerLinkActive="active">About</a>
|
||||||
|
<a id="open-close" routerLink="/open-close" routerLinkActive="active">Open/Close</a>
|
||||||
|
<a id="status" routerLink="/status" routerLinkActive="active">Status Slider</a>
|
||||||
|
<a id="toggle" routerLink="/toggle" routerLinkActive="active">Toggle Animations</a>
|
||||||
|
<a id="enter-leave" routerLink="/enter-leave" routerLinkActive="active">Enter/Leave</a>
|
||||||
|
<a id="auto" routerLink="/auto" routerLinkActive="active">Auto Calculation</a>
|
||||||
|
<a id="heroes" routerLink="/heroes" routerLinkActive="active">Filter/Stagger</a>
|
||||||
|
<a id="hero-groups" routerLink="/hero-groups" routerLinkActive="active">Hero Groups</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- #docregion route-animations-outlet -->
|
||||||
|
<div [@routeAnimations]="prepareRoute(outlet)" >
|
||||||
|
<router-outlet #outlet="outlet"></router-outlet>
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion route-animations-outlet -->
|
47
aio/content/examples/animations/src/app/app.component.ts
Normal file
47
aio/content/examples/animations/src/app/app.component.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// #docplaster
|
||||||
|
// #docregion imports
|
||||||
|
import { Component, HostBinding } from '@angular/core';
|
||||||
|
import {
|
||||||
|
trigger,
|
||||||
|
state,
|
||||||
|
style,
|
||||||
|
animate,
|
||||||
|
transition,
|
||||||
|
// ...
|
||||||
|
} from '@angular/animations';
|
||||||
|
|
||||||
|
// #enddocregion imports
|
||||||
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
import { slideInAnimation } from './animations';
|
||||||
|
|
||||||
|
// #docregion decorator, toggle-app-animations, define
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: 'app.component.html',
|
||||||
|
styleUrls: ['app.component.css'],
|
||||||
|
animations: [
|
||||||
|
// #enddocregion decorator
|
||||||
|
slideInAnimation
|
||||||
|
// #docregion decorator
|
||||||
|
// animation triggers go here
|
||||||
|
]
|
||||||
|
})
|
||||||
|
// #enddocregion decorator, define
|
||||||
|
export class AppComponent {
|
||||||
|
@HostBinding('@.disabled')
|
||||||
|
public animationsDisabled = false;
|
||||||
|
// #enddocregion toggle-app-animations
|
||||||
|
|
||||||
|
// #docregion prepare-router-outlet
|
||||||
|
prepareRoute(outlet: RouterOutlet) {
|
||||||
|
return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// #enddocregion prepare-router-outlet
|
||||||
|
|
||||||
|
toggleAnimations() {
|
||||||
|
this.animationsDisabled = !this.animationsDisabled;
|
||||||
|
}
|
||||||
|
// #docregion toggle-app-animations
|
||||||
|
}
|
||||||
|
// #enddocregion toggle-app-animations
|
13
aio/content/examples/animations/src/app/app.module.1.ts
Normal file
13
aio/content/examples/animations/src/app/app.module.1.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
BrowserAnimationsModule
|
||||||
|
],
|
||||||
|
declarations: [ ],
|
||||||
|
bootstrap: [ ]
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
@ -1,43 +1,63 @@
|
|||||||
// #docplaster
|
// #docregion route-animation-data
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
// #docregion animations-module
|
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
// #enddocregion animations-module
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
import { HeroTeamBuilderComponent } from './hero-team-builder.component';
|
import { OpenCloseComponent } from './open-close.component';
|
||||||
import { HeroListBasicComponent } from './hero-list-basic.component';
|
import { OpenClosePageComponent } from './open-close-page.component';
|
||||||
import { HeroListInlineStylesComponent } from './hero-list-inline-styles.component';
|
import { OpenCloseChildComponent } from './open-close.component.4';
|
||||||
import { HeroListEnterLeaveComponent } from './hero-list-enter-leave.component';
|
import { ToggleAnimationsPageComponent } from './toggle-animations-page.component';
|
||||||
import { HeroListEnterLeaveStatesComponent } from './hero-list-enter-leave-states.component';
|
import { StatusSliderComponent } from './status-slider.component';
|
||||||
import { HeroListCombinedTransitionsComponent } from './hero-list-combined-transitions.component';
|
import { StatusSliderPageComponent } from './status-slider-page.component';
|
||||||
import { HeroListTwowayComponent } from './hero-list-twoway.component';
|
import { HeroListPageComponent } from './hero-list-page.component';
|
||||||
import { HeroListAutoComponent } from './hero-list-auto.component';
|
import { HeroListGroupPageComponent } from './hero-list-group-page.component';
|
||||||
import { HeroListGroupsComponent } from './hero-list-groups.component';
|
import { HeroListGroupsComponent } from './hero-list-groups.component';
|
||||||
import { HeroListMultistepComponent } from './hero-list-multistep.component';
|
import { HeroListEnterLeavePageComponent } from './hero-list-enter-leave-page.component';
|
||||||
import { HeroListTimingsComponent } from './hero-list-timings.component';
|
import { HeroListEnterLeaveComponent } from './hero-list-enter-leave.component';
|
||||||
// #docregion animations-module
|
import { HeroListAutoCalcPageComponent } from './hero-list-auto-page.component';
|
||||||
|
import { HeroListAutoComponent } from './hero-list-auto.component';
|
||||||
|
import { HomeComponent } from './home.component';
|
||||||
|
import { AboutComponent } from './about.component';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ BrowserModule, BrowserAnimationsModule ],
|
imports: [
|
||||||
// ... more stuff ...
|
BrowserModule,
|
||||||
// #enddocregion animations-module
|
BrowserAnimationsModule,
|
||||||
declarations: [
|
RouterModule.forRoot([
|
||||||
HeroTeamBuilderComponent,
|
{ path: '', pathMatch: 'full', redirectTo: '/enter-leave' },
|
||||||
HeroListBasicComponent,
|
{ path: 'open-close', component: OpenClosePageComponent },
|
||||||
HeroListInlineStylesComponent,
|
{ path: 'status', component: StatusSliderPageComponent },
|
||||||
HeroListCombinedTransitionsComponent,
|
{ path: 'toggle', component: ToggleAnimationsPageComponent },
|
||||||
HeroListTwowayComponent,
|
{ path: 'heroes', component: HeroListPageComponent, data: {animation: 'FilterPage'} },
|
||||||
HeroListEnterLeaveComponent,
|
{ path: 'hero-groups', component: HeroListGroupPageComponent },
|
||||||
HeroListEnterLeaveStatesComponent,
|
{ path: 'enter-leave', component: HeroListEnterLeavePageComponent },
|
||||||
HeroListAutoComponent,
|
{ path: 'auto', component: HeroListAutoCalcPageComponent },
|
||||||
HeroListTimingsComponent,
|
{ path: 'home', component: HomeComponent, data: {animation: 'HomePage'} },
|
||||||
HeroListMultistepComponent,
|
{ path: 'about', component: AboutComponent, data: {animation: 'AboutPage'} },
|
||||||
HeroListGroupsComponent
|
|
||||||
|
])
|
||||||
],
|
],
|
||||||
bootstrap: [ HeroTeamBuilderComponent ]
|
// #enddocregion route-animation-data
|
||||||
// #docregion animations-module
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
StatusSliderComponent,
|
||||||
|
OpenCloseComponent,
|
||||||
|
OpenCloseChildComponent,
|
||||||
|
OpenClosePageComponent,
|
||||||
|
StatusSliderPageComponent,
|
||||||
|
ToggleAnimationsPageComponent,
|
||||||
|
HeroListPageComponent,
|
||||||
|
HeroListGroupsComponent,
|
||||||
|
HeroListGroupPageComponent,
|
||||||
|
HeroListEnterLeavePageComponent,
|
||||||
|
HeroListEnterLeaveComponent,
|
||||||
|
HeroListAutoCalcPageComponent,
|
||||||
|
HeroListAutoComponent,
|
||||||
|
HomeComponent,
|
||||||
|
AboutComponent
|
||||||
|
],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
// #enddocregion animations-module
|
|
||||||
|
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { HEROES } from './mock-heroes';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-list-auto-page',
|
||||||
|
template: `
|
||||||
|
<section>
|
||||||
|
<h2>Automatic Calculation</h2>
|
||||||
|
|
||||||
|
<app-hero-list-auto [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-auto>
|
||||||
|
</section>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class HeroListAutoCalcPageComponent {
|
||||||
|
heroes = HEROES.slice();
|
||||||
|
|
||||||
|
onRemove(id: number) {
|
||||||
|
this.heroes = this.heroes.filter(hero => hero.id !== id);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
<ul class="heroes">
|
||||||
|
<li *ngFor="let hero of heroes"
|
||||||
|
[@shrinkOut]="'in'" (click)="removeHero(hero.id)">
|
||||||
|
<div class="inner">
|
||||||
|
<span class="badge">{{ hero.id }}</span>
|
||||||
|
<span>{{ hero.name }}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
Input
|
Input,
|
||||||
|
Output,
|
||||||
|
EventEmitter
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
trigger,
|
trigger,
|
||||||
@ -10,38 +12,30 @@ import {
|
|||||||
transition
|
transition
|
||||||
} from '@angular/animations';
|
} from '@angular/animations';
|
||||||
|
|
||||||
import { Hero } from './hero.service';
|
import { Hero } from './hero';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-hero-list-auto',
|
selector: 'app-hero-list-auto',
|
||||||
// #docregion template
|
templateUrl: 'hero-list-auto.component.html',
|
||||||
template: `
|
styleUrls: ['./hero-list-page.component.css'],
|
||||||
<ul>
|
// #docregion auto-calc
|
||||||
<li *ngFor="let hero of heroes"
|
|
||||||
[@shrinkOut]="'in'">
|
|
||||||
{{hero.name}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
`,
|
|
||||||
// #enddocregion template
|
|
||||||
styleUrls: ['./hero-list.component.css'],
|
|
||||||
|
|
||||||
/* When the element leaves (transition "in => void" occurs),
|
|
||||||
* get the element's current computed height and animate
|
|
||||||
* it down to 0.
|
|
||||||
*/
|
|
||||||
// #docregion animationdef
|
|
||||||
animations: [
|
animations: [
|
||||||
trigger('shrinkOut', [
|
trigger('shrinkOut', [
|
||||||
state('in', style({height: '*'})),
|
state('in', style({ height: '*' })),
|
||||||
transition('* => void', [
|
transition('* => void', [
|
||||||
style({height: '*'}),
|
style({ height: '*' }),
|
||||||
animate(250, style({height: 0}))
|
animate(250, style({ height: 0 }))
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
// #enddocregion animationdef
|
// #enddocregion auto-calc
|
||||||
})
|
})
|
||||||
export class HeroListAutoComponent {
|
export class HeroListAutoComponent {
|
||||||
@Input() heroes: Hero[];
|
@Input() heroes: Hero[];
|
||||||
|
|
||||||
|
@Output() remove = new EventEmitter<number>();
|
||||||
|
|
||||||
|
removeHero(id: number) {
|
||||||
|
this.remove.emit(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
// #docplaster
|
|
||||||
// #docregion
|
|
||||||
// #docregion imports
|
|
||||||
import {
|
|
||||||
Component,
|
|
||||||
Input
|
|
||||||
} from '@angular/core';
|
|
||||||
import {
|
|
||||||
trigger,
|
|
||||||
state,
|
|
||||||
style,
|
|
||||||
animate,
|
|
||||||
transition
|
|
||||||
} from '@angular/animations';
|
|
||||||
// #enddocregion imports
|
|
||||||
|
|
||||||
import { Hero } from './hero.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-hero-list-basic',
|
|
||||||
// #enddocregion
|
|
||||||
/* The click event calls hero.toggleState(), which
|
|
||||||
* causes the state of that hero to switch from
|
|
||||||
* active to inactive or vice versa.
|
|
||||||
*/
|
|
||||||
// #docregion
|
|
||||||
// #docregion template
|
|
||||||
template: `
|
|
||||||
<ul>
|
|
||||||
<li *ngFor="let hero of heroes"
|
|
||||||
[@heroState]="hero.state"
|
|
||||||
(click)="hero.toggleState()">
|
|
||||||
{{hero.name}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
`,
|
|
||||||
// #enddocregion template
|
|
||||||
styleUrls: ['./hero-list.component.css'],
|
|
||||||
// #enddocregion
|
|
||||||
/**
|
|
||||||
* Define two states, "inactive" and "active", and the end
|
|
||||||
* styles that apply whenever the element is in those states.
|
|
||||||
* Then define animations for transitioning between the states,
|
|
||||||
* one in each direction
|
|
||||||
*/
|
|
||||||
// #docregion
|
|
||||||
// #docregion animationdef
|
|
||||||
animations: [
|
|
||||||
trigger('heroState', [
|
|
||||||
// #docregion states
|
|
||||||
state('inactive', style({
|
|
||||||
backgroundColor: '#eee',
|
|
||||||
transform: 'scale(1)'
|
|
||||||
})),
|
|
||||||
state('active', style({
|
|
||||||
backgroundColor: '#cfd8dc',
|
|
||||||
transform: 'scale(1.1)'
|
|
||||||
})),
|
|
||||||
// #enddocregion states
|
|
||||||
// #docregion transitions
|
|
||||||
transition('inactive => active', animate('100ms ease-in')),
|
|
||||||
transition('active => inactive', animate('100ms ease-out'))
|
|
||||||
// #enddocregion transitions
|
|
||||||
])
|
|
||||||
]
|
|
||||||
// #enddocregion animationdef
|
|
||||||
})
|
|
||||||
export class HeroListBasicComponent {
|
|
||||||
@Input() heroes: Hero[];
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
// #docregion
|
|
||||||
// #docregion imports
|
|
||||||
import {
|
|
||||||
Component,
|
|
||||||
Input
|
|
||||||
} from '@angular/core';
|
|
||||||
import {
|
|
||||||
trigger,
|
|
||||||
state,
|
|
||||||
style,
|
|
||||||
animate,
|
|
||||||
transition
|
|
||||||
} from '@angular/animations';
|
|
||||||
// #enddocregion imports
|
|
||||||
|
|
||||||
import { Hero } from './hero.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-hero-list-combined-transitions',
|
|
||||||
// #docregion template
|
|
||||||
template: `
|
|
||||||
<ul>
|
|
||||||
<li *ngFor="let hero of heroes"
|
|
||||||
[@heroState]="hero.state"
|
|
||||||
(click)="hero.toggleState()">
|
|
||||||
{{hero.name}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
`,
|
|
||||||
// #enddocregion template
|
|
||||||
styleUrls: ['./hero-list.component.css'],
|
|
||||||
/*
|
|
||||||
* Define two states, "inactive" and "active", and the end
|
|
||||||
* styles that apply whenever the element is in those states.
|
|
||||||
* Then define an animated transition between these two
|
|
||||||
* states, in *both* directions.
|
|
||||||
*/
|
|
||||||
// #docregion animationdef
|
|
||||||
animations: [
|
|
||||||
trigger('heroState', [
|
|
||||||
state('inactive', style({
|
|
||||||
backgroundColor: '#eee',
|
|
||||||
transform: 'scale(1)'
|
|
||||||
})),
|
|
||||||
state('active', style({
|
|
||||||
backgroundColor: '#cfd8dc',
|
|
||||||
transform: 'scale(1.1)'
|
|
||||||
})),
|
|
||||||
// #docregion transitions
|
|
||||||
transition('inactive => active, active => inactive',
|
|
||||||
animate('100ms ease-out'))
|
|
||||||
// #enddocregion transitions
|
|
||||||
])
|
|
||||||
]
|
|
||||||
// #enddocregion animationdef
|
|
||||||
})
|
|
||||||
export class HeroListCombinedTransitionsComponent {
|
|
||||||
@Input() heroes: Hero[];
|
|
||||||
}
|
|
@ -0,0 +1,20 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { HEROES } from './mock-heroes';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-list-enter-leave-page',
|
||||||
|
template: `
|
||||||
|
<section>
|
||||||
|
<h2>Enter/Leave</h2>
|
||||||
|
|
||||||
|
<app-hero-list-enter-leave [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-enter-leave>
|
||||||
|
</section>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class HeroListEnterLeavePageComponent {
|
||||||
|
heroes = HEROES.slice();
|
||||||
|
|
||||||
|
onRemove(id: number) {
|
||||||
|
this.heroes = this.heroes.filter(hero => hero.id !== id);
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
Input
|
|
||||||
} from '@angular/core';
|
|
||||||
import {
|
|
||||||
trigger,
|
|
||||||
state,
|
|
||||||
style,
|
|
||||||
animate,
|
|
||||||
transition
|
|
||||||
} from '@angular/animations';
|
|
||||||
|
|
||||||
import { Hero } from './hero.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-hero-list-enter-leave-states',
|
|
||||||
// #docregion template
|
|
||||||
template: `
|
|
||||||
<ul>
|
|
||||||
<li *ngFor="let hero of heroes"
|
|
||||||
(click)="hero.toggleState()"
|
|
||||||
[@heroState]="hero.state">
|
|
||||||
{{hero.name}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
`,
|
|
||||||
// #enddocregion template
|
|
||||||
styleUrls: ['./hero-list.component.css'],
|
|
||||||
/* The elements here have two possible states based
|
|
||||||
* on the hero state, "active", or "inactive". We animate
|
|
||||||
* six transitions: Between the two states in both directions,
|
|
||||||
* and between each state and void. With this we can animate
|
|
||||||
* the enter and leave of elements differently based on which
|
|
||||||
* state they are in when they are added and removed.
|
|
||||||
*/
|
|
||||||
// #docregion animationdef
|
|
||||||
animations: [
|
|
||||||
trigger('heroState', [
|
|
||||||
state('inactive', style({transform: 'translateX(0) scale(1)'})),
|
|
||||||
state('active', style({transform: 'translateX(0) scale(1.1)'})),
|
|
||||||
transition('inactive => active', animate('100ms ease-in')),
|
|
||||||
transition('active => inactive', animate('100ms ease-out')),
|
|
||||||
transition('void => inactive', [
|
|
||||||
style({transform: 'translateX(-100%) scale(1)'}),
|
|
||||||
animate(100)
|
|
||||||
]),
|
|
||||||
transition('inactive => void', [
|
|
||||||
animate(100, style({transform: 'translateX(100%) scale(1)'}))
|
|
||||||
]),
|
|
||||||
transition('void => active', [
|
|
||||||
style({transform: 'translateX(0) scale(0)'}),
|
|
||||||
animate(200)
|
|
||||||
]),
|
|
||||||
transition('active => void', [
|
|
||||||
animate(200, style({transform: 'translateX(0) scale(0)'}))
|
|
||||||
])
|
|
||||||
])
|
|
||||||
]
|
|
||||||
// #enddocregion animationdef
|
|
||||||
})
|
|
||||||
export class HeroListEnterLeaveStatesComponent {
|
|
||||||
@Input() heroes: Hero[];
|
|
||||||
}
|
|
@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
Input
|
Input,
|
||||||
|
Output,
|
||||||
|
EventEmitter
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
trigger,
|
trigger,
|
||||||
@ -10,42 +12,45 @@ import {
|
|||||||
transition
|
transition
|
||||||
} from '@angular/animations';
|
} from '@angular/animations';
|
||||||
|
|
||||||
import { Hero } from './hero.service';
|
import { Hero } from './hero';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-hero-list-enter-leave',
|
selector: 'app-hero-list-enter-leave',
|
||||||
// #docregion template
|
// #docregion template
|
||||||
template: `
|
template: `
|
||||||
<ul>
|
<ul class="heroes">
|
||||||
<li *ngFor="let hero of heroes"
|
<li *ngFor="let hero of heroes"
|
||||||
[@flyInOut]="'in'">
|
[@flyInOut]="'in'" (click)="removeHero(hero.id)">
|
||||||
{{hero.name}}
|
<div class="inner">
|
||||||
|
<span class="badge">{{ hero.id }}</span>
|
||||||
|
<span>{{ hero.name }}</span>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
`,
|
`,
|
||||||
// #enddocregion template
|
// #enddocregion template
|
||||||
styleUrls: ['./hero-list.component.css'],
|
styleUrls: ['./hero-list-page.component.css'],
|
||||||
/* The element here always has the state "in" when it
|
|
||||||
* is present. We animate two transitions: From void
|
|
||||||
* to in and from in to void, to achieve an animated
|
|
||||||
* enter and leave transition. The element enters from
|
|
||||||
* the left and leaves to the right using translateX.
|
|
||||||
*/
|
|
||||||
// #docregion animationdef
|
// #docregion animationdef
|
||||||
animations: [
|
animations: [
|
||||||
trigger('flyInOut', [
|
trigger('flyInOut', [
|
||||||
state('in', style({transform: 'translateX(0)'})),
|
state('in', style({ transform: 'translateX(0)' })),
|
||||||
transition('void => *', [
|
transition('void => *', [
|
||||||
style({transform: 'translateX(-100%)'}),
|
style({ transform: 'translateX(-100%)' }),
|
||||||
animate(100)
|
animate(100)
|
||||||
]),
|
]),
|
||||||
transition('* => void', [
|
transition('* => void', [
|
||||||
animate(100, style({transform: 'translateX(100%)'}))
|
animate(100, style({ transform: 'translateX(100%)' }))
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
// #enddocregion animationdef
|
// #enddocregion animationdef
|
||||||
})
|
})
|
||||||
export class HeroListEnterLeaveComponent {
|
export class HeroListEnterLeaveComponent {
|
||||||
@Input() heroes: Hero[];
|
@Input() heroes: Hero[];
|
||||||
|
|
||||||
|
@Output() remove = new EventEmitter<number>();
|
||||||
|
|
||||||
|
removeHero(id: number) {
|
||||||
|
this.remove.emit(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { HEROES } from './mock-heroes';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-list-groups-page',
|
||||||
|
template: `
|
||||||
|
<section>
|
||||||
|
<h2>Hero List Group</h2>
|
||||||
|
|
||||||
|
<app-hero-list-groups [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-groups>
|
||||||
|
</section>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class HeroListGroupPageComponent {
|
||||||
|
heroes = HEROES.slice();
|
||||||
|
|
||||||
|
onRemove(id: number) {
|
||||||
|
this.heroes = this.heroes.filter(hero => hero.id !== id);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user