mirror of
https://github.com/go-task/task.git
synced 2026-05-18 05:05:20 +02:00
Compare commits
2580 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b27a6653f6 | ||
|
|
76269b03c4 | ||
|
|
ed492ac862 | ||
|
|
1d385ad5b4 | ||
|
|
2eff3545d9 | ||
|
|
bd79c29a39 | ||
|
|
136e0dae89 | ||
|
|
41a2137044 | ||
|
|
aea7f7a713 | ||
|
|
c3e16b0a68 | ||
|
|
19699a1f0e | ||
|
|
9e3ff27ba1 | ||
|
|
de05ebc503 | ||
|
|
6e154f2b71 | ||
|
|
9ba1e566a9 | ||
|
|
1b53d425ee | ||
|
|
7b3559ce4c | ||
|
|
43dbeb4260 | ||
|
|
d9e0e04725 | ||
|
|
629a10813f | ||
|
|
dc64864643 | ||
|
|
5f78da2d0a | ||
|
|
7915b78ac2 | ||
|
|
3441bf522e | ||
|
|
46f5db22c8 | ||
|
|
ecffcc720f | ||
|
|
be35b3af75 | ||
|
|
3aaedc790d | ||
|
|
0b19d973ac | ||
|
|
70b6cd8ee0 | ||
|
|
1eb5720e7e | ||
|
|
1b06da16f6 | ||
|
|
6e37e3d7a7 | ||
|
|
4bea638b05 | ||
|
|
8f2d17a387 | ||
|
|
f7d17fffad | ||
|
|
697ef35303 | ||
|
|
8fe3d048fa | ||
|
|
d61d92dfdf | ||
|
|
114ac1eedc | ||
|
|
a016b7b72b | ||
|
|
219bf3e5a5 | ||
|
|
b36fcfd8bb | ||
|
|
ef8fb84c8f | ||
|
|
ddecd51715 | ||
|
|
88fc6d4f24 | ||
|
|
07fbd9887e | ||
|
|
f2e32beabd | ||
|
|
8fa9dc04ac | ||
|
|
0c98f1ad13 | ||
|
|
a12cc6e843 | ||
|
|
44e1350d0c | ||
|
|
2973dd75f9 | ||
|
|
a10f1d2ee7 | ||
|
|
f727b55fbc | ||
|
|
363153cbf3 | ||
|
|
6b436eda48 | ||
|
|
2ddebca4e1 | ||
|
|
b6ab6153a2 | ||
|
|
bca99520bf | ||
|
|
1312ee8a81 | ||
|
|
b7cb204f10 | ||
|
|
921f84157a | ||
|
|
470ef30f8f | ||
|
|
87b12e663e | ||
|
|
e61700f36d | ||
|
|
8b6aca5722 | ||
|
|
19d8fae5f9 | ||
|
|
c387048f8f | ||
|
|
f2b3ba1263 | ||
|
|
2139e32426 | ||
|
|
d4e168128b | ||
|
|
54bdcba369 | ||
|
|
c55c969474 | ||
|
|
73f9879421 | ||
|
|
aa83651da2 | ||
|
|
4ddad9f9f7 | ||
|
|
080ee8869f | ||
|
|
af943b064b | ||
|
|
962eada344 | ||
|
|
a0d9750edf | ||
|
|
a1b8985df0 | ||
|
|
21daf6160a | ||
|
|
c70d28f7b8 | ||
|
|
90e6ef88dc | ||
|
|
a788034148 | ||
|
|
90bcbe9ba5 | ||
|
|
60a808ca23 | ||
|
|
edc80aed81 | ||
|
|
68bea7f273 | ||
|
|
c62f9c7147 | ||
|
|
c4ecff753d | ||
|
|
2ed77716be | ||
|
|
a2f8e144ca | ||
|
|
2cdd7d3e43 | ||
|
|
56b316a124 | ||
|
|
39ce6a21ac | ||
|
|
fc5f6fa3aa | ||
|
|
44a2f2e5f5 | ||
|
|
d356c649aa | ||
|
|
ca24d32f37 | ||
|
|
f63a63fa6c | ||
|
|
c0ff7105e7 | ||
|
|
8b063d6b92 | ||
|
|
dc8ac5e79f | ||
|
|
df7810ab63 | ||
|
|
82783417ea | ||
|
|
d5bed6b716 | ||
|
|
c8f722c0d5 | ||
|
|
b7743eda88 | ||
|
|
c0796e9701 | ||
|
|
cf54be3266 | ||
|
|
e129ae2fac | ||
|
|
ed69256512 | ||
|
|
40ad9719d4 | ||
|
|
48f75f0913 | ||
|
|
f000ea2b22 | ||
|
|
e8be687a40 | ||
|
|
788605a3a9 | ||
|
|
697cf442a2 | ||
|
|
e957edf783 | ||
|
|
09e7247d05 | ||
|
|
502f24a2ad | ||
|
|
f09f31c6d5 | ||
|
|
5a78808caa | ||
|
|
026c899d90 | ||
|
|
f6720760b4 | ||
|
|
065236f076 | ||
|
|
1bd5aa6bd5 | ||
|
|
c3fd3c4b5e | ||
|
|
299232ee7d | ||
|
|
12a26fa15e | ||
|
|
4ab5dec8ae | ||
|
|
af311229fe | ||
|
|
1443e2d989 | ||
|
|
5bf4e4a29b | ||
|
|
f9052c9fdf | ||
|
|
0a82e2e053 | ||
|
|
6dedcafd7d | ||
|
|
c84cfa41f7 | ||
|
|
9bc1efbc47 | ||
|
|
da7eb0c855 | ||
|
|
edb491a4d0 | ||
|
|
2ad3d26f4a | ||
|
|
cdfcd08213 | ||
|
|
382c37bc2a | ||
|
|
618cd8956f | ||
|
|
b53e5da41a | ||
|
|
b9c1ab8eae | ||
|
|
e47f55783e | ||
|
|
d5f071c096 | ||
|
|
fb784f4e3d | ||
|
|
91f9299c98 | ||
|
|
145412a75c | ||
|
|
ba38344ca6 | ||
|
|
4e963f8714 | ||
|
|
3d4d189bcd | ||
|
|
179bde1f37 | ||
|
|
e4de687aee | ||
|
|
06538860a8 | ||
|
|
8a37bf5c1f | ||
|
|
ca99266aea | ||
|
|
8dfafe507f | ||
|
|
678fdec7d2 | ||
|
|
3626b271a7 | ||
|
|
fc378cfb92 | ||
|
|
9488a2a744 | ||
|
|
6ece2445ae | ||
|
|
9b95e758f4 | ||
|
|
28fee2c356 | ||
|
|
763e77467b | ||
|
|
f2385e625d | ||
|
|
e929cccd73 | ||
|
|
cb183349b7 | ||
|
|
2ebbb99f58 | ||
|
|
6660afc8d2 | ||
|
|
b710259bfa | ||
|
|
4ec6c453bd | ||
|
|
28408ef3f4 | ||
|
|
a2d34ffc4c | ||
|
|
1a190a118f | ||
|
|
18efa3982f | ||
|
|
655e83454e | ||
|
|
3ad4604c36 | ||
|
|
5a27d04655 | ||
|
|
ea933bcc55 | ||
|
|
e0d6b71971 | ||
|
|
d7ee855e49 | ||
|
|
511f35a456 | ||
|
|
5889ff6b65 | ||
|
|
85a98b5f90 | ||
|
|
89b6140166 | ||
|
|
8cd51af3b0 | ||
|
|
a40ddd4949 | ||
|
|
b1814277c2 | ||
|
|
500ab8b941 | ||
|
|
745633dc0e | ||
|
|
9b99866224 | ||
|
|
54e4905432 | ||
|
|
c95805e0e0 | ||
|
|
4560589652 | ||
|
|
084d6444b4 | ||
|
|
3fb7919577 | ||
|
|
69b345efc9 | ||
|
|
4af5278d73 | ||
|
|
12fbdd3ec7 | ||
|
|
72a349b0e9 | ||
|
|
896d65b21f | ||
|
|
2161f33b5c | ||
|
|
b93638b97a | ||
|
|
47b78ca879 | ||
|
|
f0b15d397b | ||
|
|
eb285fa3d2 | ||
|
|
02b13a687a | ||
|
|
a085d62727 | ||
|
|
4ab1958df1 | ||
|
|
54ca217b92 | ||
|
|
a6c0c1daba | ||
|
|
9cc1c7b40b | ||
|
|
7901cce831 | ||
|
|
c7b4f26900 | ||
|
|
3ed403b839 | ||
|
|
386dcbc1a0 | ||
|
|
799bc85498 | ||
|
|
0d9e8dd71b | ||
|
|
a927ffb31e | ||
|
|
42ad618205 | ||
|
|
2b713f564f | ||
|
|
cb8e94aa33 | ||
|
|
6bc339d714 | ||
|
|
5712c463f5 | ||
|
|
78cc6e5fd3 | ||
|
|
38e07ea812 | ||
|
|
72e25a25fd | ||
|
|
a496ee5fcb | ||
|
|
ef4292c42f | ||
|
|
dc315efc7f | ||
|
|
a3a3e7fb0b | ||
|
|
ee99849b1d | ||
|
|
bf9dc3f662 | ||
|
|
94f82cbc5a | ||
|
|
b14318ed3f | ||
|
|
17757c0c15 | ||
|
|
19f72b7eb0 | ||
|
|
0052ad2309 | ||
|
|
af1e755196 | ||
|
|
43074c20f2 | ||
|
|
39c86992bd | ||
|
|
c71241bcbd | ||
|
|
7c2bb78540 | ||
|
|
32e675895a | ||
|
|
786813d95d | ||
|
|
f7287c503a | ||
|
|
413574e3ee | ||
|
|
4b39becf65 | ||
|
|
15b7e3c69a | ||
|
|
7c93ea8b44 | ||
|
|
6a7cfa58f9 | ||
|
|
74b93f6eef | ||
|
|
88101613c8 | ||
|
|
599591ad3c | ||
|
|
348158a5f6 | ||
|
|
c3e410e95a | ||
|
|
42bcd5406a | ||
|
|
ba23aca631 | ||
|
|
5ef245a4bd | ||
|
|
036a60f517 | ||
|
|
9c969541a5 | ||
|
|
a52b483dd0 | ||
|
|
4e84c6bb76 | ||
|
|
0f9baf62a1 | ||
|
|
979ad523ef | ||
|
|
975c07688e | ||
|
|
67a02255b5 | ||
|
|
028ae1a660 | ||
|
|
68b1d2783d | ||
|
|
12793c350d | ||
|
|
8716ab81be | ||
|
|
c2a4e4470b | ||
|
|
f5a8ec8a0c | ||
|
|
048d92709a | ||
|
|
8dc9637e7a | ||
|
|
700bf00107 | ||
|
|
4836d42828 | ||
|
|
5762d5ef8e | ||
|
|
9f2fe0da61 | ||
|
|
d1a5771839 | ||
|
|
7663abdcde | ||
|
|
1e42e1f817 | ||
|
|
5f7ae5d32e | ||
|
|
17db402e4b | ||
|
|
f2242958a6 | ||
|
|
ea4b695b5a | ||
|
|
209c88c341 | ||
|
|
bd94f9f607 | ||
|
|
9f6b78ec84 | ||
|
|
fbde227167 | ||
|
|
fc06e92a87 | ||
|
|
a0cab3f5ec | ||
|
|
bb4c254211 | ||
|
|
57bf348829 | ||
|
|
092b9b6391 | ||
|
|
cd8c831204 | ||
|
|
0d03f4f266 | ||
|
|
b8bf298c84 | ||
|
|
9a91c4cb21 | ||
|
|
2921450bf7 | ||
|
|
dffa355cad | ||
|
|
48039be12c | ||
|
|
43cb64e6cc | ||
|
|
25a7b5936f | ||
|
|
4ae3071845 | ||
|
|
242523c797 | ||
|
|
0fdb5e8665 | ||
|
|
534dfa089c | ||
|
|
51a3bcaacd | ||
|
|
6289fcf34c | ||
|
|
2959737d7d | ||
|
|
a3047d3cd8 | ||
|
|
725600f220 | ||
|
|
fd83414074 | ||
|
|
6c645a33f7 | ||
|
|
9d969e5971 | ||
|
|
8b382a3bae | ||
|
|
a34892ad94 | ||
|
|
e55bb29554 | ||
|
|
1168ef32df | ||
|
|
245d7f747f | ||
|
|
b216ae885c | ||
|
|
61cb15ad01 | ||
|
|
04579c0c44 | ||
|
|
39462cbfde | ||
|
|
72dfec68b0 | ||
|
|
f89c12ddf0 | ||
|
|
c903d07332 | ||
|
|
138b9a5a4f | ||
|
|
1e2121a99f | ||
|
|
9495fb2b1c | ||
|
|
1fda55910e | ||
|
|
e6c808c02b | ||
|
|
0fc26a43a9 | ||
|
|
c0b4c19443 | ||
|
|
1a8df44e9e | ||
|
|
82ad1de8d0 | ||
|
|
d59c795502 | ||
|
|
504cb94e8b | ||
|
|
e7606635fe | ||
|
|
9a05ceaa80 | ||
|
|
083654d8c9 | ||
|
|
79c93fb42b | ||
|
|
64fc538a16 | ||
|
|
4da081e5c3 | ||
|
|
4bdfe5ce3b | ||
|
|
26ef693417 | ||
|
|
952f32d388 | ||
|
|
e72c35f79f | ||
|
|
72991d4f04 | ||
|
|
6f965e3043 | ||
|
|
1c6d686356 | ||
|
|
dac5aa1954 | ||
|
|
303bd6ccb2 | ||
|
|
f736cfaaf1 | ||
|
|
53f97889bc | ||
|
|
fe2da74ea3 | ||
|
|
64fb66895b | ||
|
|
d2bd834c81 | ||
|
|
8a43ca5d8f | ||
|
|
a10a9faabf | ||
|
|
3d3ed0e403 | ||
|
|
47dc87a2c9 | ||
|
|
3b0a746f85 | ||
|
|
281edfe5b3 | ||
|
|
7289ffce0b | ||
|
|
61e1af50ff | ||
|
|
715a143735 | ||
|
|
a0b1605634 | ||
|
|
69fc13bd13 | ||
|
|
b42a52ba77 | ||
|
|
cb812476b3 | ||
|
|
b09c6870fe | ||
|
|
86e4a3aac7 | ||
|
|
7782bc92ae | ||
|
|
9cc2d65091 | ||
|
|
b932e539d9 | ||
|
|
be45eb04d9 | ||
|
|
6b878980dc | ||
|
|
cd910abd45 | ||
|
|
6e524bb2fa | ||
|
|
b4c8f5a0fe | ||
|
|
09f85844ba | ||
|
|
d54d2ccabc | ||
|
|
cf81ab3112 | ||
|
|
aaa7b7772d | ||
|
|
71eb8cdeea | ||
|
|
68ce8b1d84 | ||
|
|
5323990c72 | ||
|
|
ec4e68d601 | ||
|
|
bb5b045293 | ||
|
|
89f29cb75b | ||
|
|
da4ce5b0a5 | ||
|
|
fb68a5f79a | ||
|
|
f40f389cb4 | ||
|
|
a459eeaabb | ||
|
|
84f02a822f | ||
|
|
55d1aa260d | ||
|
|
e7084cdf26 | ||
|
|
ca55e9b621 | ||
|
|
6528b36caa | ||
|
|
f8736c5f77 | ||
|
|
6896accf86 | ||
|
|
c12ed49acb | ||
|
|
d1bfd3e9f7 | ||
|
|
fc17343fcc | ||
|
|
d3e9be1520 | ||
|
|
d850d03c96 | ||
|
|
0058f18676 | ||
|
|
b3c4007756 | ||
|
|
9e8fd54be9 | ||
|
|
a33544101a | ||
|
|
1c35358fcc | ||
|
|
13daa6dc35 | ||
|
|
20c1ffe098 | ||
|
|
bd8ccb8d03 | ||
|
|
8162b05f59 | ||
|
|
68d5095761 | ||
|
|
6cb0a5a2f2 | ||
|
|
08056924e0 | ||
|
|
39706105e1 | ||
|
|
bf4e7960cb | ||
|
|
3d36616e9e | ||
|
|
3976e8372a | ||
|
|
c2123dc016 | ||
|
|
0a6cd1ee42 | ||
|
|
7169bf6434 | ||
|
|
84cd4dfdad | ||
|
|
672b39413f | ||
|
|
7eebf6e704 | ||
|
|
4834ac743c | ||
|
|
c5afffb551 | ||
|
|
1ae3bf0b25 | ||
|
|
a84f09d45f | ||
|
|
f47f237093 | ||
|
|
04df108fb5 | ||
|
|
8885d9e4f7 | ||
|
|
a60c2ec3f8 | ||
|
|
f789c57624 | ||
|
|
7416b7d77e | ||
|
|
c1ab661cf2 | ||
|
|
768dca053b | ||
|
|
e65159f613 | ||
|
|
789a7ea950 | ||
|
|
b11da93c78 | ||
|
|
8c720b03aa | ||
|
|
8c8b1b5f3b | ||
|
|
38b42d0fb1 | ||
|
|
669bf33619 | ||
|
|
6f0f38b8d9 | ||
|
|
a9de239e38 | ||
|
|
f0414f162d | ||
|
|
a24f4958cd | ||
|
|
55790be6ad | ||
|
|
88fdbd13cf | ||
|
|
566ac29932 | ||
|
|
ffef3ed1a6 | ||
|
|
2a60842707 | ||
|
|
41bd866813 | ||
|
|
01bc0a0529 | ||
|
|
a6a9792b7e | ||
|
|
ce032dc46b | ||
|
|
f07f4c85b2 | ||
|
|
cd81d94e18 | ||
|
|
1939f83ffe | ||
|
|
2a92b70bc2 | ||
|
|
4736bc2734 | ||
|
|
180fcef364 | ||
|
|
f6baa5942e | ||
|
|
d54b0d6a2a | ||
|
|
03b242d4c3 | ||
|
|
60e28ecdcc | ||
|
|
dd8daa68cd | ||
|
|
55617e062f | ||
|
|
c6f1b3ae4f | ||
|
|
cb14a4f3a1 | ||
|
|
0d5f2b5dab | ||
|
|
89caf1e049 | ||
|
|
7f7e8306da | ||
|
|
1f2eecda9e | ||
|
|
60c959c75c | ||
|
|
a771e91ff3 | ||
|
|
532644d7f8 | ||
|
|
b68f4067d9 | ||
|
|
c544b0058d | ||
|
|
d1360ee72a | ||
|
|
076aff1f8e | ||
|
|
ffeb3bcc3f | ||
|
|
8181352d54 | ||
|
|
23fd7e782c | ||
|
|
6604b9a8cc | ||
|
|
6ee1053c96 | ||
|
|
8eaf83599e | ||
|
|
cd086228b2 | ||
|
|
1b8b399c7e | ||
|
|
8426f84b18 | ||
|
|
14bbb324e5 | ||
|
|
b9d202c491 | ||
|
|
c23c46e326 | ||
|
|
a266fba93e | ||
|
|
fb631902ce | ||
|
|
b14125bacd | ||
|
|
3c5782f4a4 | ||
|
|
60c8ee0ce6 | ||
|
|
cdaf69e03d | ||
|
|
d6234af49a | ||
|
|
a31f2cf4a8 | ||
|
|
0dd6f78855 | ||
|
|
6f80777faf | ||
|
|
8558e0c48a | ||
|
|
461714a899 | ||
|
|
8a35033abc | ||
|
|
daf39a04bf | ||
|
|
25f9299d0a | ||
|
|
4d15a8be8f | ||
|
|
cbde4c33f8 | ||
|
|
cdb6a3f70a | ||
|
|
fb27318601 | ||
|
|
35ea4e0460 | ||
|
|
2b4d9bfba7 | ||
|
|
ce96447468 | ||
|
|
e7a6de64cb | ||
|
|
ff8c913ce7 | ||
|
|
0e23404d23 | ||
|
|
65a64a01ee | ||
|
|
f6ec7444d5 | ||
|
|
6ce798e16c | ||
|
|
be81885835 | ||
|
|
69ac06170a | ||
|
|
c995fe6d11 | ||
|
|
9009124192 | ||
|
|
80f96d67da | ||
|
|
002b8c929a | ||
|
|
b5b1524d3a | ||
|
|
3aee0a0519 | ||
|
|
23df1f0c61 | ||
|
|
edbb83f6de | ||
|
|
c903d5c6f4 | ||
|
|
88c4ba1740 | ||
|
|
7d4c52546a | ||
|
|
f5121de468 | ||
|
|
b5d573fbd9 | ||
|
|
888de0f8ef | ||
|
|
09b11d343b | ||
|
|
a2390d0dca | ||
|
|
0f633091eb | ||
|
|
6b16c532c2 | ||
|
|
69f5714e45 | ||
|
|
b3e4cfcf48 | ||
|
|
65a71e5df3 | ||
|
|
bad2c8fcc1 | ||
|
|
97f41b710e | ||
|
|
240047152d | ||
|
|
24a830e384 | ||
|
|
fe9f489702 | ||
|
|
27de441ed2 | ||
|
|
79f7af2b04 | ||
|
|
b588d49cfb | ||
|
|
45006e2ce0 | ||
|
|
e5d8237053 | ||
|
|
89740ed72a | ||
|
|
c1e14c461b | ||
|
|
dc2eceb634 | ||
|
|
43f3dcea05 | ||
|
|
f27daea5c9 | ||
|
|
49e88e92cf | ||
|
|
da40aabcc7 | ||
|
|
8ce9bdc8c7 | ||
|
|
0409c3c3ba | ||
|
|
fd3532812e | ||
|
|
2965841eb7 | ||
|
|
dbe6e41ac8 | ||
|
|
8f73ced037 | ||
|
|
2a4f93eb41 | ||
|
|
9d8c4ba7e6 | ||
|
|
1bda388925 | ||
|
|
d64df3f9d7 | ||
|
|
d1f18d36b8 | ||
|
|
5f1d46c770 | ||
|
|
9727eef476 | ||
|
|
c5be676555 | ||
|
|
f3317266dc | ||
|
|
36ff00e3f9 | ||
|
|
041063b732 | ||
|
|
2ab1dcbf1d | ||
|
|
24a0f24835 | ||
|
|
4dffab2e0a | ||
|
|
b9a5d1c573 | ||
|
|
e1818e9e31 | ||
|
|
bb2de3fdf9 | ||
|
|
82f6029043 | ||
|
|
cfaecf8b4c | ||
|
|
4595c1e32a | ||
|
|
1a648dea50 | ||
|
|
a273183745 | ||
|
|
e2243fc6d9 | ||
|
|
c1209d9f13 | ||
|
|
2b54b04cfc | ||
|
|
32fa3a0156 | ||
|
|
973e928c28 | ||
|
|
bc844246d4 | ||
|
|
41884f0a69 | ||
|
|
2a96c20739 | ||
|
|
c28eb204fb | ||
|
|
b1535aedc1 | ||
|
|
7e3feb2993 | ||
|
|
8a79a41717 | ||
|
|
530818a742 | ||
|
|
517bb3fc97 | ||
|
|
6645a1f34c | ||
|
|
2aa2963565 | ||
|
|
390220ec9c | ||
|
|
c3bd6b9384 | ||
|
|
d8e176311d | ||
|
|
1c68f0fee4 | ||
|
|
118ef01a69 | ||
|
|
148b090d8e | ||
|
|
28a96d1427 | ||
|
|
47f5e6ab89 | ||
|
|
fe09c01637 | ||
|
|
7ef3164b16 | ||
|
|
b48a32b103 | ||
|
|
2d2c408652 | ||
|
|
c381923d3e | ||
|
|
7bfddaa25a | ||
|
|
5581954fb1 | ||
|
|
c4f708b222 | ||
|
|
27056a9827 | ||
|
|
a35910429c | ||
|
|
9a7e79258c | ||
|
|
8dd3f4b119 | ||
|
|
9ecc8fc878 | ||
|
|
e078261f12 | ||
|
|
bdb3ffddd1 | ||
|
|
a72e70b026 | ||
|
|
c5eea294aa | ||
|
|
0fff404eb8 | ||
|
|
61172fa8da | ||
|
|
a6bc3f51cc | ||
|
|
1af7bf2670 | ||
|
|
d75536bf00 | ||
|
|
ce3e058f89 | ||
|
|
8d0f0b049c | ||
|
|
e619bad4a9 | ||
|
|
e6ea0647d7 | ||
|
|
d1dc271b9a | ||
|
|
f5082f3692 | ||
|
|
30c59bf387 | ||
|
|
38d0fc2c55 | ||
|
|
460e587c66 | ||
|
|
ad5a3166ac | ||
|
|
ddccd1bb61 | ||
|
|
96a690ac2f | ||
|
|
cb07189bab | ||
|
|
7e6577eb5f | ||
|
|
58ab26c4ab | ||
|
|
65d332dfd0 | ||
|
|
5eaf0b2dcd | ||
|
|
56f3735b38 | ||
|
|
23d578ac8c | ||
|
|
1bf850592c | ||
|
|
0be05795b9 | ||
|
|
08a2a91180 | ||
|
|
84cc5e57b0 | ||
|
|
5aa68e47e5 | ||
|
|
15aa4b86af | ||
|
|
114d5e1404 | ||
|
|
8ab5fe0e80 | ||
|
|
c89a6add48 | ||
|
|
888071e234 | ||
|
|
ff2e0f846a | ||
|
|
3c177d3fdc | ||
|
|
41bc490e0f | ||
|
|
f8e3742d11 | ||
|
|
a6100b39f8 | ||
|
|
1275ab1b5b | ||
|
|
0c05dcbe0f | ||
|
|
e9983e299f | ||
|
|
a450f2daea | ||
|
|
c77c8a419b | ||
|
|
a233b52c65 | ||
|
|
0e2c9cc88f | ||
|
|
dd9cec611a | ||
|
|
6985413f93 | ||
|
|
cf77768c82 | ||
|
|
6c3b13b676 | ||
|
|
ad45c7aeb3 | ||
|
|
e4b4d04abd | ||
|
|
a3bdb6c40a | ||
|
|
eb39dd94d0 | ||
|
|
21cd573770 | ||
|
|
281d259e6e | ||
|
|
1cb5daf73e | ||
|
|
3747b2ab7f | ||
|
|
d727ef5393 | ||
|
|
a72b65b3b2 | ||
|
|
ef3b853728 | ||
|
|
f302b50519 | ||
|
|
c243b0ec7e | ||
|
|
32158dac87 | ||
|
|
0a59890a46 | ||
|
|
defbcf6acd | ||
|
|
045d054a5f | ||
|
|
0941de3318 | ||
|
|
b259edeb65 | ||
|
|
35119c12ab | ||
|
|
f6ff775d11 | ||
|
|
5e9851f42f | ||
|
|
51c569ef37 | ||
|
|
1ca432a80d | ||
|
|
e781b3d4e0 | ||
|
|
81ff1cdea0 | ||
|
|
1f2cbfb932 | ||
|
|
4b6c79aca5 | ||
|
|
5739495739 | ||
|
|
9d72fa3250 | ||
|
|
4123ffc780 | ||
|
|
cdafc67bef | ||
|
|
9ee4f21d62 | ||
|
|
133086d647 | ||
|
|
88b095020e | ||
|
|
cc14996b71 | ||
|
|
375106c988 | ||
|
|
6ce6a38899 | ||
|
|
76030c9146 | ||
|
|
a71020eab5 | ||
|
|
6bef2ff8a9 | ||
|
|
413dcd28a8 | ||
|
|
da6f5c66a0 | ||
|
|
6012da7a21 | ||
|
|
46c5eafe35 | ||
|
|
830b745112 | ||
|
|
b52d4e4f40 | ||
|
|
3aaa3223a0 | ||
|
|
a9ff58d0fe | ||
|
|
eeaebaf8c7 | ||
|
|
2213141fcb | ||
|
|
19956889a7 | ||
|
|
4c580ebf18 | ||
|
|
3dccde270a | ||
|
|
53dd0b138a | ||
|
|
ea85909e8b | ||
|
|
6bf6fe7ead | ||
|
|
f39c6352ac | ||
|
|
4294cc92b9 | ||
|
|
40d77156df | ||
|
|
856ba3b8c2 | ||
|
|
0810ef01b0 | ||
|
|
527bbc3bf5 | ||
|
|
912bbcab8e | ||
|
|
aa45491510 | ||
|
|
1e25ceab29 | ||
|
|
a74b0bc679 | ||
|
|
a3fce1c302 | ||
|
|
7958cf50b3 | ||
|
|
b0efbad591 | ||
|
|
30e9c7d4cd | ||
|
|
baa5e2c378 | ||
|
|
cc97e2da1d | ||
|
|
a55e21bbb7 | ||
|
|
8d138a5eea | ||
|
|
635e3f4e7d | ||
|
|
252d549e3f | ||
|
|
182d43e8d8 | ||
|
|
f35e51e4e5 | ||
|
|
fb3c64c46e | ||
|
|
7535467f45 | ||
|
|
3e5cd6cdfd | ||
|
|
dcc060af89 | ||
|
|
55593090fa | ||
|
|
57c094f415 | ||
|
|
2f4876b71c | ||
|
|
725f929778 | ||
|
|
8266b28b48 | ||
|
|
f5c7472f64 | ||
|
|
ced3e7a579 | ||
|
|
36dd71b122 | ||
|
|
21531b6291 | ||
|
|
bfc9d7847d | ||
|
|
3397f2855f | ||
|
|
78a69c4c3e | ||
|
|
01716f55b3 | ||
|
|
ca364c20bb | ||
|
|
ee901fe568 | ||
|
|
7fa06eedf4 | ||
|
|
651033c5a7 | ||
|
|
17f6e816d8 | ||
|
|
cd259a741f | ||
|
|
c81dbda157 | ||
|
|
e23ef818ea | ||
|
|
ddd9964db7 | ||
|
|
a5b949f5dc | ||
|
|
630e58767b | ||
|
|
d87e5de56f | ||
|
|
f75aa1f84b | ||
|
|
53235f07ad | ||
|
|
f19c520f23 | ||
|
|
6951e5cd0c | ||
|
|
24059a4b76 | ||
|
|
fa022be1f9 | ||
|
|
a3b9554efd | ||
|
|
16070c7a24 | ||
|
|
72d9671fcf | ||
|
|
d01b3c8979 | ||
|
|
4024b4fa37 | ||
|
|
54c7f35b00 | ||
|
|
3efb437c9a | ||
|
|
e9448bd4be | ||
|
|
8f3180a9fa | ||
|
|
1d230af90d | ||
|
|
fb9f6c20ab | ||
|
|
6854b4c300 | ||
|
|
b10c573270 | ||
|
|
6ecfb634d2 | ||
|
|
6b3f8e29bb | ||
|
|
220bf74a9e | ||
|
|
0a027df50d | ||
|
|
a50580b5a1 | ||
|
|
1890722b75 | ||
|
|
1ff618cc17 | ||
|
|
eb2783fcce | ||
|
|
43d84560e5 | ||
|
|
1d4bb7b5ef | ||
|
|
454fe65ef3 | ||
|
|
c6c69a5a63 | ||
|
|
1f157fef94 | ||
|
|
e269bcf028 | ||
|
|
f06a4a35b1 | ||
|
|
567d84c317 | ||
|
|
3c8ad5a77c | ||
|
|
4db3759ace | ||
|
|
36fdd4e677 | ||
|
|
9c169ac9c6 | ||
|
|
bb68fb333f | ||
|
|
64b7d3415a | ||
|
|
a496a1dfa8 | ||
|
|
b5df4e89c2 | ||
|
|
9a5fb38f48 | ||
|
|
68191205c7 | ||
|
|
cbc19d35ea | ||
|
|
f00693052a | ||
|
|
5ab9329128 | ||
|
|
97cf02872f | ||
|
|
7c61a59ecb | ||
|
|
5538636373 | ||
|
|
0c3c2d70a2 | ||
|
|
faa9e07627 | ||
|
|
d973871efa | ||
|
|
c4d8b36e05 | ||
|
|
40e97bbbf4 | ||
|
|
41b45e6dc4 | ||
|
|
d2e26e2328 | ||
|
|
96f13e5f2c | ||
|
|
29a1322577 | ||
|
|
4882f81f15 | ||
|
|
0bbdbc5739 | ||
|
|
20e2dc7238 | ||
|
|
696fb38f3b | ||
|
|
0b74a57b4c | ||
|
|
4774273c98 | ||
|
|
a0a2cee218 | ||
|
|
c0a0faf3d3 | ||
|
|
a425e2bb6c | ||
|
|
f06f48e225 | ||
|
|
29e91a4137 | ||
|
|
08a888dc8a | ||
|
|
19a4d8f928 | ||
|
|
5c7ba665e5 | ||
|
|
efb12c0c04 | ||
|
|
ac561db9dc | ||
|
|
3c05c9c6e1 | ||
|
|
60d20c042e | ||
|
|
aff1f5316d | ||
|
|
d30539c17e | ||
|
|
5395921acc | ||
|
|
8a73411803 | ||
|
|
6c21568447 | ||
|
|
330722335d | ||
|
|
99397dfe98 | ||
|
|
1157b213de | ||
|
|
fa40e8a762 | ||
|
|
98e0cea469 | ||
|
|
508ff717c9 | ||
|
|
c7ba42b81a | ||
|
|
bb9d582255 | ||
|
|
38a06dad8e | ||
|
|
beb9f42215 | ||
|
|
df251de33e | ||
|
|
9a3d2bc3aa | ||
|
|
1ef5cf71d0 | ||
|
|
65fdb618aa | ||
|
|
3b44da323b | ||
|
|
2c20407e1b | ||
|
|
27455fc4c8 | ||
|
|
971c3e3a01 | ||
|
|
67b94798b7 | ||
|
|
c465234aa9 | ||
|
|
07a0b8938f | ||
|
|
ba81181eb7 | ||
|
|
e2b0789b0c | ||
|
|
2c6969d572 | ||
|
|
8d0754af4d | ||
|
|
81148c312e | ||
|
|
1f477eb456 | ||
|
|
870c07eafb | ||
|
|
6682489967 | ||
|
|
d5b42e97ec | ||
|
|
d6b2926828 | ||
|
|
909ec1ed0f | ||
|
|
672e0198f9 | ||
|
|
e9392df30b | ||
|
|
d0efc1c5cd | ||
|
|
3ff8fdbc0a | ||
|
|
6ebe2e765f | ||
|
|
fa82051a06 | ||
|
|
90a56df621 | ||
|
|
26e79121f9 | ||
|
|
e2b85c6aa1 | ||
|
|
8c0236c795 | ||
|
|
63ec83b8f7 | ||
|
|
1b146543c5 | ||
|
|
9ee0ea6ad1 | ||
|
|
b377ddebff | ||
|
|
d6b9b30804 | ||
|
|
149f6fe233 | ||
|
|
0488a80ace | ||
|
|
2cb68aff8b | ||
|
|
f1e2fee088 | ||
|
|
dc3cf1cb16 | ||
|
|
94aaea390f | ||
|
|
40b6150030 | ||
|
|
dbc120c970 | ||
|
|
25b1966506 | ||
|
|
bff0a0a3d4 | ||
|
|
b495a6bd0b | ||
|
|
98ea907284 | ||
|
|
1f3fca50b3 | ||
|
|
c655d90ab3 | ||
|
|
2ccf80713d | ||
|
|
d87e7981fb | ||
|
|
dfe39bfb5d | ||
|
|
f6a24fe925 | ||
|
|
d2522a6d9d | ||
|
|
e734e29009 | ||
|
|
3b5fbf94f7 | ||
|
|
7cb45a23b6 | ||
|
|
1a03c3fbaf | ||
|
|
d684e59b6a | ||
|
|
5fc66293b0 | ||
|
|
bb16c3efca | ||
|
|
f108fdd580 | ||
|
|
33f90a8c16 | ||
|
|
00896a1318 | ||
|
|
d797836cb8 | ||
|
|
7393821d64 | ||
|
|
42af0fc791 | ||
|
|
07e6f5cad7 | ||
|
|
334fd39e95 | ||
|
|
c199aaeac9 | ||
|
|
b4a7ad4fbe | ||
|
|
19bf2c2d48 | ||
|
|
61c0c32c2a | ||
|
|
bc88ad0de2 | ||
|
|
247c2586c2 | ||
|
|
2b67d05b9d | ||
|
|
212ff42304 | ||
|
|
b11be9d079 | ||
|
|
685a6f36d9 | ||
|
|
c569cbc220 | ||
|
|
58275b4b33 | ||
|
|
862237a931 | ||
|
|
9d81608337 | ||
|
|
39a4b4d413 | ||
|
|
21ceb05080 | ||
|
|
b592648d55 | ||
|
|
658b6012a6 | ||
|
|
311cdf00ab | ||
|
|
453538b405 | ||
|
|
743a15f35b | ||
|
|
f00ffad63d | ||
|
|
0d209ef05d | ||
|
|
e177f48e41 | ||
|
|
e53dafa47e | ||
|
|
7c93741670 | ||
|
|
43a2979e77 | ||
|
|
cb195da72f | ||
|
|
77aaf996a1 | ||
|
|
7feceeae87 | ||
|
|
1eeb7d5cf9 | ||
|
|
4a0414274f | ||
|
|
1a12b94bd3 | ||
|
|
12a8fb0581 | ||
|
|
20aad66e48 | ||
|
|
1cd26ae1b9 | ||
|
|
5516ac1a00 | ||
|
|
de09e675c1 | ||
|
|
f58257a208 | ||
|
|
abf0d29736 | ||
|
|
c5a2e92e5e | ||
|
|
edb9dcd284 | ||
|
|
cb0e6c5efc | ||
|
|
4e35b1e9c2 | ||
|
|
1becb64d83 | ||
|
|
c4dce8f506 | ||
|
|
7c221ef999 | ||
|
|
ec35d43677 | ||
|
|
a7958c0e3b | ||
|
|
546a4d7e46 | ||
|
|
834babe0ef | ||
|
|
8355f16809 | ||
|
|
db2414402f | ||
|
|
c7f80a3be4 | ||
|
|
b883a25c9a | ||
|
|
7fbded2b13 | ||
|
|
fb506acc27 | ||
|
|
a8d3a69013 | ||
|
|
30a2415ac8 | ||
|
|
b681ef9868 | ||
|
|
38efad5aa2 | ||
|
|
6de3be1384 | ||
|
|
781e55fce9 | ||
|
|
9b0de2e72e | ||
|
|
d4f7216256 | ||
|
|
2842ae7fb5 | ||
|
|
75aa066d9c | ||
|
|
244aa93b3a | ||
|
|
6177376e50 | ||
|
|
b5f6a237cc | ||
|
|
d741dfe26d | ||
|
|
5168e54af7 | ||
|
|
05755f3a52 | ||
|
|
dc77286282 | ||
|
|
222cd8c8f8 | ||
|
|
2f92f2ac5f | ||
|
|
a70f5aafc2 | ||
|
|
adfb96b637 | ||
|
|
383746fcee | ||
|
|
460ecdf8e9 | ||
|
|
74c503a33d | ||
|
|
f0d25515e6 | ||
|
|
9dc7502e4f | ||
|
|
078e213890 | ||
|
|
b1ff13d3e8 | ||
|
|
f5aca75798 | ||
|
|
99d247e254 | ||
|
|
d1d312f396 | ||
|
|
ba299aa71f | ||
|
|
92f30d4d70 | ||
|
|
93cccd4027 | ||
|
|
8e7e231aec | ||
|
|
72d77eb6c0 | ||
|
|
42ac242927 | ||
|
|
d0551353f3 | ||
|
|
1417f9f6cd | ||
|
|
978d66e148 | ||
|
|
4fd69154a3 | ||
|
|
22ce67c5e5 | ||
|
|
84ad0056e4 | ||
|
|
07d5e80c57 | ||
|
|
c6241af64a | ||
|
|
4f6eea8799 | ||
|
|
a207289955 | ||
|
|
3f2abe011b | ||
|
|
afe8a618fe | ||
|
|
b2e6c93b4b | ||
|
|
c3d2437c3a | ||
|
|
e2552dae45 | ||
|
|
1189bdec87 | ||
|
|
f51f9621d1 | ||
|
|
19eba3cc14 | ||
|
|
5b8b58b6d9 | ||
|
|
d1f643ebd9 | ||
|
|
e96712b020 | ||
|
|
6102900060 | ||
|
|
6f986af0d4 | ||
|
|
dd9b1a1065 | ||
|
|
ae135f5203 | ||
|
|
c42bc6914e | ||
|
|
0cddd8c167 | ||
|
|
22cd0edfc9 | ||
|
|
600966ac26 | ||
|
|
2b21fd2eda | ||
|
|
90a8c26b25 | ||
|
|
7145791f62 | ||
|
|
d9a4b4241e | ||
|
|
f219e1ee76 | ||
|
|
c0d9c81393 | ||
|
|
7bcdccc645 | ||
|
|
44ca1fc77e | ||
|
|
a16a5ea81a | ||
|
|
ca72f3c3a1 | ||
|
|
d447cc3f19 | ||
|
|
6be3ff6141 | ||
|
|
36565bbbd2 | ||
|
|
755acd616c | ||
|
|
7ff1b1795e | ||
|
|
7ece04e996 | ||
|
|
d4d3571c96 | ||
|
|
364ddef56b | ||
|
|
3908c05d14 | ||
|
|
6059ce2ac4 | ||
|
|
659ba317c1 | ||
|
|
7e7a016df3 | ||
|
|
5041c8058d | ||
|
|
a66e804904 | ||
|
|
d6d571779d | ||
|
|
8a0689328b | ||
|
|
2a0c99b5d8 | ||
|
|
6e3e95a721 | ||
|
|
a4d242680b | ||
|
|
ee2e939d13 | ||
|
|
788a63ca2f | ||
|
|
dea98467c0 | ||
|
|
4e6ec14223 | ||
|
|
d6b7d532ed | ||
|
|
46f7bba90d | ||
|
|
02f1c8482a | ||
|
|
4de2ccea59 | ||
|
|
0bf5fab9c0 | ||
|
|
e97c48051e | ||
|
|
307f39cee3 | ||
|
|
f346015d8c | ||
|
|
a2f8adbb5c | ||
|
|
82510a04af | ||
|
|
5eda349bbd | ||
|
|
8d99c33472 | ||
|
|
5fdaa9aa36 | ||
|
|
d8a12fe56d | ||
|
|
c79378f380 | ||
|
|
5e78171d3e | ||
|
|
d82b0faca1 | ||
|
|
26f3fb157f | ||
|
|
7c66bcc857 | ||
|
|
1dd5d7ad1a | ||
|
|
667835f2a0 | ||
|
|
427e0cd46d | ||
|
|
92aa4927db | ||
|
|
5c68f87114 | ||
|
|
ede8da7677 | ||
|
|
b44231a6b8 | ||
|
|
ae3884386d | ||
|
|
6cc8d602fc | ||
|
|
e2c1b3b931 | ||
|
|
59e99caf8a | ||
|
|
127b685104 | ||
|
|
5af361ab1c | ||
|
|
06727c3892 | ||
|
|
a452f0b4bd | ||
|
|
9ee714d048 | ||
|
|
76eb49c355 | ||
|
|
91878fccaf | ||
|
|
44aaec86a1 | ||
|
|
f815ce2901 | ||
|
|
105756eb27 | ||
|
|
7a2f8d691c | ||
|
|
75659485ee | ||
|
|
18cb66f8c7 | ||
|
|
45c3592818 | ||
|
|
2789801668 | ||
|
|
e09f42791a | ||
|
|
8d19ad306e | ||
|
|
793c1e5587 | ||
|
|
1a86c2c52d | ||
|
|
15cbe131fb | ||
|
|
082cdcc358 | ||
|
|
1936142042 | ||
|
|
8e4afa88f7 | ||
|
|
794de91d05 | ||
|
|
16bd9bc61e | ||
|
|
c0d3584626 | ||
|
|
e0d3e33c32 | ||
|
|
e37c109f0c | ||
|
|
efd8bab615 | ||
|
|
603463926e | ||
|
|
31bbb47162 | ||
|
|
62b52911fa | ||
|
|
ac96612a17 | ||
|
|
3913701f7f | ||
|
|
dcf66e7380 | ||
|
|
188034650b | ||
|
|
e01b5565a2 | ||
|
|
8bc98fedbf | ||
|
|
9a406f5998 | ||
|
|
f0e9751f7e | ||
|
|
0aa6c5eae8 | ||
|
|
785fc4ac3c | ||
|
|
3eab444c03 | ||
|
|
b28aff04a7 | ||
|
|
81fd454ef4 | ||
|
|
65c923e07a | ||
|
|
45dd77ad6d | ||
|
|
51c2a104b2 | ||
|
|
59ffb0a4c4 | ||
|
|
38341fffbd | ||
|
|
6633e65ee6 | ||
|
|
745de72d7e | ||
|
|
af95e5b3e0 | ||
|
|
270ca697b2 | ||
|
|
02ac79e577 | ||
|
|
031558afe4 | ||
|
|
eaf252f46d | ||
|
|
bf043f411b | ||
|
|
7ec5cac56b | ||
|
|
50d6e057d5 | ||
|
|
8adb9f4ece | ||
|
|
c145658206 | ||
|
|
8cfac5a25a | ||
|
|
1e8fc5011b | ||
|
|
42dfc778f8 | ||
|
|
90b11dd02e | ||
|
|
44b5b1b6ed | ||
|
|
c2523796c0 | ||
|
|
125f34ef47 | ||
|
|
5a361f7845 | ||
|
|
52e0b59548 | ||
|
|
b42299a5aa | ||
|
|
f9c77acd96 | ||
|
|
9ec544817f | ||
|
|
606a8f9db5 | ||
|
|
6995cd71d9 | ||
|
|
d9165646c6 | ||
|
|
720137304b | ||
|
|
8026d8ddb3 | ||
|
|
7876ccb3bc | ||
|
|
f22389a824 | ||
|
|
719f30219b | ||
|
|
cfa409b5e7 | ||
|
|
1b30c9dbca | ||
|
|
09c9094a6b | ||
|
|
aab51c331f | ||
|
|
a6d57496c2 | ||
|
|
f285d5dbf7 | ||
|
|
79fde26f4f | ||
|
|
a729ee6fca | ||
|
|
451a3773c3 | ||
|
|
38ade8fbc9 | ||
|
|
c229570bd9 | ||
|
|
ce14f10297 | ||
|
|
5430c49833 | ||
|
|
510b977cea | ||
|
|
7a966d8c1b | ||
|
|
bdf7fb0858 | ||
|
|
a80da8b65c | ||
|
|
22983bcdd3 | ||
|
|
4d4acc72f0 | ||
|
|
d7d8d3411c | ||
|
|
006097bee2 | ||
|
|
4b23d63d39 | ||
|
|
d8053f64ef | ||
|
|
4cbdcb2659 | ||
|
|
5583cea936 | ||
|
|
cc1fd3d03e | ||
|
|
d72eb009e4 | ||
|
|
738ffde962 | ||
|
|
34f15a4976 | ||
|
|
c807c7bd39 | ||
|
|
1081231b7c | ||
|
|
fc50b846c4 | ||
|
|
eead5f44fc | ||
|
|
fc0280a6ab | ||
|
|
65f5222a2a | ||
|
|
54b5d4d389 | ||
|
|
ee36c8ba9c | ||
|
|
e40d2eec9e | ||
|
|
15ef1fa1c2 | ||
|
|
291ee123c9 | ||
|
|
1b7009f4d5 | ||
|
|
9c3ee234f1 | ||
|
|
e0fcb040ee | ||
|
|
fc2b0e0fee | ||
|
|
44bc0971ad | ||
|
|
8717c4c287 | ||
|
|
cf542f6fdf | ||
|
|
94e4a2431b | ||
|
|
3eb4c9eae8 | ||
|
|
5ecabc5fe2 | ||
|
|
0838d48ee3 | ||
|
|
c64f8818be | ||
|
|
97ffd84d0e | ||
|
|
f2114f09f7 | ||
|
|
9c844850e4 | ||
|
|
68aef2ef0d | ||
|
|
88d644a7e9 | ||
|
|
4b97d4f7f5 | ||
|
|
bc14c633ae | ||
|
|
a29e5d39ca | ||
|
|
f1506ee500 | ||
|
|
6e346de9fb | ||
|
|
99ab2a4d62 | ||
|
|
d4ed7c3cfc | ||
|
|
bc0554575a | ||
|
|
1f4906244b | ||
|
|
52756ab83e | ||
|
|
97dcbe6932 | ||
|
|
e35bf22dd3 | ||
|
|
a36b1b9cec | ||
|
|
1920ee38c3 | ||
|
|
ec2110e58f | ||
|
|
12a1cd6f62 | ||
|
|
9af056e746 | ||
|
|
c8fe450623 | ||
|
|
ab1fe742f3 | ||
|
|
8b72c86ba5 | ||
|
|
28c5f4a635 | ||
|
|
74f69a21cd | ||
|
|
e23dacd6d4 | ||
|
|
58d582941b | ||
|
|
3bbc51949c | ||
|
|
69e0254a99 | ||
|
|
1091a914bd | ||
|
|
ecc65a218e | ||
|
|
426ed7eff6 | ||
|
|
73aba36309 | ||
|
|
cb393ccd3a | ||
|
|
347fcf9f67 | ||
|
|
fce7575b03 | ||
|
|
2da7ddc399 | ||
|
|
1c1be683ab | ||
|
|
4be1050234 | ||
|
|
2efb3533ec | ||
|
|
aa6c7e4b94 | ||
|
|
63c50d13ee | ||
|
|
c1e127e42f | ||
|
|
9e38e8a4db | ||
|
|
b4c95d6b0b | ||
|
|
c4766e2611 | ||
|
|
796097e3ab | ||
|
|
c7d9efebf9 | ||
|
|
8f4306d321 | ||
|
|
435f086cb7 | ||
|
|
01c9158120 | ||
|
|
e235d77d64 | ||
|
|
dbe8131b75 | ||
|
|
0a9d76515e | ||
|
|
0ce1af9ee0 | ||
|
|
c4452d2698 | ||
|
|
491888f6c0 | ||
|
|
e4158dc5e4 | ||
|
|
0307ca8ac6 | ||
|
|
156a273351 | ||
|
|
d6d51a2f8b | ||
|
|
a98b41d657 | ||
|
|
87ec78fbaa | ||
|
|
957bff4b89 | ||
|
|
321f7b59d8 | ||
|
|
41a9316523 | ||
|
|
1072ff5950 | ||
|
|
983f6fff5d | ||
|
|
b3627fcb18 | ||
|
|
99d7338c29 | ||
|
|
9cf930454d | ||
|
|
4b4962e8c6 | ||
|
|
f2afa77114 | ||
|
|
3aa647c89b | ||
|
|
45ab4dc718 | ||
|
|
d1850e8fd2 | ||
|
|
f1d516cf2a | ||
|
|
d55282b53c | ||
|
|
ef9f7af0c5 | ||
|
|
8823887bb4 | ||
|
|
593980e45a | ||
|
|
081dc16312 | ||
|
|
35599af04b | ||
|
|
a74b35379e | ||
|
|
7d16c9f68d | ||
|
|
890759cc5f | ||
|
|
e710e2cc5d | ||
|
|
d787faece4 | ||
|
|
9702109ea9 | ||
|
|
e547829505 | ||
|
|
a664a26062 | ||
|
|
fa105a8a93 | ||
|
|
3a0c7a8c36 | ||
|
|
13f4b376e8 | ||
|
|
5a08409a27 | ||
|
|
9bbdac3c2e | ||
|
|
a990ffe53d | ||
|
|
3a4b347d50 | ||
|
|
b80e1e4a43 | ||
|
|
fd71dfda6a | ||
|
|
fdbcbd395d | ||
|
|
dba964b559 | ||
|
|
8e0816a09d | ||
|
|
405b79f86c | ||
|
|
620e6955e5 | ||
|
|
a4997dd54d | ||
|
|
3efa9ac8c3 | ||
|
|
fdd52d74e9 | ||
|
|
ac81dea3ec | ||
|
|
549c37ef87 | ||
|
|
6a369ee31c | ||
|
|
2e573d37ae | ||
|
|
394afe2633 | ||
|
|
99ed3001f0 | ||
|
|
9e4cab2af9 | ||
|
|
33b6927b79 | ||
|
|
852a176e1f | ||
|
|
7511249514 | ||
|
|
3429cdd8af | ||
|
|
a1cd8eafd8 | ||
|
|
fbfb4ba9c4 | ||
|
|
ba9ba63792 | ||
|
|
460b89ce51 | ||
|
|
a4ec6e5257 | ||
|
|
44aa2ee3b3 | ||
|
|
80b417c4ab | ||
|
|
6d90c781c9 | ||
|
|
c51f04eca8 | ||
|
|
dda2004753 | ||
|
|
297f9eccea | ||
|
|
d2f2cba6d8 | ||
|
|
172d71435a | ||
|
|
bb1aec8a7e | ||
|
|
476d9f5e70 | ||
|
|
99014ad38d | ||
|
|
403456d3dc | ||
|
|
6335878317 | ||
|
|
6bff658af0 | ||
|
|
b111e7bd12 | ||
|
|
3e5ee2332a | ||
|
|
66f6998c86 | ||
|
|
f2a8f8ad8f | ||
|
|
540f6ecfdb | ||
|
|
8ec89f1bbd | ||
|
|
d33906b6e4 | ||
|
|
bb79fa1dc3 | ||
|
|
376a6182eb | ||
|
|
81de61d8db | ||
|
|
d2061ec898 | ||
|
|
077efbd2e7 | ||
|
|
8ce1782380 | ||
|
|
c2f20465ab | ||
|
|
fb0e43989d | ||
|
|
754248395c | ||
|
|
6e975ca155 | ||
|
|
79a2bc404e | ||
|
|
42a26e1741 | ||
|
|
695711e124 | ||
|
|
0d5811e502 | ||
|
|
b9d070f76b | ||
|
|
122c3f083e | ||
|
|
5d22cf4327 | ||
|
|
219d3ad193 | ||
|
|
e72157e26a | ||
|
|
50a377a7c4 | ||
|
|
9d7ddff60c | ||
|
|
081d878f86 | ||
|
|
d8dc091267 | ||
|
|
1c44d8049a | ||
|
|
a95191d29e | ||
|
|
111f6e7f18 | ||
|
|
4a5c1e9ec4 | ||
|
|
8f0893b5f7 | ||
|
|
b16e705a6c | ||
|
|
3cad318b70 | ||
|
|
8c6002cae6 | ||
|
|
0355bbaf3b | ||
|
|
2ba083a650 | ||
|
|
c79ea5a257 | ||
|
|
44706f4957 | ||
|
|
a1b3bb03ed | ||
|
|
76caa16909 | ||
|
|
160b788198 | ||
|
|
eada62f62c | ||
|
|
bd9419e6db | ||
|
|
bdd9de3001 | ||
|
|
200ba4ed04 | ||
|
|
1e8939dd58 | ||
|
|
f45dd11e53 | ||
|
|
1a0cc1d64d | ||
|
|
421cb522d9 | ||
|
|
1b18b041d6 | ||
|
|
8788703ac6 | ||
|
|
b6c25e3ad9 | ||
|
|
73eaa68cd1 | ||
|
|
beb927f7b4 | ||
|
|
cdc969cd4e | ||
|
|
2a67499f12 | ||
|
|
6a3cc79daa | ||
|
|
97d4a947ee | ||
|
|
e0e47ad9a0 | ||
|
|
b08eac58e9 | ||
|
|
11409ccf21 | ||
|
|
e3b6c97c3b | ||
|
|
d3da086ebf | ||
|
|
3507fa40f1 | ||
|
|
6f8f1f1409 | ||
|
|
c2148a359d | ||
|
|
c172185a24 | ||
|
|
1140a5c4ae | ||
|
|
3cc378c960 | ||
|
|
9b3a961303 | ||
|
|
d048555149 | ||
|
|
7533858a52 | ||
|
|
c4e10ef0aa | ||
|
|
c20842e7cd | ||
|
|
6cfdb21313 | ||
|
|
e396f4d06f | ||
|
|
47c1bb6a5b | ||
|
|
adfb0b513e | ||
|
|
98d78b9d8a | ||
|
|
a1c32a56ea | ||
|
|
6584bcf87f | ||
|
|
7ac75af622 | ||
|
|
b3c283b282 | ||
|
|
c2615dd746 | ||
|
|
789518f70d | ||
|
|
ad3008d855 | ||
|
|
3da426603f | ||
|
|
8d26e34b0a | ||
|
|
110d1d7245 | ||
|
|
f7384623df | ||
|
|
5d24e166ab | ||
|
|
d9ec5bcd24 | ||
|
|
bf9cd7625b | ||
|
|
afbf98c974 | ||
|
|
fedb68cde7 | ||
|
|
f787937a30 | ||
|
|
f54fef7e7b | ||
|
|
e36c77aaf3 | ||
|
|
de45e48c37 | ||
|
|
ef0ce8224e | ||
|
|
5b4d5387bf | ||
|
|
5c460b38c9 | ||
|
|
8b836ab446 | ||
|
|
1be1fccc76 | ||
|
|
2c1fda97f0 | ||
|
|
71f7b719b5 | ||
|
|
e39006b511 | ||
|
|
37d07d415a | ||
|
|
b80c8f78fc | ||
|
|
7707179b93 | ||
|
|
4e7d8bacdb | ||
|
|
0c46fa5a56 | ||
|
|
36aca00de3 | ||
|
|
0ec8cf1b53 | ||
|
|
4b2b713e59 | ||
|
|
cc0afce237 | ||
|
|
620f3fc919 | ||
|
|
af949ef0dd | ||
|
|
475c5dc19a | ||
|
|
71cfe2364a | ||
|
|
c1466f8aca | ||
|
|
7989f73f06 | ||
|
|
c9a582fbcc | ||
|
|
63aad1e501 | ||
|
|
16ba12239c | ||
|
|
b00ae9256b | ||
|
|
936045e01c | ||
|
|
b1570ab117 | ||
|
|
f066e3b1e0 | ||
|
|
1487c0e51b | ||
|
|
9f244b9bd9 | ||
|
|
374ec27ab5 | ||
|
|
b222c34f12 | ||
|
|
007a096632 | ||
|
|
26e02e3773 | ||
|
|
b8668ca3ce | ||
|
|
21a2f9f93b | ||
|
|
8a74141e4b | ||
|
|
752d9d5316 | ||
|
|
58c7cc5d05 | ||
|
|
a790fb7afe | ||
|
|
5836cb1728 | ||
|
|
19fd219409 | ||
|
|
43f99e0bf7 | ||
|
|
6f83f6c1b5 | ||
|
|
3f9c177d76 | ||
|
|
55dd7e20a0 | ||
|
|
bfbf29b78f | ||
|
|
1b6d421a5b | ||
|
|
112d9c4086 | ||
|
|
adb089dc78 | ||
|
|
5024d270ec | ||
|
|
f4d5abfc5b | ||
|
|
f55cf3ab8d | ||
|
|
d35141a369 | ||
|
|
d450444596 | ||
|
|
7e11815409 | ||
|
|
017cd4cd6c | ||
|
|
ac6c2ff769 | ||
|
|
c4e8ca4b32 | ||
|
|
c9aec2f281 | ||
|
|
591561f657 | ||
|
|
2ad4054133 | ||
|
|
7883977a56 | ||
|
|
d5cb842db2 | ||
|
|
03bbb0571e | ||
|
|
1d355d74ef | ||
|
|
7f9913590e | ||
|
|
9e1d4e7855 | ||
|
|
a1f9b584dc | ||
|
|
7d474db765 | ||
|
|
367c0b38a6 | ||
|
|
cacd57f72b | ||
|
|
22dfc1e265 | ||
|
|
bffb6e1a07 | ||
|
|
cdff0c60d9 | ||
|
|
f55eb3cba9 | ||
|
|
6b8d4dd101 | ||
|
|
d05e130250 | ||
|
|
d33e50f367 | ||
|
|
f3c9c53b6c | ||
|
|
7d5b9c78b1 | ||
|
|
e6a4b7bbba | ||
|
|
ad0b269d53 | ||
|
|
5472570958 | ||
|
|
4576ba4db0 | ||
|
|
efcfab0955 | ||
|
|
1acd59c7d6 | ||
|
|
4951a2bf7a | ||
|
|
95fc26d4ad | ||
|
|
b65935d6cf | ||
|
|
2155fdd756 | ||
|
|
f2abc13ce2 | ||
|
|
0f4621fb02 | ||
|
|
c6ff641f6d | ||
|
|
350f74a53d | ||
|
|
41cd7acc87 | ||
|
|
c6eea26660 | ||
|
|
61c5718663 | ||
|
|
9897f4b527 | ||
|
|
978a6e5ecb | ||
|
|
a018997ddc | ||
|
|
de09843467 | ||
|
|
78a57fdb4b | ||
|
|
0bc2fd72f0 | ||
|
|
dda5164efd | ||
|
|
3df2396b63 | ||
|
|
d3da84e724 | ||
|
|
eb61015477 | ||
|
|
40c644f006 | ||
|
|
c9aa0180a8 | ||
|
|
a06e46885d | ||
|
|
60fa6e6c0a | ||
|
|
2f18f7927d | ||
|
|
292cf75836 | ||
|
|
fc95061f4c | ||
|
|
1f1275255c | ||
|
|
d8555e5a5d | ||
|
|
b323531dd5 | ||
|
|
cfb665310e | ||
|
|
51c6ebcd4d | ||
|
|
e94d1b6b9f | ||
|
|
ca7b32105d | ||
|
|
264db2737b | ||
|
|
5f2c9a6e45 | ||
|
|
19be1f1bf0 | ||
|
|
7cdf0000d9 | ||
|
|
13606e5e00 | ||
|
|
35af240faa | ||
|
|
0ac56f8973 | ||
|
|
6e5f8b1fb0 | ||
|
|
15e831c0b0 | ||
|
|
248952bc8f | ||
|
|
2373743eac | ||
|
|
f119596be6 | ||
|
|
b7cb41b388 | ||
|
|
a65ee26446 | ||
|
|
d3e2fbf1e2 | ||
|
|
66748ab5e5 | ||
|
|
c73a2c8f84 | ||
|
|
4bbcd99b8b | ||
|
|
02e7ff27c7 | ||
|
|
7ed3cea40b | ||
|
|
74f5cf8f29 | ||
|
|
086d13ca2f | ||
|
|
2780e96179 | ||
|
|
191678f9d6 | ||
|
|
79f595d8d1 | ||
|
|
db2865fb17 | ||
|
|
f945fa60d9 | ||
|
|
454988f657 | ||
|
|
7e0346d6eb | ||
|
|
00a90d1fe6 | ||
|
|
d6c185580a | ||
|
|
fd9132c15d | ||
|
|
42702e81b3 | ||
|
|
09c9d55695 | ||
|
|
69e9effc88 | ||
|
|
1c782c599f | ||
|
|
ed37071fd6 | ||
|
|
d73cf106b1 | ||
|
|
1d7982e80a | ||
|
|
d5d1984116 | ||
|
|
9eda1629bb | ||
|
|
9a5d49774e | ||
|
|
b2efebce96 | ||
|
|
85232bd704 | ||
|
|
df4e3aea79 | ||
|
|
290d45fd05 | ||
|
|
168e8c925c | ||
|
|
d9859b18fe | ||
|
|
784847f35b | ||
|
|
97287377d1 | ||
|
|
a15b66e003 | ||
|
|
a441b4b90d | ||
|
|
0dcc1390a6 | ||
|
|
01c86636e9 | ||
|
|
846c27d579 | ||
|
|
db05059b42 | ||
|
|
b824328850 | ||
|
|
a8767a2b1a | ||
|
|
5e14e7fb70 | ||
|
|
fbaa7be52e | ||
|
|
b6016b244e | ||
|
|
e339a64261 | ||
|
|
17e18442ab | ||
|
|
e8aa3a17a6 | ||
|
|
bdb97eab86 | ||
|
|
690000254c | ||
|
|
6a0b778978 | ||
|
|
549d141053 | ||
|
|
c31ecdb8de | ||
|
|
8a09d044c7 | ||
|
|
a3b5b89930 | ||
|
|
ad6f100f6a | ||
|
|
3cfe21af58 | ||
|
|
b70a660975 | ||
|
|
04c1d1389f | ||
|
|
f12156bf81 | ||
|
|
0177ac660b | ||
|
|
361b9b4ce4 | ||
|
|
78792bd11c | ||
|
|
8b38ddfcd9 | ||
|
|
78ddf50d2d | ||
|
|
93dcb20e12 | ||
|
|
41a71e1dee | ||
|
|
a5ed8ad58c | ||
|
|
e45ed85b55 | ||
|
|
52474f9103 | ||
|
|
c2587da27d | ||
|
|
26036877b2 | ||
|
|
906cdd9050 | ||
|
|
762662d056 | ||
|
|
1de4b38766 | ||
|
|
6c73ab823b | ||
|
|
5ef1651151 | ||
|
|
8d695bc8d7 | ||
|
|
c892d055ed | ||
|
|
b327e54be1 | ||
|
|
989045489c | ||
|
|
888338c60e | ||
|
|
e6c6cc7811 | ||
|
|
fa0e72bd69 | ||
|
|
18decac44d | ||
|
|
1012a0cf2b | ||
|
|
7e4de945cf | ||
|
|
a468272726 | ||
|
|
039d8f000d | ||
|
|
2dc181c75e | ||
|
|
d35f960a8a | ||
|
|
634f8ed574 | ||
|
|
d369451308 | ||
|
|
8f1202424d | ||
|
|
0a6833e9d8 | ||
|
|
8f80fc4e2c | ||
|
|
50e5813222 | ||
|
|
7bc268aeaa | ||
|
|
537b5b1e25 | ||
|
|
aae38f8ce7 | ||
|
|
ad05432bcf | ||
|
|
8aa983257d | ||
|
|
046a97d1e5 | ||
|
|
d28649b13d | ||
|
|
3e16ca37bc | ||
|
|
2da38a5bdc | ||
|
|
bbe1d8b52e | ||
|
|
97c85e39c3 | ||
|
|
a7b59e5b12 | ||
|
|
9eb1252ce9 | ||
|
|
0e01e13670 | ||
|
|
239e61e718 | ||
|
|
22549e9fd8 | ||
|
|
1f9fd24064 | ||
|
|
a7594740e3 | ||
|
|
945c72cf6c | ||
|
|
824b0c0132 | ||
|
|
51e9f2f579 | ||
|
|
e8ec33d9d0 | ||
|
|
3bbbaf12fd | ||
|
|
30ffacd879 | ||
|
|
75e9b7791c | ||
|
|
4b665ab19a | ||
|
|
08265ed1d7 | ||
|
|
cded9af90f | ||
|
|
4e1f2ad017 | ||
|
|
7f92b7072d | ||
|
|
4a589ba6a4 | ||
|
|
7f16325fcc | ||
|
|
b62e5bf34c | ||
|
|
bd6b348cc7 | ||
|
|
62f35fe8c8 | ||
|
|
cb2cb4659c | ||
|
|
a2c58415cf | ||
|
|
2cb9987c99 | ||
|
|
36584cfb7c | ||
|
|
b825ad6a12 | ||
|
|
f8545d4c61 | ||
|
|
9b42ef5d46 | ||
|
|
05ddfc0495 | ||
|
|
53b2cebb66 | ||
|
|
58c69e36a1 | ||
|
|
837fb71a24 | ||
|
|
2e13cf5f74 | ||
|
|
0ae1681d9c | ||
|
|
ebb66ba8fb | ||
|
|
e79354a039 | ||
|
|
a57beb1de4 | ||
|
|
1648c44ee2 | ||
|
|
efe47a149e | ||
|
|
2d66a2f0f3 | ||
|
|
43a1f1314e | ||
|
|
4f4b282d7c | ||
|
|
d3d4da18e5 | ||
|
|
b8da583986 | ||
|
|
73f6b42715 | ||
|
|
0e2a4efdaa | ||
|
|
6798e16aaf | ||
|
|
c9cc64ecfc | ||
|
|
761f9045ac | ||
|
|
dfae979287 | ||
|
|
fe917affd2 | ||
|
|
d44207dd7f | ||
|
|
ec8b1403bd | ||
|
|
6f3d108c1e | ||
|
|
c34ee9c1f9 | ||
|
|
2a3f049336 | ||
|
|
0c91011e88 | ||
|
|
8bcd8719aa | ||
|
|
29a8af509b | ||
|
|
d3cd9f17f9 | ||
|
|
b9aea8c5ec | ||
|
|
897619a961 | ||
|
|
e6c4706b73 | ||
|
|
8994c50d34 | ||
|
|
55b62e47eb | ||
|
|
c6ecf70377 | ||
|
|
f0cd7d27fb | ||
|
|
f923bb499b | ||
|
|
aa3a29fed2 | ||
|
|
47d3011c85 | ||
|
|
cec713a47a | ||
|
|
bf6d0c0a74 | ||
|
|
c11672fca3 | ||
|
|
e086b654aa | ||
|
|
1107f691ea | ||
|
|
b095ca5756 | ||
|
|
4afc0e8ed0 | ||
|
|
141b377b4e | ||
|
|
402a478785 | ||
|
|
73680584f3 | ||
|
|
45dbbcd179 | ||
|
|
83d25bfa00 | ||
|
|
299e27af15 | ||
|
|
ec4cd5ed48 | ||
|
|
59d2733b88 | ||
|
|
cbdd088188 | ||
|
|
2d52485d7b | ||
|
|
d830178ef8 | ||
|
|
049984b4cc | ||
|
|
d261a986ab | ||
|
|
9b2e25735b | ||
|
|
e09e75b0ba | ||
|
|
6630113fef | ||
|
|
b2f08c9c20 | ||
|
|
6a4315b7e7 | ||
|
|
8b3e62ff6d | ||
|
|
f1d3f6740d | ||
|
|
9ccd1d920c | ||
|
|
9674d75ff6 | ||
|
|
22fd74846d | ||
|
|
777645888a | ||
|
|
ac8e344173 | ||
|
|
16fad60833 | ||
|
|
cb96a39b46 | ||
|
|
a540634b5b | ||
|
|
e15576bc47 | ||
|
|
95359760ae | ||
|
|
be209cb7b6 | ||
|
|
f5eb80759b | ||
|
|
9f125502f8 | ||
|
|
3f856c4b1c | ||
|
|
f55fb1e3a5 | ||
|
|
bf88bd5da5 | ||
|
|
b7112e02db | ||
|
|
347c796662 | ||
|
|
9bed7f7a9b | ||
|
|
b136166fc9 | ||
|
|
75727c3d68 | ||
|
|
6c625b3359 | ||
|
|
60759a4e3b | ||
|
|
582a66bb2f | ||
|
|
d78f78bb5c | ||
|
|
71b7d062d5 | ||
|
|
c6138a0660 | ||
|
|
ce4ac97269 | ||
|
|
2088a86512 | ||
|
|
e296fe2b98 | ||
|
|
96b8890ecc | ||
|
|
db6fae2f5b | ||
|
|
6743cdbb65 | ||
|
|
71466c9a27 | ||
|
|
1bdf7e3192 | ||
|
|
7285f3c844 | ||
|
|
eb257d3aa7 | ||
|
|
87f11491d9 | ||
|
|
5735a02473 | ||
|
|
47dd9b5a03 | ||
|
|
7652d7889b | ||
|
|
dd2116c897 | ||
|
|
c5566b3e94 | ||
|
|
30cbf02bff | ||
|
|
9e4e9b4f1a | ||
|
|
6f290f28b6 | ||
|
|
6ff3c9015b | ||
|
|
e28b82b2b7 | ||
|
|
3edf124f96 | ||
|
|
fb72b46a3c | ||
|
|
49bf395f61 | ||
|
|
eab14b6c49 | ||
|
|
8b962fb8e8 | ||
|
|
e5a3c861cb | ||
|
|
c7bb3d63b0 | ||
|
|
e6b543c15e | ||
|
|
8137517d93 | ||
|
|
572f6a7fab | ||
|
|
c6d9201680 | ||
|
|
7dcb3af944 | ||
|
|
4bc183a8a1 | ||
|
|
9f83311931 | ||
|
|
10986d3a7c | ||
|
|
f4f6efa547 | ||
|
|
bf64259af3 | ||
|
|
6141ba84ce | ||
|
|
329902f0db | ||
|
|
bfcaa7a443 | ||
|
|
45915bf0ed | ||
|
|
ee7f2a541f | ||
|
|
59a00eae98 | ||
|
|
df8293bee6 | ||
|
|
935216f179 | ||
|
|
f56bbd46fd | ||
|
|
9f0f18c5c4 | ||
|
|
191c34c9c4 | ||
|
|
6a604b3002 | ||
|
|
5a435b533e | ||
|
|
4b027722b1 | ||
|
|
68ce8642b1 | ||
|
|
4913b6a0f1 | ||
|
|
aee0ab05f4 | ||
|
|
b44432f24a | ||
|
|
86be13ff1f | ||
|
|
ee95df0e57 | ||
|
|
442c29f020 | ||
|
|
739037fc37 | ||
|
|
f8252020aa | ||
|
|
38b87439fe | ||
|
|
116879f7ea | ||
|
|
2eafb2f067 | ||
|
|
c36f0f6f7f | ||
|
|
bfc033959b | ||
|
|
814f350b6d | ||
|
|
05db8ce582 | ||
|
|
3fd36a0c72 | ||
|
|
cbb12b29bd | ||
|
|
6ed30f1add | ||
|
|
a044c41c66 | ||
|
|
fb78e53a14 | ||
|
|
acfbbaa549 | ||
|
|
d52d74c64c | ||
|
|
d36f73de01 | ||
|
|
628c4a7b5f | ||
|
|
ca07a663e1 | ||
|
|
66d008391e | ||
|
|
eef84bda26 | ||
|
|
e0defe71aa | ||
|
|
069257151e | ||
|
|
3f80a3b39e | ||
|
|
b2a56161bb | ||
|
|
5e75639244 | ||
|
|
cb2cd3e10f | ||
|
|
0acb911d6a | ||
|
|
17ad7060b3 | ||
|
|
f38ba7fcd3 | ||
|
|
a3464068bd | ||
|
|
347ecc028f | ||
|
|
94ac60fa09 | ||
|
|
d567e23e50 | ||
|
|
8ff81562d2 | ||
|
|
7a8142ed92 | ||
|
|
eaba1b9cc8 | ||
|
|
b08b3546d2 | ||
|
|
7453e688fd | ||
|
|
32b097b3f2 | ||
|
|
22394def78 | ||
|
|
68ecb7fbdd | ||
|
|
de98a53b43 | ||
|
|
1c9fbf92c6 | ||
|
|
c068b05232 | ||
|
|
15338ecb18 | ||
|
|
01e9a8f720 | ||
|
|
4bdfe64afb | ||
|
|
b7b752b92f | ||
|
|
b7bcd204b4 | ||
|
|
ec934ba3c0 | ||
|
|
7373639f57 | ||
|
|
d718527a1f | ||
|
|
48add0f293 | ||
|
|
a4685229c9 | ||
|
|
f0bc4d26a0 | ||
|
|
1d3b93d88d | ||
|
|
62752ba7e1 | ||
|
|
6a4f420187 | ||
|
|
6640632683 | ||
|
|
09d5d802d0 | ||
|
|
fea23ed6d4 | ||
|
|
10a6c4dc7a | ||
|
|
4cdaa72224 | ||
|
|
27bc1ca5d1 | ||
|
|
1ea49188c9 | ||
|
|
3084ef129c | ||
|
|
c0d112f858 | ||
|
|
2265dda84c | ||
|
|
263b094cab | ||
|
|
fbd13614a5 | ||
|
|
9eab74b595 | ||
|
|
5acdb041a9 | ||
|
|
0494d7ebe3 | ||
|
|
9a8442c946 | ||
|
|
e1dcd0b441 | ||
|
|
a152db7054 | ||
|
|
b9e092674e | ||
|
|
4162b5f41d | ||
|
|
67ae6f210f | ||
|
|
f6c5a46626 | ||
|
|
d6f7e01c53 | ||
|
|
46463e4e24 | ||
|
|
bc99509395 | ||
|
|
5c420f3a34 | ||
|
|
393712ead2 | ||
|
|
d3060b0060 | ||
|
|
14d7f04a81 | ||
|
|
1a28e5e0d4 | ||
|
|
884cd0d636 | ||
|
|
6a7a3c0ae8 | ||
|
|
948e6bd57c | ||
|
|
78595fba0b | ||
|
|
8020284b12 | ||
|
|
d6a49da870 | ||
|
|
84da80356d | ||
|
|
bcbb85eac3 | ||
|
|
0e1d8a72e6 | ||
|
|
bbdd698869 | ||
|
|
bae1e1ee9f | ||
|
|
7138785500 | ||
|
|
8f684ffa6d | ||
|
|
9be3666fe7 | ||
|
|
b7785678f4 | ||
|
|
d8005b4cf6 | ||
|
|
52028fc3bc | ||
|
|
5285ec23ae | ||
|
|
3c882e5c57 | ||
|
|
1a33f9168b | ||
|
|
ccae3d7383 | ||
|
|
847651a90a | ||
|
|
1b8998e7a2 | ||
|
|
dc8fb79759 | ||
|
|
6b0935d6cf | ||
|
|
d1183ce272 | ||
|
|
a1aec8178a | ||
|
|
ad569a8a36 | ||
|
|
0d9fdbaac1 | ||
|
|
f5cd3eab9e | ||
|
|
cb6fe4bb59 | ||
|
|
db36bc67f1 | ||
|
|
e0f72a6193 | ||
|
|
1ee684b7c0 | ||
|
|
93005512b4 | ||
|
|
8987cd64a0 | ||
|
|
fac51dcf03 | ||
|
|
01101a4c9b | ||
|
|
d561e40817 | ||
|
|
b8094fd771 | ||
|
|
af5d9c952d | ||
|
|
ce4e187cbc | ||
|
|
821c80b61e | ||
|
|
5a6fb7c973 | ||
|
|
0cb298ebdf | ||
|
|
0f385f9f4e | ||
|
|
a149368725 | ||
|
|
afeefe8259 | ||
|
|
690d3c27a2 | ||
|
|
3d56ea5ce5 | ||
|
|
fdff7f80a3 | ||
|
|
fe6978b107 | ||
|
|
57db6865d2 | ||
|
|
d235d5ab28 | ||
|
|
c47c15ee47 | ||
|
|
613dfe06d3 | ||
|
|
6803ad2e59 | ||
|
|
d5a791b470 | ||
|
|
a312d61d68 | ||
|
|
e414c1f7b0 | ||
|
|
955359b073 | ||
|
|
26e0c0887a | ||
|
|
4c295b564a | ||
|
|
2eb52da0db | ||
|
|
d8bfb3ab13 | ||
|
|
d970e93507 | ||
|
|
e6255081a8 | ||
|
|
623db0ed94 | ||
|
|
0e575e9c25 | ||
|
|
fb23ba9878 | ||
|
|
4e09fc7f43 | ||
|
|
64cfdd815f | ||
|
|
f6f31e0a8d | ||
|
|
bd5fb9be03 | ||
|
|
762714de68 | ||
|
|
82a3651a18 | ||
|
|
dd9cdb0ec9 | ||
|
|
7f082a821d | ||
|
|
abe0352de9 | ||
|
|
4cee4aa5a8 | ||
|
|
9c68c7c50b | ||
|
|
0608782cfa | ||
|
|
edeaf3794a | ||
|
|
fe2b8c8afa | ||
|
|
b66bf58064 | ||
|
|
957dfa9cdf | ||
|
|
cc9264854e | ||
|
|
d1463b3e24 | ||
|
|
f1082520e1 | ||
|
|
733c563194 | ||
|
|
0200d043c3 | ||
|
|
9c475c36e7 | ||
|
|
c663c5c507 | ||
|
|
1e93c38307 | ||
|
|
81baf808c9 | ||
|
|
74537689dc | ||
|
|
12ab01d5e6 | ||
|
|
044d3a0ff9 | ||
|
|
659cae6a4c | ||
|
|
8efc38ad82 | ||
|
|
bd5882f0f0 | ||
|
|
6ff9ba9df9 | ||
|
|
b2df398a12 | ||
|
|
83d618e1eb | ||
|
|
f0768b3af1 | ||
|
|
0233ce52ed | ||
|
|
6e6f337509 | ||
|
|
1546415b8f | ||
|
|
20725c69bf | ||
|
|
90613220c6 | ||
|
|
659fd2ae93 | ||
|
|
29d899f7da | ||
|
|
902a0a01a9 | ||
|
|
8001fb3915 | ||
|
|
e81e2802f0 | ||
|
|
1ee066ec42 | ||
|
|
53d54d1c4a | ||
|
|
10082b60b8 | ||
|
|
c5b9773922 | ||
|
|
de11323d28 | ||
|
|
9f269e1a95 | ||
|
|
e4204168a0 | ||
|
|
9c350f8ef1 | ||
|
|
db19fdac29 | ||
|
|
d516b238b1 | ||
|
|
f9330f6cd9 | ||
|
|
360da29e1f | ||
|
|
9cfac1642a | ||
|
|
db90e87d10 | ||
|
|
b7564080bc | ||
|
|
1d783bf6c7 | ||
|
|
1025c2e3a1 | ||
|
|
4fd82ab222 | ||
|
|
8eadfc1bf6 | ||
|
|
f66edbad50 | ||
|
|
c7f17b5319 | ||
|
|
23c4adcef6 | ||
|
|
808542bed0 | ||
|
|
93bfd57856 | ||
|
|
7e7e1bccba | ||
|
|
34f6da86c3 | ||
|
|
15c0381c3c | ||
|
|
c2f4a57e02 | ||
|
|
f945cf2343 | ||
|
|
5bca3cfd71 | ||
|
|
26ce4e6886 | ||
|
|
f5f0e0c376 | ||
|
|
9dea1e7f3e | ||
|
|
c2e0f8c81f | ||
|
|
d341bc25ce | ||
|
|
0379e2b51b | ||
|
|
e79026b840 | ||
|
|
fc34d6b56f | ||
|
|
2a1571a99e | ||
|
|
c158608255 | ||
|
|
3ca590b185 | ||
|
|
3f8ee21849 | ||
|
|
845b88a193 | ||
|
|
e252972c7f | ||
|
|
a9012ebfc5 | ||
|
|
5cfd9bbbbd | ||
|
|
c82a7240bb | ||
|
|
a4a20d92a4 | ||
|
|
890996f595 | ||
|
|
474f27c6d3 | ||
|
|
33f3894372 | ||
|
|
24436ac76e | ||
|
|
3ee66ef705 | ||
|
|
a1765e1d33 | ||
|
|
765e3dbf72 | ||
|
|
80f5cee599 | ||
|
|
4dcb124693 | ||
|
|
31ecf167cc | ||
|
|
3999480d64 | ||
|
|
9dbb503c23 | ||
|
|
a98f803d87 | ||
|
|
9e9ffeb5d5 | ||
|
|
33d4ad4d84 | ||
|
|
d05d418c4c | ||
|
|
06d0af7a1d | ||
|
|
9a3b726068 | ||
|
|
2676ab9a59 | ||
|
|
a1837d553e | ||
|
|
fdbc130d8d | ||
|
|
4b3cea3812 | ||
|
|
1c3082ffa6 | ||
|
|
0446cfdba0 | ||
|
|
db1d3183b6 | ||
|
|
fb666394fc | ||
|
|
1054c89a9d | ||
|
|
8dd87dc482 | ||
|
|
b2edbf05a1 | ||
|
|
6fb53a406b | ||
|
|
b05fa0821d | ||
|
|
0a808b1212 | ||
|
|
f1d83e92a7 | ||
|
|
31b60f7f60 | ||
|
|
c0f9af5daa | ||
|
|
b25a9e8884 | ||
|
|
3c0cf3cd55 | ||
|
|
1ac6f17e6a | ||
|
|
399a2b38f3 | ||
|
|
b97221cdb2 | ||
|
|
0164bc21ea | ||
|
|
5a23250d32 | ||
|
|
80d88d9789 | ||
|
|
31ead854c7 | ||
|
|
4b64fcb8a4 | ||
|
|
a951f2403d | ||
|
|
f9adeba7f1 | ||
|
|
5c823d51d0 | ||
|
|
9be7521b83 | ||
|
|
c73ddc3552 | ||
|
|
4b7f058f41 | ||
|
|
07221a1b20 | ||
|
|
13614fb3c4 | ||
|
|
4fa983bde7 | ||
|
|
9cb1db8c0a | ||
|
|
5738436d55 | ||
|
|
5e49b38c33 | ||
|
|
0c94adaff9 | ||
|
|
f8a6c5d06c | ||
|
|
21e66c7c02 | ||
|
|
902f0d3ac4 | ||
|
|
713ecd35f6 | ||
|
|
27b35157cd | ||
|
|
f8fb639870 | ||
|
|
14f41ae619 | ||
|
|
a026d72924 | ||
|
|
2cb070f5b3 | ||
|
|
1dec956e99 | ||
|
|
310394aa60 | ||
|
|
468ff18243 | ||
|
|
44a63580f0 | ||
|
|
4ac1fa43aa | ||
|
|
6f992a3cf7 | ||
|
|
fd4ce656d5 | ||
|
|
9ed2dca427 | ||
|
|
dfb804fe3f | ||
|
|
4f2a84b426 | ||
|
|
14a127b6b3 | ||
|
|
06000533fb | ||
|
|
7722aba403 | ||
|
|
4817d8c67f | ||
|
|
9a062d90d1 | ||
|
|
959eb45373 | ||
|
|
a42f2af9eb | ||
|
|
4ddad68212 | ||
|
|
aac6c5a1c7 | ||
|
|
5572e31fd4 | ||
|
|
233b8bf81a | ||
|
|
2ae3810f80 | ||
|
|
736165876c | ||
|
|
61b3fca9a3 | ||
|
|
469863b7b3 | ||
|
|
5238bc55fd | ||
|
|
57a01aa6ff | ||
|
|
9361dbc39e | ||
|
|
11d257cb26 | ||
|
|
a928ab75e3 | ||
|
|
55a240c82e | ||
|
|
f8aedf438b | ||
|
|
9f1bb9a42e | ||
|
|
0ed7274610 | ||
|
|
df032b09a7 | ||
|
|
7dba742e4c | ||
|
|
83dbe78965 | ||
|
|
95b75c5330 | ||
|
|
780bd08490 | ||
|
|
a9b1f38a7c | ||
|
|
4ed4ad9852 | ||
|
|
81f172315c | ||
|
|
54666221a4 | ||
|
|
5327d702f8 | ||
|
|
a701ea3007 | ||
|
|
f0fb7d9661 | ||
|
|
3cbc89769d | ||
|
|
cb95200be3 | ||
|
|
a52f6c0acf | ||
|
|
77f9e3dd41 | ||
|
|
259d3e2df1 | ||
|
|
6eee5421b0 | ||
|
|
8004e9c943 | ||
|
|
2ab8511f45 | ||
|
|
7514ff53c9 | ||
|
|
309cfb1499 | ||
|
|
a567f7ed20 | ||
|
|
5720936247 | ||
|
|
5d9de14ca3 | ||
|
|
f519f56078 | ||
|
|
5eb1a1f7f5 | ||
|
|
5a28560177 | ||
|
|
db280adf55 | ||
|
|
b77fcd6c8a | ||
|
|
b5b2649283 | ||
|
|
318f9b216d | ||
|
|
61247a0b2a | ||
|
|
849a418273 | ||
|
|
2e63a62e08 | ||
|
|
6ccf1f2a3c | ||
|
|
e298256b82 | ||
|
|
787e5b2e29 | ||
|
|
4aa1e8b093 | ||
|
|
9ee224c36b | ||
|
|
08263c0597 | ||
|
|
347fe87229 | ||
|
|
b65a0a3a8d | ||
|
|
9a5a1e2253 | ||
|
|
687b4ec837 | ||
|
|
8bdf5c554d | ||
|
|
f4a18e531f | ||
|
|
df951a0c7c | ||
|
|
a6cac2691b | ||
|
|
a9f5179066 | ||
|
|
687e2699cf | ||
|
|
491da0ceb9 | ||
|
|
1bac40bc58 | ||
|
|
fb9061480d | ||
|
|
a04cf100b4 | ||
|
|
76253bf516 | ||
|
|
feaf70922d | ||
|
|
c70343a5bc | ||
|
|
550c116aea | ||
|
|
a5f31a4280 | ||
|
|
27fc4c4ca8 | ||
|
|
90a5f17f58 | ||
|
|
108cb91d95 | ||
|
|
00a0755ff3 | ||
|
|
3f7e8c88eb | ||
|
|
1c7ca94d49 | ||
|
|
31273cd6ff | ||
|
|
14e39dd745 | ||
|
|
cc6f7b6088 | ||
|
|
da1b0c9558 | ||
|
|
9f294b4d10 | ||
|
|
13f60bae41 | ||
|
|
67105b332f | ||
|
|
18961e3d07 | ||
|
|
fe31f5050d | ||
|
|
ab8549adea | ||
|
|
05600601ff | ||
|
|
c541356289 | ||
|
|
467c4360ca | ||
|
|
3b152a38b0 | ||
|
|
f4d3855528 | ||
|
|
09eab770a7 | ||
|
|
102f8ab74e | ||
|
|
a830dba5da | ||
|
|
3f13a50ca3 | ||
|
|
7c456f2ab9 | ||
|
|
bae95cd6f6 | ||
|
|
bbd6e443b0 | ||
|
|
db0d847e03 | ||
|
|
0afb453fed | ||
|
|
dbc79b4311 | ||
|
|
ac6008c33c | ||
|
|
e540e752f2 | ||
|
|
cdbe821eb8 | ||
|
|
6be994f1ca | ||
|
|
a407b0a8eb | ||
|
|
051ff35878 | ||
|
|
8b3c34c308 | ||
|
|
2cb2668803 | ||
|
|
4dccdb95b9 | ||
|
|
96db9a9410 | ||
|
|
0cd34bbebc | ||
|
|
15f50c0e58 | ||
|
|
7fca9732e7 | ||
|
|
0ea8c3ed28 | ||
|
|
0af9600e92 | ||
|
|
328e3725e5 | ||
|
|
2183e1e9f5 | ||
|
|
120d0be84c | ||
|
|
5649f75a8d | ||
|
|
a209f7d6be | ||
|
|
d48a2f3ccf | ||
|
|
c1ae36866e | ||
|
|
4f368923a5 | ||
|
|
7c02097d93 | ||
|
|
51998f706f | ||
|
|
1a3df08aca | ||
|
|
975f262ac0 | ||
|
|
1cb4a3b8d5 | ||
|
|
35f4b2f686 | ||
|
|
407ec91ca7 | ||
|
|
12c0d18932 | ||
|
|
2d4ca37226 | ||
|
|
afe6744e97 | ||
|
|
19d4b8b7f7 | ||
|
|
3556942516 | ||
|
|
87a200e42c | ||
|
|
152fc0ad38 | ||
|
|
3212ae4713 | ||
|
|
040cef1479 | ||
|
|
f5f70d7a75 | ||
|
|
42509cf2f5 | ||
|
|
6f74c2d823 | ||
|
|
e23a6dc9f1 | ||
|
|
134c6b79c4 | ||
|
|
00ff1447ee | ||
|
|
78f6cb08d8 | ||
|
|
dfd890c8a6 | ||
|
|
7457b3668b | ||
|
|
71e7cd5808 | ||
|
|
57e42af238 | ||
|
|
e065dcb816 | ||
|
|
9619c7f54d | ||
|
|
2508bed363 | ||
|
|
c469632ee0 | ||
|
|
44a52359dc | ||
|
|
2022551b26 | ||
|
|
baac067a1a | ||
|
|
f4216dd67f | ||
|
|
60186bdcd5 | ||
|
|
2c2eb1684b | ||
|
|
33b167215d | ||
|
|
c53db134c6 | ||
|
|
0513a21e25 | ||
|
|
2fc32414f5 | ||
|
|
309bc4ee4c | ||
|
|
7977e6fb16 | ||
|
|
abb19dfbf8 | ||
|
|
14676dc3f8 | ||
|
|
c16f8a4d46 | ||
|
|
48bf09da21 | ||
|
|
c295a1998a | ||
|
|
95f7b9443f | ||
|
|
0160f5dd30 | ||
|
|
f3097845b4 | ||
|
|
5e72de4ba2 | ||
|
|
7a64530e83 | ||
|
|
36f3be9979 | ||
|
|
451b965fb0 | ||
|
|
2b2852aad7 | ||
|
|
72bfd94329 | ||
|
|
300376b0b1 | ||
|
|
e67792177d | ||
|
|
0f24fd593e | ||
|
|
23ec6c721d | ||
|
|
26761e5445 | ||
|
|
e78e4e6a2e | ||
|
|
e765b7a9c4 | ||
|
|
c210e34ce3 | ||
|
|
ddd063f29e | ||
|
|
a2c96e9cdd | ||
|
|
f2416d68b8 | ||
|
|
1eccb61d44 | ||
|
|
cf2b189fbd | ||
|
|
394b69676a | ||
|
|
9704dc5734 | ||
|
|
f54dde4f78 | ||
|
|
70ef9fbcfe | ||
|
|
bb1aff84cf | ||
|
|
dc1ec77da5 | ||
|
|
7077b20a54 | ||
|
|
09e84c2583 | ||
|
|
e3ac6f9e01 | ||
|
|
f91bbe9397 | ||
|
|
31faf05c3a | ||
|
|
55672410cd | ||
|
|
8a66857fb9 | ||
|
|
d0b37df615 | ||
|
|
dc6cb68327 | ||
|
|
72250b32d3 | ||
|
|
726130be09 | ||
|
|
968a29d869 | ||
|
|
ce27e973be | ||
|
|
2607866c49 | ||
|
|
e8e914b11c | ||
|
|
d22b3b0d88 | ||
|
|
5ece1d74f6 | ||
|
|
ac7ab42d94 | ||
|
|
38a3f538f5 | ||
|
|
998935ea55 | ||
|
|
e781ac2512 | ||
|
|
ef2695974d | ||
|
|
b552cc2b12 | ||
|
|
4ae9c2445d | ||
|
|
769e25f080 | ||
|
|
86a23849e0 | ||
|
|
a586fef414 | ||
|
|
a548c63a92 | ||
|
|
5268df6bfd | ||
|
|
82e1c0f810 | ||
|
|
81b0ffb7f4 | ||
|
|
0da130ee2c | ||
|
|
6e880c9027 | ||
|
|
082fa321cb | ||
|
|
ff1c49f111 | ||
|
|
50f592c540 | ||
|
|
7a7f66dfdc | ||
|
|
a1140aa62f | ||
|
|
2dd3564da1 | ||
|
|
fb4b0a187e | ||
|
|
ac48ee066e | ||
|
|
06031efc09 | ||
|
|
9bea80b862 | ||
|
|
92ecb1c7ec | ||
|
|
645f77b849 | ||
|
|
2f9381065d | ||
|
|
774ef61c2f | ||
|
|
dd2c66d2e1 | ||
|
|
0deb2d78fb | ||
|
|
fdd7e7f2a8 | ||
|
|
ad1a440576 | ||
|
|
222b5cb587 | ||
|
|
a1d1f73fe7 | ||
|
|
e7f9ace559 | ||
|
|
cb72c404f5 | ||
|
|
01b9bf5289 | ||
|
|
a52a66ec1c | ||
|
|
313d7089da | ||
|
|
06d80e92eb | ||
|
|
e1fc3aa4fb | ||
|
|
b8fe8d465e | ||
|
|
196d3cb13d | ||
|
|
a3bfa13670 | ||
|
|
2ace0defd0 | ||
|
|
023a902f61 | ||
|
|
789a4c03df | ||
|
|
ecfd8e8a62 | ||
|
|
9ba44f3e6e | ||
|
|
03fd5c84ec | ||
|
|
81e0f170ef | ||
|
|
7e06ba1728 | ||
|
|
f8a5825083 | ||
|
|
a14f7f215c | ||
|
|
8393e8c52f | ||
|
|
81d221667b |
@@ -7,8 +7,7 @@ insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[*.{md,yml,yaml,json,toml}]
|
||||
[*.{md,mdx,yml,yaml,json,toml,htm,html,js,ts,vue,css,svg,sh,bash,fish}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1 +1,5 @@
|
||||
* text=auto
|
||||
*.mdx -linguist-detectable
|
||||
|
||||
# Keep LF line endings in testdata for consistent checksums across platforms
|
||||
testdata/** text eol=lf
|
||||
|
||||
69
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
69
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: '🐞 Bug Report'
|
||||
description: Report a bug in Task.
|
||||
labels: ['state: needs-triage']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your bug report!
|
||||
|
||||
Before submitting, please check the list of [existing issues](https://github.com/go-task/task/issues) and make sure the same bug was not already reported by someone else.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Describe the bug you're seeing.
|
||||
placeholder: |
|
||||
- What did you do?
|
||||
- What did you expect to happen?
|
||||
- What happened instead?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version(s) of Task is the issue occurring on?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
description: What operating system(s) is the issue occurring on?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: experiments
|
||||
attributes:
|
||||
label: Experiments Enabled
|
||||
description: Do you have any experiments enabled? You can check by running `task --experiments`.
|
||||
multiple: true
|
||||
options:
|
||||
- Env Precedence
|
||||
- Gentle Force
|
||||
- Map Variables (1)
|
||||
- Map Variables (2)
|
||||
- Remote Taskfiles
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Example Taskfile
|
||||
description: |
|
||||
If you have a Taskfile that reproduces the issue, please paste it here.
|
||||
This will be automatically formatted into code, so no need for backticks.
|
||||
render: YAML
|
||||
placeholder: |
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- 'echo "This Taskfile is buggy :("'
|
||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: '🔌 Task for Visual Studio Code'
|
||||
url: https://github.com/go-task/vscode-task
|
||||
about: 'Issues related to the Visual Studio Code extension should be opened here.'
|
||||
- name: '💬 Help forum on Discord'
|
||||
url: https://discord.com/channels/974121106208354339/1025054680289660989
|
||||
about: 'The #help channel on our Discord is the best way to get help from the community.'
|
||||
- name: '❓ Questions, Ideas and General Discussions'
|
||||
url: https://github.com/go-task/task/discussions
|
||||
about: 'Ask questions and discuss general ideas with the community.'
|
||||
23
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
23
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: '✨ Feature Request'
|
||||
description: Suggest a new feature or enhancement for Task.
|
||||
labels: ['state: needs-triage']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your feature request!
|
||||
|
||||
Before submitting, please check the list of [existing issues](https://github.com/go-task/task/issues) and make sure the same change was not already requested by someone else.
|
||||
If your request is more of an idea than a feature request, consider opening a [discussion](https://github.com/go-task/task/discussions) instead.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Describe the feature/enhancement you want to see in Task.
|
||||
placeholder: |
|
||||
- Give a general overview of the feature/enhancement.
|
||||
- Explain problem is the change trying to solve.
|
||||
- Give examples of how you would use the feature.
|
||||
validations:
|
||||
required: true
|
||||
11
.github/pull_request_template.md
vendored
Normal file
11
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
|
||||
Thanks for your pull request, we really appreciate contributions!
|
||||
|
||||
Please understand that it may take some time to be reviewed.
|
||||
|
||||
Also, make sure to follow the [Contribution Guide](https://taskfile.dev/contributing/).
|
||||
|
||||
-->
|
||||
|
||||
- [ ] I have read and followed the [Contribution Guide](https://taskfile.dev/contributing/)
|
||||
36
.github/renovate.json
vendored
Normal file
36
.github/renovate.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
"group:allNonMajor",
|
||||
"schedule:weekly",
|
||||
":semanticCommitTypeAll(chore)"
|
||||
],
|
||||
"mode": "full",
|
||||
"addLabels":["area: dependencies"],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"fileMatch": ["^\\.github/workflows/.*\\.ya?ml$"],
|
||||
"matchStrings": [
|
||||
"uses:\\s*golangci/golangci-lint-action@\\S+\\s+with:\\s+version:\\s*(?<currentValue>v[\\d.]+)"
|
||||
],
|
||||
"datasourceTemplate": "github-releases",
|
||||
"depNameTemplate": "golangci/golangci-lint"
|
||||
}
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["github-actions"],
|
||||
"addLabels": ["area: github actions"]
|
||||
},
|
||||
{
|
||||
"matchCategories": ["js", "node"],
|
||||
"addLabels": ["lang: javascript"]
|
||||
},
|
||||
{
|
||||
"matchCategories": ["golang"],
|
||||
"addLabels": ["lang: go"]
|
||||
}
|
||||
]
|
||||
}
|
||||
46
.github/workflows/issue-awaiting-response.yml
vendored
Normal file
46
.github/workflows/issue-awaiting-response.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: issue awaiting response
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-awaiting-response:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const issue = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
})
|
||||
const comments = await github.paginate(
|
||||
github.rest.issues.listComments, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
}
|
||||
)
|
||||
const labels = await github.paginate(
|
||||
github.rest.issues.listLabelsOnIssue, {
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
}
|
||||
)
|
||||
if (labels.find(label => label.name === 'state: awaiting response')) {
|
||||
if (comments[comments.length-1].user?.login === issue.data.user?.login) {
|
||||
github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'state: awaiting response'
|
||||
})
|
||||
}
|
||||
}
|
||||
32
.github/workflows/issue-closed.yml
vendored
Normal file
32
.github/workflows/issue-closed.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: issue closed
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-closed:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const labels = await github.paginate(
|
||||
github.rest.issues.listLabelsOnIssue, {
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
}
|
||||
)
|
||||
if (labels.find(label => label.name === 'state: needs triage')) {
|
||||
github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'state: needs triage'
|
||||
})
|
||||
}
|
||||
126
.github/workflows/issue-experiment.yml
vendored
Normal file
126
.github/workflows/issue-experiment.yml
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
name: issue experiment
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [field_added]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-experiment-proposal:
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'proposal'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: 'This issue has been marked as an experiment proposal! :test_tube: It will now enter a period of consultation during which we encourage the community to provide feedback on the proposed design. Please see the [experiment workflow documentation](https://taskfile.dev/experiments#workflow) for more information on how we release experiments.'
|
||||
})
|
||||
issue-experiment-draft:
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'draft'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: 'This experiment has been marked as a draft! :sparkles: This means that an initial implementation has been added to the latest release of Task! You can find information about this experiment and how to enable it in our [experiments documentation](https://taskfile.dev/experiments). Please see the [experiment workflow documentation](https://taskfile.dev/experiments#workflow) for more information on how we release experiments.'
|
||||
})
|
||||
issue-experiment-candidate:
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'candidate'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: 'This experiment has been marked as a candidate! :fire: This means that the implementation is nearing completion and we are entering a period for final comments and feedback! You can find information about this experiment and how to enable it in our [experiments documentation](https://taskfile.dev/experiments). Please see the [experiment workflow documentation](https://taskfile.dev/experiments#workflow) for more information on how we release experiments.'
|
||||
})
|
||||
issue-experiment-stable:
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'stable'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: 'This experiment has been marked as stable! :metal: This means that the implementation is now final and ready to be released. No more changes will be made and the experiment is safe to use in production! You can find information about this experiment and how to enable it in our [experiments documentation](https://taskfile.dev/experiments). Please see the [experiment workflow documentation](https://taskfile.dev/experiments#workflow) for more information on how we release experiments.'
|
||||
})
|
||||
issue-experiment-released:
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'released'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: 'This experiment has been released! :rocket: This means that it is no longer an experiment and is available in the latest major version of Task. Please see the [experiment workflow documentation](https://taskfile.dev/experiments#workflow) for more information on how we release experiments.'
|
||||
})
|
||||
github.rest.issues.update({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'closed'
|
||||
})
|
||||
issue-experiment-abandoned:
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'abandoned'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: 'This experiment has been abandoned. :disappointed: This means that this feature will not be added to Task and any experimental functionality will be removed. Please see the [experiment workflow documentation](https://taskfile.dev/experiments#workflow) for more information on how we release experiments.'
|
||||
})
|
||||
github.rest.issues.update({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'closed'
|
||||
})
|
||||
issue-experiment-superseded:
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'superseded'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: 'This experiment has been superseded. :seedling: This means that another experiment has replaced this one. Please see the [experiment workflow documentation](https://taskfile.dev/experiments#workflow) for more information on how we release experiments.'
|
||||
})
|
||||
github.rest.issues.update({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'closed'
|
||||
})
|
||||
32
.github/workflows/issue-needs-triage.yml
vendored
Normal file
32
.github/workflows/issue-needs-triage.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: issue needs triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-needs-triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const labels = await github.paginate(
|
||||
github.rest.issues.listLabelsOnIssue, {
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
}
|
||||
)
|
||||
if (labels.length === 0) {
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['state: needs triage']
|
||||
})
|
||||
}
|
||||
46
.github/workflows/lint.yml
vendored
Normal file
46
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.25.10, 1.26.x]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
|
||||
with:
|
||||
version: v2.12.2
|
||||
|
||||
lint-jsonschema:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: install check-jsonschema
|
||||
run: python -m pip install 'check-jsonschema==0.27.3'
|
||||
|
||||
- name: check-jsonschema (metaschema)
|
||||
run: check-jsonschema --check-metaschema website/src/public/schema.json
|
||||
69
.github/workflows/pr-build.yml
vendored
Normal file
69
.github/workflows/pr-build.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: PR Build
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled, synchronize]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'needs-build')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: "1.26.x"
|
||||
cache: true
|
||||
- uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7
|
||||
with:
|
||||
version: "~> v2"
|
||||
args: release --snapshot --clean --config .goreleaser-pr.yml
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_linux_amd64
|
||||
path: dist/task_linux_amd64.tar.gz
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_linux_arm64
|
||||
path: dist/task_linux_arm64.tar.gz
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_darwin_amd64
|
||||
path: dist/task_darwin_amd64.tar.gz
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_darwin_arm64
|
||||
path: dist/task_darwin_arm64.tar.gz
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_windows_amd64
|
||||
path: dist/task_windows_amd64.zip
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: checksums
|
||||
path: dist/task_checksums.txt
|
||||
- uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||
id: find-comment
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-includes: "📦 Build artifacts ready!"
|
||||
- uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
## 📦 Build artifacts ready!
|
||||
|
||||
Download binaries from [this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
|
||||
|
||||
Available platforms: Linux, macOS, Windows (amd64, arm64)
|
||||
edit-mode: replace
|
||||
34
.github/workflows/release-nightly.yml
vendored
Normal file
34
.github/workflows/release-nightly.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Release nightly
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: 1.26.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
args: release --clean --nightly -f .goreleaser-nightly.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
||||
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
|
||||
60
.github/workflows/release.yml
vendored
Normal file
60
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: goreleaser
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: 1.26.x
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "24"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Install Task
|
||||
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6
|
||||
with:
|
||||
package_json_file: "website/package.json"
|
||||
run_install: "true"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
args: release --clean --draft
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
GH_GORELEASER_TOKEN: ${{secrets.GH_GORELEASER_TOKEN}}
|
||||
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
||||
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
|
||||
|
||||
- name: Deploy Website
|
||||
shell: bash
|
||||
run: |
|
||||
task website:deploy:prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
19
.github/workflows/security.yml
vendored
Normal file
19
.github/workflows/security.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Security
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
govulncheck:
|
||||
name: govulncheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
|
||||
33
.github/workflows/test.yml
vendored
Normal file
33
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: [1.25.10, 1.26.x]
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{matrix.platform}}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Go ${{matrix.go-version}}
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
|
||||
- name: Test
|
||||
run: go run ./cmd/task test
|
||||
27
.gitignore
vendored
27
.gitignore
vendored
@@ -10,16 +10,29 @@
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Graphvis files
|
||||
*.gv
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
./task
|
||||
.task
|
||||
dist/
|
||||
|
||||
vendor/**
|
||||
!vendor/**/*.go
|
||||
!vendor/**/LICENSE
|
||||
!vendor/**/COPYING
|
||||
!vendor/**/README
|
||||
!vendor/**/README.md
|
||||
vendor/**/*_test.go
|
||||
.DS_Store
|
||||
|
||||
# editors
|
||||
.idea/
|
||||
.vscode/settings.json
|
||||
.fleet/
|
||||
|
||||
# exuberant ctags
|
||||
tags
|
||||
|
||||
/bin/*
|
||||
!/bin/.keep
|
||||
/testdata/vars/v1
|
||||
/tmp
|
||||
node_modules
|
||||
website/.netlify/
|
||||
|
||||
47
.golangci.yml
Normal file
47
.golangci.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
version: "2"
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gci
|
||||
settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/go-task)
|
||||
- localmodule
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- depguard
|
||||
- gosec
|
||||
- mirror
|
||||
- misspell
|
||||
- modernize
|
||||
- noctx
|
||||
- paralleltest
|
||||
- thelper
|
||||
- tparallel
|
||||
- usetesting
|
||||
settings:
|
||||
gosec:
|
||||
excludes:
|
||||
- G306
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
files:
|
||||
- $all
|
||||
- '!$test'
|
||||
- '!**/errors/*.go'
|
||||
deny:
|
||||
- pkg: errors
|
||||
desc: Use github.com/go-task/task/v3/errors instead
|
||||
exclusions:
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
15
.goreleaser-nightly.yml
Normal file
15
.goreleaser-nightly.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
version: 2
|
||||
pro: true
|
||||
|
||||
release:
|
||||
name_template: 'v{{.Version}}'
|
||||
|
||||
nightly:
|
||||
publish_release: true
|
||||
keep_single_release: true
|
||||
version_template: "{{incminor .Version}}-nightly"
|
||||
|
||||
includes:
|
||||
- from_file:
|
||||
path: ./.goreleaser.yml
|
||||
31
.goreleaser-pr.yml
Normal file
31
.goreleaser-pr.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- binary: task
|
||||
main: ./cmd/task
|
||||
goos: [windows, darwin, linux]
|
||||
goarch: [amd64, arm64]
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- "-s -w"
|
||||
|
||||
archives:
|
||||
- name_template: '{{.Binary}}_{{.Os}}_{{.Arch}}'
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- completion/**/*
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
formats: [zip]
|
||||
|
||||
snapshot:
|
||||
version_template: 'pr-{{ .ShortCommit }}'
|
||||
|
||||
checksum:
|
||||
name_template: 'task_checksums.txt'
|
||||
176
.goreleaser.yml
Normal file
176
.goreleaser.yml
Normal file
@@ -0,0 +1,176 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- binary: task
|
||||
main: ./cmd/task
|
||||
goos:
|
||||
- windows
|
||||
- darwin
|
||||
- linux
|
||||
- freebsd
|
||||
goarch:
|
||||
- '386'
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- riscv64
|
||||
goarm:
|
||||
- '6'
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: '386'
|
||||
- goos: darwin
|
||||
goarch: riscv64
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
- goos: windows
|
||||
goarch: riscv64
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- "-s -w"
|
||||
- "{{if .IsNightly}}-X github.com/go-task/task/v3/internal/version.version={{.Version}}{{end}}"
|
||||
|
||||
gomod:
|
||||
proxy: true
|
||||
|
||||
archives:
|
||||
- name_template: '{{.Binary}}_{{.Os}}_{{.Arch}}'
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- completion/**/*
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
formats: [zip]
|
||||
|
||||
git:
|
||||
ignore_tags:
|
||||
- "{{if not .IsNightly}}nightly{{end}}"
|
||||
|
||||
snapshot:
|
||||
version_template: '{{.Version}}'
|
||||
|
||||
checksum:
|
||||
name_template: 'task_checksums.txt'
|
||||
|
||||
nfpms:
|
||||
- vendor: Task
|
||||
homepage: https://taskfile.dev
|
||||
maintainer: The Task authors <task@taskfile.dev>
|
||||
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
section: golang
|
||||
license: MIT
|
||||
conflicts:
|
||||
- taskwarrior
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
- apk
|
||||
file_name_template: '{{.ProjectName}}_{{.Version}}_{{.Os}}_{{.Arch}}'
|
||||
contents:
|
||||
- src: completion/bash/task.bash
|
||||
dst: /etc/bash_completion.d/task
|
||||
- src: completion/fish/task.fish
|
||||
dst: /usr/share/fish/completions/task.fish
|
||||
- src: completion/zsh/_task
|
||||
dst: /usr/local/share/zsh/site-functions/_task
|
||||
|
||||
brews:
|
||||
- name: go-task
|
||||
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
license: MIT
|
||||
homepage: https://taskfile.dev
|
||||
directory: Formula
|
||||
repository:
|
||||
owner: go-task
|
||||
name: homebrew-tap
|
||||
token: "{{.Env.GH_GORELEASER_TOKEN}}" # So that it runs as the task-bot user
|
||||
test: system "#{bin}/task", "--help"
|
||||
install: |-
|
||||
bin.install "task"
|
||||
bash_completion.install "completion/bash/task.bash" => "task"
|
||||
zsh_completion.install "completion/zsh/_task" => "_task"
|
||||
fish_completion.install "completion/fish/task.fish"
|
||||
commit_author:
|
||||
name: task-bot
|
||||
email: 106601941+task-bot@users.noreply.github.com
|
||||
|
||||
winget:
|
||||
- name: Task
|
||||
publisher: Task
|
||||
short_description: The modern task runner.
|
||||
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
license: MIT
|
||||
homepage: https://taskfile.dev/
|
||||
publisher_url: https://taskfile.dev/
|
||||
publisher_support_url: https://github.com/go-task/task/issues
|
||||
package_identifier: Task.Task
|
||||
commit_author:
|
||||
name: task-bot
|
||||
email: 106601941+task-bot@users.noreply.github.com
|
||||
commit_msg_template: 'chore: release {{.PackageIdentifier}} {{.Tag}}'
|
||||
release_notes_url: https://github.com/go-task/task/releases/tag/{{.Tag}}
|
||||
tags:
|
||||
- build
|
||||
- build-tool
|
||||
- devops
|
||||
- go
|
||||
- make
|
||||
- makefile
|
||||
- runner
|
||||
- task
|
||||
- task-runner
|
||||
- taskfile
|
||||
- tool
|
||||
repository:
|
||||
owner: go-task
|
||||
name: winget-pkgs
|
||||
branch: 'task-{{.Version}}'
|
||||
token: "{{.Env.GH_GORELEASER_TOKEN}}" # So that it runs as the task-bot user
|
||||
pull_request:
|
||||
enabled: true
|
||||
draft: false
|
||||
check_boxes: true
|
||||
base:
|
||||
owner: microsoft
|
||||
name: winget-pkgs
|
||||
branch: master
|
||||
body: |
|
||||
/cc @andreynering @pd93 @vmaerten
|
||||
|
||||
npms:
|
||||
- name: "@go-task/cli"
|
||||
repository: "git+https://github.com/go-task/task.git"
|
||||
bugs: https://github.com/go-task/task/issues
|
||||
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
homepage: https://taskfile.dev
|
||||
license: MIT
|
||||
author: "The Task authors"
|
||||
access: public
|
||||
keywords:
|
||||
- "task"
|
||||
- "taskfile"
|
||||
- "build-tool"
|
||||
- "task-runner"
|
||||
|
||||
cloudsmiths:
|
||||
- organization: "task"
|
||||
repository: "{{if not .IsNightly}}task{{end}}"
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
- apk
|
||||
distributions:
|
||||
deb:
|
||||
- "any-distro/any-version"
|
||||
rpm:
|
||||
- "any-distro/any-version"
|
||||
alpine:
|
||||
- "alpine/any-version"
|
||||
component: main
|
||||
republish: true
|
||||
8
.mockery.yaml
Normal file
8
.mockery.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
all: False
|
||||
template: testify
|
||||
filename: '{{base (trimSuffix ".go" .InterfaceFile)}}_mock.go'
|
||||
packages:
|
||||
github.com/go-task/task/v3/internal/fingerprint:
|
||||
interfaces:
|
||||
SourcesCheckable:
|
||||
StatusCheckable:
|
||||
7
.prettierrc.yml
Normal file
7
.prettierrc.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
trailingComma: none
|
||||
singleQuote: true
|
||||
overrides:
|
||||
- files: "*.md"
|
||||
options:
|
||||
printWidth: 80
|
||||
proseWrap: always
|
||||
4
.taskrc.yml
Normal file
4
.taskrc.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
experiments:
|
||||
GENTLE_FORCE: 0
|
||||
REMOTE_TASKFILES: 0
|
||||
ENV_PRECEDENCE: 0
|
||||
@@ -1,9 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.7
|
||||
- 1.8
|
||||
script:
|
||||
- go install github.com/go-task/task/cmd/task
|
||||
- task dl-deps
|
||||
- task lint
|
||||
- task test
|
||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"editorconfig.editorconfig",
|
||||
"golang.go",
|
||||
"task.vscode-task"
|
||||
]
|
||||
}
|
||||
14
.vscode/settings-sample.json
vendored
Normal file
14
.vscode/settings-sample.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"yaml.schemas": {
|
||||
"./website/src/public/schema.json": [
|
||||
"Taskfile.yml",
|
||||
"Taskfile.yaml",
|
||||
"taskfile.yml",
|
||||
"taskfile.yaml"
|
||||
]
|
||||
},
|
||||
"gopls": {
|
||||
"formatting.local": "github.com/go-task"
|
||||
},
|
||||
"go.formatTool": "gofumpt"
|
||||
}
|
||||
1524
CHANGELOG.md
Normal file
1524
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
105
Gopkg.lock
generated
105
Gopkg.lock
generated
@@ -1,105 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/BurntSushi/toml"
|
||||
packages = ["."]
|
||||
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/Masterminds/semver"
|
||||
packages = ["."]
|
||||
revision = "abff1900528dbdaf6f3f5aa92c398be1eaf2a9f7"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/Masterminds/sprig"
|
||||
packages = ["."]
|
||||
revision = "e039e20e500c2c025d9145be375e27cf42a94174"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/aokoli/goutils"
|
||||
packages = ["."]
|
||||
revision = "3391d3790d23d03408670993e957e8f408993c34"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
packages = ["."]
|
||||
revision = "4da3e2cfbabc9f751898f250b49f2439785783a1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/huandu/xstrings"
|
||||
packages = ["."]
|
||||
revision = "3959339b333561bf62a38b424fd41517c2c90f40"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/imdario/mergo"
|
||||
packages = ["."]
|
||||
revision = "d806ba8c21777d504a2090a2ca4913c750dd3a33"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-zglob"
|
||||
packages = [".","fastwalk"]
|
||||
revision = "95345c4e1c0ebc9d16a3284177f09360f4d20fab"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mvdan/sh"
|
||||
packages = ["interp","syntax"]
|
||||
revision = "c79708369f1c3cde582a511d52173966c25edefd"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/satori/go.uuid"
|
||||
packages = ["."]
|
||||
revision = "5bf94b69c6b68ee1b541973bb8e1144db23a194b"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["pbkdf2","scrypt"]
|
||||
revision = "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context"]
|
||||
revision = "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sync"
|
||||
packages = ["errgroup"]
|
||||
revision = "f52d1811a62927559de87708c8913c1650ce4f26"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "b90f89a1e7a9c1f6b918820b3daa7f08488c8594"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "2f626389e1dc4d5ac2e6b83c6b9a50cab0dd3187bbc4e03694810df03a08b3fc"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
103
Gopkg.toml
103
Gopkg.toml
@@ -1,103 +0,0 @@
|
||||
|
||||
## Gopkg.toml example (these lines may be deleted)
|
||||
|
||||
## "metadata" defines metadata about the project that could be used by other independent
|
||||
## systems. The metadata defined here will be ignored by dep.
|
||||
# [metadata]
|
||||
# key1 = "value that convey data to other systems"
|
||||
# system1-data = "value that is used by a system"
|
||||
# system2-data = "value that is used by another system"
|
||||
|
||||
## "required" lists a set of packages (not projects) that must be included in
|
||||
## Gopkg.lock. This list is merged with the set of packages imported by the current
|
||||
## project. Use it when your project needs a package it doesn't explicitly import -
|
||||
## including "main" packages.
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
|
||||
## "ignored" lists a set of packages (not projects) that are ignored when
|
||||
## dep statically analyzes source code. Ignored packages can be in this project,
|
||||
## or in a dependency.
|
||||
# ignored = ["github.com/user/project/badpkg"]
|
||||
|
||||
## Constraints are rules for how directly imported projects
|
||||
## may be incorporated into the depgraph. They are respected by
|
||||
## dep whether coming from the Gopkg.toml of the current project or a dependency.
|
||||
# [[constraint]]
|
||||
## Required: the root import path of the project being constrained.
|
||||
# name = "github.com/user/project"
|
||||
#
|
||||
## Recommended: the version constraint to enforce for the project.
|
||||
## Only one of "branch", "version" or "revision" can be specified.
|
||||
# version = "1.0.0"
|
||||
# branch = "master"
|
||||
# revision = "abc123"
|
||||
#
|
||||
## Optional: an alternate location (URL or import path) for the project's source.
|
||||
# source = "https://github.com/myfork/package.git"
|
||||
#
|
||||
## "metadata" defines metadata about the dependency or override that could be used
|
||||
## by other independent systems. The metadata defined here will be ignored by dep.
|
||||
# [metadata]
|
||||
# key1 = "value that convey data to other systems"
|
||||
# system1-data = "value that is used by a system"
|
||||
# system2-data = "value that is used by another system"
|
||||
|
||||
## Overrides have the same structure as [[constraint]], but supersede all
|
||||
## [[constraint]] declarations from all projects. Only [[override]] from
|
||||
## the current project's are applied.
|
||||
##
|
||||
## Overrides are a sledgehammer. Use them only as a last resort.
|
||||
# [[override]]
|
||||
## Required: the root import path of the project being constrained.
|
||||
# name = "github.com/user/project"
|
||||
#
|
||||
## Optional: specifying a version constraint override will cause all other
|
||||
## constraints on this project to be ignored; only the overridden constraint
|
||||
## need be satisfied.
|
||||
## Again, only one of "branch", "version" or "revision" can be specified.
|
||||
# version = "1.0.0"
|
||||
# branch = "master"
|
||||
# revision = "abc123"
|
||||
#
|
||||
## Optional: specifying an alternate source location as an override will
|
||||
## enforce that the alternate location is used for that project, regardless of
|
||||
## what source location any dependent projects specify.
|
||||
# source = "https://github.com/myfork/package.git"
|
||||
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/BurntSushi/toml"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/Masterminds/sprig"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/imdario/mergo"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-zglob"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mvdan/sh"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/spf13/pflag"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sync"
|
||||
|
||||
[[constraint]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
434
README.md
434
README.md
@@ -1,385 +1,49 @@
|
||||
[](https://github.com/go-task/task/blob/master/LICENSE)
|
||||
[](https://gitter.im/go-task/task?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://gophers.slack.com/messages/task)
|
||||
[](http://www.somsubhra.com/github-release-stats/?username=go-task&repository=task)
|
||||
[](https://travis-ci.org/go-task/task)
|
||||
|
||||
# Task - Simple task runner / "Make" alternative
|
||||
|
||||
Task is a simple tool that allows you to easily run development and build
|
||||
tasks. Task is written in Golang, but can be used to develop any language.
|
||||
It aims to be simpler and easier to use then [GNU Make][make].
|
||||
|
||||
## Installation
|
||||
|
||||
If you have a [Golang][golang] environment setup, you can simply run:
|
||||
|
||||
```bash
|
||||
go get -u -v github.com/go-task/task/cmd/task
|
||||
```
|
||||
|
||||
Or you can download from the [releases][releases] page and add to your `PATH`.
|
||||
|
||||
## Usage
|
||||
|
||||
Create a file called `Taskfile.yml` in the root of the project.
|
||||
(`Taskfile.toml` and `Taskfile.json` are also supported, but YAML is used in
|
||||
the documentation). The `cmds` attribute should contains the commands of a
|
||||
task:
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
assets:
|
||||
cmds:
|
||||
- gulp
|
||||
```
|
||||
|
||||
Running the tasks is as simple as running:
|
||||
|
||||
```bash
|
||||
task assets build
|
||||
```
|
||||
|
||||
Task uses [github.com/mvdan/sh](https://github.com/mvdan/sh), a native Go sh
|
||||
interpreter. So you can write sh/bash commands and it will work even on
|
||||
Windows, where `sh` or `bash` is usually not available. Just remember any
|
||||
executable called must be available by the OS or in PATH.
|
||||
|
||||
If you ommit a task name, "default" will be assumed.
|
||||
|
||||
### Environment
|
||||
|
||||
You can specify environment variables that are added when running a command:
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- echo $hallo
|
||||
env:
|
||||
hallo: welt
|
||||
```
|
||||
|
||||
### OS specific task support
|
||||
|
||||
If you add a `Taskfile_{{GOOS}}` you can override or amend your taskfile based
|
||||
on the operating system.
|
||||
|
||||
Example:
|
||||
|
||||
Taskfile.yml:
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- echo "default"
|
||||
```
|
||||
|
||||
Taskfile_linux.yml:
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- echo "linux"
|
||||
```
|
||||
|
||||
Will print out `linux` and not default
|
||||
|
||||
### Running task in another dir
|
||||
|
||||
By default, tasks will be executed in the directory where the Taskfile is
|
||||
located. But you can easily make the task run in another folder informing
|
||||
`dir`:
|
||||
|
||||
```yml
|
||||
js:
|
||||
dir: www/public/js
|
||||
cmds:
|
||||
- gulp
|
||||
```
|
||||
|
||||
### Task dependencies
|
||||
|
||||
You may have tasks that depends on others. Just pointing them on `deps` will
|
||||
make them run automatically before running the parent task:
|
||||
|
||||
```yml
|
||||
build:
|
||||
deps: [assets]
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
assets:
|
||||
cmds:
|
||||
- gulp
|
||||
```
|
||||
|
||||
In the above example, `assets` will always run right before `build` if you run
|
||||
`task build`.
|
||||
|
||||
A task can have only dependencies and no commands to group tasks together:
|
||||
|
||||
```yml
|
||||
assets:
|
||||
deps: [js, css]
|
||||
|
||||
js:
|
||||
cmds:
|
||||
- npm run buildjs
|
||||
|
||||
css:
|
||||
cmds:
|
||||
- npm run buildcss
|
||||
```
|
||||
|
||||
Each task can only be run once. If it is included from another dependend task causing
|
||||
a cyclomatic dependency, execution will be stopped.
|
||||
|
||||
```yml
|
||||
task1:
|
||||
deps: [task2]
|
||||
|
||||
task2:
|
||||
deps: [task1]
|
||||
```
|
||||
|
||||
Will stop at the moment the dependencies of `task2` are executed.
|
||||
|
||||
### Calling a task from another task
|
||||
|
||||
When a task has many dependencies, they are executed concurrently. This will
|
||||
often result in a faster build pipeline. But in some situations you may need
|
||||
to call other tasks serially. For this just prefix a command with `^`:
|
||||
|
||||
```yml
|
||||
a-task:
|
||||
cmds:
|
||||
- ^another-task
|
||||
- ^even-another-task
|
||||
- echo "Both done"
|
||||
|
||||
another-task:
|
||||
cmds:
|
||||
- ...
|
||||
|
||||
even-another-task:
|
||||
cmds:
|
||||
- ...
|
||||
```
|
||||
|
||||
### Prevent task from running when not necessary
|
||||
|
||||
If a task generates something, you can inform Task the source and generated
|
||||
files, so Task will prevent to run them if not necessary.
|
||||
|
||||
```yml
|
||||
build:
|
||||
deps: [js, css]
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
js:
|
||||
cmds:
|
||||
- npm run buildjs
|
||||
sources:
|
||||
- js/src/**/*.js
|
||||
generates:
|
||||
- public/bundle.js
|
||||
|
||||
css:
|
||||
cmds:
|
||||
- npm run buildcss
|
||||
sources:
|
||||
- css/src/*.css
|
||||
generates:
|
||||
- public/bundle.css
|
||||
```
|
||||
|
||||
`sources` and `generates` should be file patterns. When both are given, Task
|
||||
will compare the modification date/time of the files to determine if it's
|
||||
necessary to run the task. If not, it will just print
|
||||
`Task "js" is up to date`.
|
||||
|
||||
Alternatively, you can inform a sequence of tests as `status`. If no error
|
||||
is returned (exit status 0), the task is considered up-to-date:
|
||||
|
||||
```yml
|
||||
generate-files:
|
||||
cmds:
|
||||
- mkdir directory
|
||||
- touch directory/file1.txt
|
||||
- touch directory/file2.txt
|
||||
# test existence of files
|
||||
status:
|
||||
- test -d directory
|
||||
- test -f directory/file1.txt
|
||||
- test -f directory/file2.txt
|
||||
```
|
||||
|
||||
You can use `--force` or `-f` if you want to force a task to run even when
|
||||
up-to-date.
|
||||
|
||||
### Variables
|
||||
|
||||
```yml
|
||||
build:
|
||||
deps: [setvar]
|
||||
cmds:
|
||||
- echo "{{.PREFIX}} {{.THEVAR}}"
|
||||
vars:
|
||||
PREFIX: "Path:"
|
||||
|
||||
setvar:
|
||||
cmds:
|
||||
- echo "{{.PATH}}"
|
||||
set: THEVAR
|
||||
```
|
||||
|
||||
The above sample saves the path into a new variable which is then again echoed.
|
||||
|
||||
You can use environment variables, task level variables and a file called
|
||||
`Taskvars.yml` (or `Taskvars.toml` or `Taskvars.json`) as source of variables.
|
||||
|
||||
They are evaluated in the following order:
|
||||
|
||||
Task local variables are overwritten by variables found in `Taskvars` file.
|
||||
Variables found in `Taskvars` file are overwritten with variables from the
|
||||
environment. The output of the last command is stored in the environment. So
|
||||
you can do something like this:
|
||||
|
||||
```yml
|
||||
build:
|
||||
deps: [setvar]
|
||||
cmds:
|
||||
- echo "{{.PREFIX}} '{{.THEVAR}}'"
|
||||
vars:
|
||||
PREFIX: "Result: "
|
||||
|
||||
setvar:
|
||||
cmds:
|
||||
- echo -n "a"
|
||||
- echo -n "{{.THEVAR}}b"
|
||||
- echo -n "{{.THEVAR}}c"
|
||||
set: THEVAR
|
||||
```
|
||||
|
||||
The result of a run of build would be:
|
||||
|
||||
```
|
||||
a
|
||||
ab
|
||||
abc
|
||||
Result: 'abc'
|
||||
```
|
||||
|
||||
#### Dynamic variables
|
||||
|
||||
If you prefix a variable with `$`, then the variable is considered a dynamic
|
||||
variable. The value after the $-symbol will be treated as a command and the
|
||||
output assigned.
|
||||
|
||||
```yml
|
||||
build:
|
||||
cmds:
|
||||
- go build -ldflags="-X main.Version={{.LAST_GIT_COMMIT}}" main.go
|
||||
vars:
|
||||
LAST_GIT_COMMIT: $git log -n 1 --format=%h
|
||||
```
|
||||
|
||||
### Go's template engine
|
||||
|
||||
Task parse commands as [Go's template engine][gotemplate] before executing
|
||||
them. Variables are acessible through dot syntax (`.VARNAME`).
|
||||
|
||||
All functions by the Go's [sprig lib](http://masterminds.github.io/sprig/)
|
||||
are available. The following example gets the current date in a given format:
|
||||
|
||||
```go
|
||||
print-date:
|
||||
cmds:
|
||||
- echo {{now | date "2006-01-02"}}
|
||||
```
|
||||
|
||||
Task also adds the following functions:
|
||||
|
||||
- `OS`: Returns operating system. Possible values are "windows", "linux",
|
||||
"darwin" (macOS) and "freebsd".
|
||||
- `ARCH`: return the architecture Task was compiled to: "386", "amd64", "arm"
|
||||
or "s390x".
|
||||
- `ToSlash`: Does nothing on Unix, but on Windows converts a string from `\`
|
||||
path format to `/`.
|
||||
- `FromSlash`: Oposite of `ToSlash`. Does nothing on Unix, but on Windows
|
||||
converts a string from `\` path format to `/`.
|
||||
- `ExeExt`: Returns the right executable extension for the current OS
|
||||
(`".exe"` for Windows, `""` for others).
|
||||
|
||||
Example:
|
||||
|
||||
```yml
|
||||
printos:
|
||||
cmds:
|
||||
- echo '{{OS}} {{ARCH}}'
|
||||
- "echo '{{if eq OS \"windows\"}}windows-command{{else}}unix-command{{end}}'"
|
||||
# This will be path/to/file on Unix but path\to\file on Windows
|
||||
- "{{FromSlash \"path/to/file\"}}"
|
||||
```
|
||||
|
||||
### Help
|
||||
|
||||
Running `task help` lists all tasks with a description. The following taskfile:
|
||||
|
||||
```yml
|
||||
build:
|
||||
desc: Build the go binary.
|
||||
cmds:
|
||||
- go build -v -i main.go
|
||||
|
||||
test:
|
||||
desc: Run all the go tests.
|
||||
cmds:
|
||||
- go test -race ./...
|
||||
|
||||
js:
|
||||
cmds:
|
||||
- npm run buildjs
|
||||
|
||||
css:
|
||||
cmds:
|
||||
- npm run buildcss
|
||||
```
|
||||
|
||||
would print the following output:
|
||||
|
||||
```bash
|
||||
build Build the go binary.
|
||||
test Run all the go tests.
|
||||
```
|
||||
|
||||
## Watch tasks (experimental)
|
||||
|
||||
If you give a `--watch` or `-w` argument, task will watch for files changes
|
||||
and run the task again. This requires the `sources` attribute to be given,
|
||||
so task know which files to watch.
|
||||
|
||||
## Alternative task runners
|
||||
|
||||
- YAML based:
|
||||
- [tj/robo][robo]
|
||||
- [dogtools/dog][dog]
|
||||
- [goeuro/myke][myke]
|
||||
- Go based:
|
||||
- [go-godo][godo]
|
||||
- [markbates/grift][grift]
|
||||
|
||||
[make]: https://www.gnu.org/software/make/
|
||||
[releases]: https://github.com/go-task/task/releases
|
||||
[golang]: https://golang.org/
|
||||
[gotemplate]: https://golang.org/pkg/text/template/
|
||||
[robo]: https://github.com/tj/robo
|
||||
[dog]: https://github.com/dogtools/dog
|
||||
[myke]: https://github.com/goeuro/myke
|
||||
[godo]: https://github.com/go-godo/godo
|
||||
[grift]: https://github.com/markbates/grift
|
||||
[sh]: https://github.com/mvdan/sh
|
||||
<div align="center">
|
||||
<a href="https://taskfile.dev">
|
||||
<img src="website/src/public/img/logo.svg" width="200px" height="200px" />
|
||||
</a>
|
||||
|
||||
<h1>Task: The Modern Task Runner</h1>
|
||||
|
||||
<p>
|
||||
A fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="https://taskfile.dev/docs/installation">Installation</a> • <a href="https://taskfile.dev/docs/getting-started">Getting Started</a> • <a href="https://taskfile.dev/docs/guide">Docs</a> • <a href="https://twitter.com/taskfiledev">Twitter</a> • <a href="https://bsky.app/profile/taskfile.dev">Bluesky</a> • <a href="https://fosstodon.org/@task">Mastodon</a> • <a href="https://discord.gg/6TY36E39UK">Discord</a>
|
||||
</p>
|
||||
|
||||
<h1>Gold Sponsors</h1>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://devowl.io">
|
||||
<img src="website/src/public/img/devowl.io.svg" height="100px" width="200px" title="devowl.io" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://goodx.international/">
|
||||
<img src="website/src/public/img/goodx.svg" height="80px" width="200px" title="GoodX" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://magic.dev/">
|
||||
<img src="website/src/public/img/magic.png" height="100px" width="200px" title="Magic" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Community Sponsors</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://cloudsmith.com/">
|
||||
<img src="website/src/public/img/cloudsmith.svg" height="100px" width="200px" title="Cloudsmith" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
268
Taskfile.yml
268
Taskfile.yml
@@ -1,49 +1,233 @@
|
||||
# compiles current source code and make "task" executable available on
|
||||
# $GOPATH/bin/task{.exe}
|
||||
install:
|
||||
desc: Installs Task
|
||||
cmds:
|
||||
- go install -v -ldflags="-w -s -X main.version={{.GIT_COMMIT}}" ./cmd/task
|
||||
vars:
|
||||
GIT_COMMIT: $git log -n 1 --format=%h
|
||||
version: '3'
|
||||
|
||||
dl-deps:
|
||||
desc: Downloads cli dependencies
|
||||
cmds:
|
||||
- go get -u github.com/golang/lint/golint
|
||||
- go get -u github.com/goreleaser/goreleaser
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
includes:
|
||||
website:
|
||||
aliases: [w, docs, d]
|
||||
taskfile: ./website
|
||||
dir: ./website
|
||||
|
||||
update-deps:
|
||||
cmds:
|
||||
- dep ensure
|
||||
- dep prune
|
||||
vars:
|
||||
BIN: "{{.ROOT_DIR}}/bin"
|
||||
GOTESTSUM_FORMAT: '{{if .CI}}github-actions{{else}}pkgname{{end}}'
|
||||
|
||||
clean:
|
||||
desc: Cleans temp files and folders
|
||||
cmds:
|
||||
- rm -rf dist/
|
||||
env:
|
||||
CGO_ENABLED: '0'
|
||||
|
||||
lint:
|
||||
desc: Runs golint
|
||||
cmds:
|
||||
- golint .
|
||||
- golint ./execext
|
||||
- golint ./cmd/task
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: lint
|
||||
- task: test
|
||||
|
||||
test:
|
||||
desc: Runs test suite
|
||||
deps: [install]
|
||||
cmds:
|
||||
- go test -v
|
||||
run:
|
||||
desc: Runs Task
|
||||
cmds:
|
||||
- go run ./cmd/task {{.CLI_ARGS}}
|
||||
|
||||
# https://github.com/goreleaser/goreleaser
|
||||
release:
|
||||
desc: Release Task
|
||||
cmds:
|
||||
- goreleaser
|
||||
install:
|
||||
desc: Installs Task
|
||||
aliases: [i]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- go.mod
|
||||
cmds:
|
||||
- go install -v ./cmd/task
|
||||
|
||||
test-release:
|
||||
desc: Tests release process without publishing
|
||||
cmds:
|
||||
- goreleaser --skip-validate --skip-publish
|
||||
generate:
|
||||
aliases: [gen, g]
|
||||
desc: Runs all generate tasks
|
||||
cmds:
|
||||
- task: generate:mocks
|
||||
- task: generate:fixtures
|
||||
|
||||
generate:mocks:
|
||||
desc: Runs Mockery to create mocks
|
||||
aliases: [gen:mocks, g:mocks]
|
||||
deps: [install:mockery]
|
||||
sources:
|
||||
- "internal/fingerprint/checker.go"
|
||||
generates:
|
||||
- "internal/mocks/*.go"
|
||||
cmds:
|
||||
- find . -type f -name *_mock.go -delete
|
||||
- "{{.BIN}}/mockery"
|
||||
|
||||
generate:fixtures:
|
||||
desc: Runs tests and generates golden fixture files
|
||||
aliases: [gen:fixtures, g:fixtures]
|
||||
env:
|
||||
GOLDIE_UPDATE: 'true'
|
||||
GOLDIE_TEMPLATE: 'true'
|
||||
cmds:
|
||||
- find ./testdata -name '*.golden' -delete
|
||||
- go test ./...
|
||||
|
||||
install:mockery:
|
||||
desc: Installs mockgen; a tool to generate mock files
|
||||
vars:
|
||||
MOCKERY_VERSION: v3.2.2
|
||||
env:
|
||||
GOBIN: "{{.BIN}}"
|
||||
status:
|
||||
- go version -m {{.BIN}}/mockery | grep github.com/vektra/mockery | grep {{.MOCKERY_VERSION}}
|
||||
cmds:
|
||||
- GOBIN="{{.BIN}}" go install github.com/vektra/mockery/v3@{{.MOCKERY_VERSION}}
|
||||
|
||||
mod:
|
||||
desc: Downloads and tidy Go modules
|
||||
cmds:
|
||||
- go mod download
|
||||
- go mod tidy
|
||||
|
||||
clean:
|
||||
desc: Cleans temp files and folders
|
||||
aliases: [clear]
|
||||
cmds:
|
||||
- rm -rf dist/
|
||||
- rm -rf tmp/
|
||||
|
||||
lint:
|
||||
desc: Runs golangci-lint
|
||||
aliases: [l]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- .golangci.yml
|
||||
- go.mod
|
||||
cmds:
|
||||
- golangci-lint run
|
||||
|
||||
lint:fix:
|
||||
desc: Runs golangci-lint and fixes any issues
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- .golangci.yml
|
||||
- go.mod
|
||||
cmds:
|
||||
- golangci-lint run --fix
|
||||
|
||||
format:
|
||||
desc: Runs golangci-lint and formats any Go files
|
||||
aliases: [fmt, f]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- .golangci.yml
|
||||
cmds:
|
||||
- golangci-lint fmt
|
||||
|
||||
sleepit:build:
|
||||
desc: Builds the sleepit test helper
|
||||
sources:
|
||||
- ./cmd/sleepit/**/*.go
|
||||
generates:
|
||||
- "{{.BIN}}/sleepit"
|
||||
cmds:
|
||||
- go build -o {{.BIN}}/sleepit{{exeExt}} ./cmd/sleepit
|
||||
|
||||
sleepit:run:
|
||||
desc: Builds the sleepit test helper
|
||||
deps: [sleepit:build]
|
||||
cmds:
|
||||
- "{{.BIN}}/sleepit {{.CLI_ARGS}}"
|
||||
silent: true
|
||||
|
||||
test:
|
||||
desc: Runs test suite
|
||||
aliases: [t]
|
||||
deps: [gotestsum:install]
|
||||
sources:
|
||||
- "**/*.go"
|
||||
- "testdata/**/*"
|
||||
cmds:
|
||||
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./...
|
||||
|
||||
test:watch:
|
||||
desc: Runs test suite with watch tests included
|
||||
deps: [sleepit:build, gotestsum:install]
|
||||
cmds:
|
||||
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./... -tags 'watch'
|
||||
|
||||
test:all:
|
||||
desc: Runs test suite with signals and watch tests included
|
||||
deps: [sleepit:build, gotestsum:install]
|
||||
cmds:
|
||||
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' -tags 'signals watch' ./...
|
||||
|
||||
goreleaser:test:
|
||||
desc: Tests release process without publishing
|
||||
cmds:
|
||||
- goreleaser --snapshot --clean
|
||||
|
||||
gotestsum:install:
|
||||
desc: Installs gotestsum
|
||||
status:
|
||||
- command -v gotestsum
|
||||
cmds:
|
||||
- go install gotest.tools/gotestsum@latest
|
||||
|
||||
goreleaser:install:
|
||||
desc: Installs goreleaser
|
||||
cmds:
|
||||
- go install github.com/goreleaser/goreleaser/v2@latest
|
||||
|
||||
gorelease:install:
|
||||
desc: "Installs gorelease: https://pkg.go.dev/golang.org/x/exp/cmd/gorelease"
|
||||
status:
|
||||
- command -v gorelease
|
||||
cmds:
|
||||
- go install golang.org/x/exp/cmd/gorelease@latest
|
||||
|
||||
api:check:
|
||||
desc: Checks what changes have been made to the public API
|
||||
deps: [gorelease:install]
|
||||
vars:
|
||||
LATEST:
|
||||
sh: git describe --tags --abbrev=0
|
||||
cmds:
|
||||
- gorelease -base={{.LATEST}}
|
||||
|
||||
release:*:
|
||||
desc: Prepare the project for a new release
|
||||
summary: |
|
||||
This task will do the following:
|
||||
|
||||
- Update the version and date in the CHANGELOG.md file
|
||||
- Update the version in the package.json and package-lock.json files
|
||||
- Copy the latest docs to the "current" version on the website
|
||||
- Commit the changes
|
||||
- Create a new tag
|
||||
- Push the commit/tag to the repository
|
||||
- Create a GitHub release
|
||||
|
||||
To use the task, run "task release:<version>" where "<version>" is is one of:
|
||||
|
||||
- "major" - Bumps the major number
|
||||
- "minor" - Bumps the minor number
|
||||
- "patch" - Bumps the patch number
|
||||
- A semver compatible version number (e.g. "1.2.3")
|
||||
vars:
|
||||
VERSION:
|
||||
sh: "go run ./cmd/release --version {{index .MATCH 0}}"
|
||||
COMPLETE_MESSAGE: |
|
||||
Creating release with GoReleaser: https://github.com/go-task/task/actions/workflows/release.yml
|
||||
|
||||
Please wait for the CI to finish and then do the following:
|
||||
|
||||
- Copy the changelog for v{{.VERSION}} to the GitHub release
|
||||
- Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml
|
||||
preconditions:
|
||||
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
|
||||
msg: "You must be on the main branch to release"
|
||||
- sh: "[[ -z $(git diff --shortstat main) ]]"
|
||||
msg: "You must have a clean working tree to release"
|
||||
prompt: "Are you sure you want to release version {{.VERSION}}?"
|
||||
cmds:
|
||||
- cmd: echo "Releasing v{{.VERSION}}"
|
||||
silent: true
|
||||
- "go run ./cmd/release {{.VERSION}}"
|
||||
- "git add --all"
|
||||
- "git commit -m v{{.VERSION}}"
|
||||
- "git push"
|
||||
- "git tag -a v{{.VERSION}} -m v{{.VERSION}}"
|
||||
- "git push origin tag v{{.VERSION}}"
|
||||
- cmd: printf "%s" '{{.COMPLETE_MESSAGE}}'
|
||||
silent: true
|
||||
|
||||
59
args/args.go
Normal file
59
args/args.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package args
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
// Get fetches the remaining arguments after CLI parsing and splits them into
|
||||
// two groups: the arguments before the double dash (--) and the arguments after
|
||||
// the double dash.
|
||||
func Get() ([]string, []string, error) {
|
||||
args := pflag.Args()
|
||||
doubleDashPos := pflag.CommandLine.ArgsLenAtDash()
|
||||
|
||||
if doubleDashPos == -1 {
|
||||
return args, nil, nil
|
||||
}
|
||||
return args[:doubleDashPos], args[doubleDashPos:], nil
|
||||
}
|
||||
|
||||
// Parse parses command line argument: tasks and global variables
|
||||
func Parse(args ...string) ([]*task.Call, *ast.Vars) {
|
||||
calls := []*task.Call{}
|
||||
globals := ast.NewVars()
|
||||
|
||||
for _, arg := range args {
|
||||
if !strings.Contains(arg, "=") {
|
||||
calls = append(calls, &task.Call{Task: arg})
|
||||
continue
|
||||
}
|
||||
|
||||
name, value := splitVar(arg)
|
||||
globals.Set(name, ast.Var{Value: value})
|
||||
}
|
||||
|
||||
return calls, globals
|
||||
}
|
||||
|
||||
func ToQuotedString(args []string) (string, error) {
|
||||
var quotedCliArgs []string
|
||||
for _, arg := range args {
|
||||
quotedCliArg, err := syntax.Quote(arg, syntax.LangBash)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
quotedCliArgs = append(quotedCliArgs, quotedCliArg)
|
||||
}
|
||||
return strings.Join(quotedCliArgs, " "), nil
|
||||
}
|
||||
|
||||
func splitVar(s string) (string, string) {
|
||||
pair := strings.SplitN(s, "=", 2)
|
||||
return pair[0], pair[1]
|
||||
}
|
||||
127
args/args_test.go
Normal file
127
args/args_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package args_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/args"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func TestArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
Args []string
|
||||
ExpectedCalls []*task.Call
|
||||
ExpectedGlobals *ast.Vars
|
||||
}{
|
||||
{
|
||||
Args: []string{"task-a", "task-b", "task-c"},
|
||||
ExpectedCalls: []*task.Call{
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
{Task: "task-c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
||||
ExpectedCalls: []*task.Call{
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
{Task: "task-c"},
|
||||
},
|
||||
ExpectedGlobals: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "FOO",
|
||||
Value: ast.Var{
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "BAR",
|
||||
Value: ast.Var{
|
||||
Value: "baz",
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "BAZ",
|
||||
Value: ast.Var{
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "CONTENT=with some spaces"},
|
||||
ExpectedCalls: []*task.Call{
|
||||
{Task: "task-a"},
|
||||
},
|
||||
ExpectedGlobals: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "CONTENT",
|
||||
Value: ast.Var{
|
||||
Value: "with some spaces",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
Args: []string{"FOO=bar", "task-a", "task-b"},
|
||||
ExpectedCalls: []*task.Call{
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
},
|
||||
ExpectedGlobals: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "FOO",
|
||||
Value: ast.Var{
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
Args: nil,
|
||||
ExpectedCalls: []*task.Call{},
|
||||
},
|
||||
{
|
||||
Args: []string{},
|
||||
ExpectedCalls: []*task.Call{},
|
||||
},
|
||||
{
|
||||
Args: []string{"FOO=bar", "BAR=baz"},
|
||||
ExpectedCalls: []*task.Call{},
|
||||
ExpectedGlobals: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "FOO",
|
||||
Value: ast.Var{
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "BAR",
|
||||
Value: ast.Var{
|
||||
Value: "baz",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
calls, globals := args.Parse(test.Args...)
|
||||
assert.Equal(t, test.ExpectedCalls, calls)
|
||||
if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 {
|
||||
assert.Equal(t, test.ExpectedGlobals, globals)
|
||||
assert.Equal(t, test.ExpectedGlobals, globals)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
11
call.go
Normal file
11
call.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package task
|
||||
|
||||
import "github.com/go-task/task/v3/taskfile/ast"
|
||||
|
||||
// Call is the parameters to a task call
|
||||
type Call struct {
|
||||
Task string
|
||||
Vars *ast.Vars
|
||||
Silent bool
|
||||
Indirect bool // True if the task was called by another task
|
||||
}
|
||||
137
cmd/release/main.go
Normal file
137
cmd/release/main.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
changelogSource = "CHANGELOG.md"
|
||||
changelogTarget = "website/src/docs/changelog.md"
|
||||
versionFile = "internal/version/version.txt"
|
||||
)
|
||||
|
||||
var changelogReleaseRegex = regexp.MustCompile(`## Unreleased`)
|
||||
|
||||
// Flags
|
||||
var (
|
||||
versionFlag bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
pflag.BoolVarP(&versionFlag, "version", "v", false, "resolved version number")
|
||||
pflag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := release(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func release() error {
|
||||
if len(pflag.Args()) != 1 {
|
||||
return errors.New("error: expected version number")
|
||||
}
|
||||
|
||||
version, err := getVersion(versionFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := bumpVersion(version, pflag.Arg(0)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if versionFlag {
|
||||
fmt.Println(version)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := changelog(version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setVersionFile(versionFile, version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVersion(filename string) (*semver.Version, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return semver.NewVersion(strings.TrimSpace(string(b)))
|
||||
}
|
||||
|
||||
func bumpVersion(version *semver.Version, verb string) error {
|
||||
switch verb {
|
||||
case "major":
|
||||
*version = version.IncMajor()
|
||||
case "minor":
|
||||
*version = version.IncMinor()
|
||||
case "patch":
|
||||
*version = version.IncPatch()
|
||||
default:
|
||||
*version = *semver.MustParse(verb)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func changelog(version *semver.Version) error {
|
||||
// Open changelog target file
|
||||
b, err := os.ReadFile(changelogTarget)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the current frontmatter
|
||||
currentChangelog := string(b)
|
||||
sections := strings.SplitN(currentChangelog, "---", 3)
|
||||
if len(sections) != 3 {
|
||||
return errors.New("error: invalid frontmatter")
|
||||
}
|
||||
frontmatter := strings.TrimSpace(sections[1])
|
||||
|
||||
// Open changelog source file
|
||||
b, err = os.ReadFile(changelogSource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changelog := string(b)
|
||||
date := time.Now().Format("2006-01-02")
|
||||
|
||||
// Replace "Unreleased" with the new version and date
|
||||
changelog = changelogReleaseRegex.ReplaceAllString(changelog, fmt.Sprintf("## v%s - %s", version, date))
|
||||
|
||||
// Write the changelog to the source file
|
||||
if err := os.WriteFile(changelogSource, []byte(changelog), 0o644); err != nil { //nolint:gosec
|
||||
return err
|
||||
}
|
||||
|
||||
// Wrap the changelog content with v-pre directive for VitePress to prevent
|
||||
// Vue from interpreting template syntax like {{.TASK_VERSION}}
|
||||
changelogWithVPre := strings.Replace(changelog, "# Changelog\n\n", "# Changelog\n\n::: v-pre\n\n", 1) + "\n:::"
|
||||
|
||||
// Add the frontmatter to the changelog
|
||||
changelogWithFrontmatter := fmt.Sprintf("---\n%s\n---\n\n%s", frontmatter, changelogWithVPre)
|
||||
|
||||
// Write the changelog to the target file
|
||||
return os.WriteFile(changelogTarget, []byte(changelogWithFrontmatter), 0o644) //nolint:gosec
|
||||
}
|
||||
|
||||
func setVersionFile(fileName string, version *semver.Version) error {
|
||||
return os.WriteFile(fileName, []byte(version.String()+"\n"), 0o644)
|
||||
}
|
||||
171
cmd/sleepit/sleepit.go
Normal file
171
cmd/sleepit/sleepit.go
Normal file
@@ -0,0 +1,171 @@
|
||||
// This code is released under the MIT License
|
||||
// Copyright (c) 2020 Marco Molteni and the timeit contributors.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
)
|
||||
|
||||
const usage = `sleepit: sleep for the specified duration, optionally handling signals
|
||||
When the line "sleepit: ready" is printed, it means that it is safe to send signals to it
|
||||
Usage: sleepit <command> [<args>]
|
||||
Commands
|
||||
default Use default action: on reception of SIGINT terminate abruptly
|
||||
handle Handle signals: on reception of SIGINT perform cleanup before exiting
|
||||
version Show the sleepit version`
|
||||
|
||||
// Filled by the linker.
|
||||
var fullVersion = "unknown" // example: v0.0.9-8-g941583d027-dirty
|
||||
|
||||
func main() {
|
||||
os.Exit(run(os.Args[1:]))
|
||||
}
|
||||
|
||||
func run(args []string) int {
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, usage)
|
||||
return 2
|
||||
}
|
||||
|
||||
defaultCmd := flag.NewFlagSet("default", flag.ExitOnError)
|
||||
defaultSleep := defaultCmd.Duration("sleep", 5*time.Second, "Sleep duration")
|
||||
|
||||
handleCmd := flag.NewFlagSet("handle", flag.ExitOnError)
|
||||
handleSleep := handleCmd.Duration("sleep", 5*time.Second, "Sleep duration")
|
||||
handleCleanup := handleCmd.Duration("cleanup", 5*time.Second, "Cleanup duration")
|
||||
handleTermAfter := handleCmd.Int("term-after", 0,
|
||||
"Terminate immediately after `N` signals.\n"+
|
||||
"Default is to terminate only when the cleanup phase has completed.")
|
||||
|
||||
versionCmd := flag.NewFlagSet("version", flag.ExitOnError)
|
||||
|
||||
switch args[0] {
|
||||
|
||||
case "default":
|
||||
_ = defaultCmd.Parse(args[1:])
|
||||
if len(defaultCmd.Args()) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "default: unexpected arguments: %v\n", defaultCmd.Args())
|
||||
return 2
|
||||
}
|
||||
return supervisor(*defaultSleep, 0, 0, nil)
|
||||
|
||||
case "handle":
|
||||
_ = handleCmd.Parse(args[1:])
|
||||
if *handleTermAfter == 1 {
|
||||
fmt.Fprintf(os.Stderr, "handle: term-after cannot be 1\n")
|
||||
return 2
|
||||
}
|
||||
if len(handleCmd.Args()) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "handle: unexpected arguments: %v\n", handleCmd.Args())
|
||||
return 2
|
||||
}
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, os.Interrupt) // Ctrl-C -> SIGINT
|
||||
return supervisor(*handleSleep, *handleCleanup, *handleTermAfter, sigCh)
|
||||
|
||||
case "version":
|
||||
_ = versionCmd.Parse(args[1:])
|
||||
if len(versionCmd.Args()) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "version: unexpected arguments: %v\n", versionCmd.Args())
|
||||
return 2
|
||||
}
|
||||
fmt.Printf("sleepit version %s\n", fullVersion)
|
||||
return 0
|
||||
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, usage)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
func supervisor(
|
||||
sleep time.Duration,
|
||||
cleanup time.Duration,
|
||||
termAfter int,
|
||||
sigCh <-chan os.Signal,
|
||||
) int {
|
||||
fmt.Printf("sleepit: ready\n")
|
||||
fmt.Printf("sleepit: PID=%d sleep=%v cleanup=%v\n",
|
||||
os.Getpid(), sleep, cleanup)
|
||||
|
||||
cancelWork := make(chan struct{})
|
||||
workerDone := worker(cancelWork, sleep, "work")
|
||||
|
||||
cancelCleaner := make(chan struct{})
|
||||
var cleanerDone <-chan struct{}
|
||||
|
||||
sigCount := 0
|
||||
for {
|
||||
select {
|
||||
case sig := <-sigCh:
|
||||
sigCount++
|
||||
fmt.Printf("sleepit: got signal=%s count=%d\n", sig, sigCount)
|
||||
if sigCount == 1 {
|
||||
// since `cancelWork` is unbuffered, sending will be synchronous:
|
||||
// we are ensured that the worker has terminated before starting cleanup.
|
||||
// This is important in some real-life situations.
|
||||
cancelWork <- struct{}{}
|
||||
cleanerDone = worker(cancelCleaner, cleanup, "cleanup")
|
||||
}
|
||||
if sigCount == termAfter {
|
||||
cancelCleaner <- struct{}{}
|
||||
return 4
|
||||
}
|
||||
case <-workerDone:
|
||||
return 0
|
||||
case <-cleanerDone:
|
||||
return 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start a worker goroutine and return immediately a `workerDone` channel.
|
||||
// The goroutine will prepend its prints with the prefix `name`.
|
||||
// The goroutine will simulate some work and will terminate when one of the following
|
||||
// conditions happens:
|
||||
// 1. When `howlong` is elapsed. This case will be signaled on the `workerDone` channel.
|
||||
// 2. When something happens on channel `canceled`. Note that this simulates real-life,
|
||||
// so cancellation is not instantaneous: if the caller wants a synchronous cancel,
|
||||
// it should send a message; if instead it wants an asynchronous cancel, it should
|
||||
// close the channel.
|
||||
func worker(
|
||||
canceled <-chan struct{},
|
||||
howlong time.Duration,
|
||||
name string,
|
||||
) <-chan struct{} {
|
||||
workerDone := make(chan struct{})
|
||||
deadline := time.Now().Add(howlong)
|
||||
go func() {
|
||||
fmt.Printf("sleepit: %s started\n", name)
|
||||
for {
|
||||
select {
|
||||
case <-canceled:
|
||||
fmt.Printf("sleepit: %s canceled\n", name)
|
||||
return
|
||||
default:
|
||||
if doSomeWork(deadline) {
|
||||
fmt.Printf("sleepit: %s done\n", name) // <== NOTE THIS LINE
|
||||
workerDone <- struct{}{}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return workerDone
|
||||
}
|
||||
|
||||
// Do some work and then return, so that the caller can decide whether to continue or not.
|
||||
// Return true when all work is done.
|
||||
func doSomeWork(deadline time.Time) bool {
|
||||
if time.Now().After(deadline) {
|
||||
return true
|
||||
}
|
||||
timeout := 100 * time.Millisecond
|
||||
time.Sleep(timeout)
|
||||
return false
|
||||
}
|
||||
251
cmd/task/task.go
251
cmd/task/task.go
@@ -1,78 +1,203 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/go-task/task"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "master"
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/args"
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/flags"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/version"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
|
||||
pflag.Usage = func() {
|
||||
fmt.Println(`task [target1 target2 ...]: Runs commands under targets like make.
|
||||
|
||||
Example: 'task hello' with the following 'Taskfile.yml' file will generate
|
||||
an 'output.txt' file.
|
||||
'''
|
||||
hello:
|
||||
cmds:
|
||||
- echo "I am going to write a file named 'output.txt' now."
|
||||
- echo "hello" > output.txt
|
||||
generates:
|
||||
- output.txt
|
||||
'''
|
||||
`)
|
||||
pflag.PrintDefaults()
|
||||
}
|
||||
|
||||
var (
|
||||
versionFlag bool
|
||||
init bool
|
||||
force bool
|
||||
watch bool
|
||||
)
|
||||
|
||||
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
||||
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yml in the current folder")
|
||||
pflag.BoolVarP(&force, "force", "f", false, "forces execution even when the task is up-to-date")
|
||||
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
|
||||
pflag.Parse()
|
||||
|
||||
if versionFlag {
|
||||
log.Printf("Task version: %s\n", version)
|
||||
return
|
||||
}
|
||||
|
||||
if init {
|
||||
if err := task.InitTaskfile(); err != nil {
|
||||
log.Fatal(err)
|
||||
if err := run(); err != nil {
|
||||
l := &logger.Logger{
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Verbose: flags.Verbose,
|
||||
Color: flags.Color,
|
||||
}
|
||||
if err, ok := err.(*errors.TaskRunError); ok && flags.ExitCode {
|
||||
emitCIErrorAnnotation(err)
|
||||
l.Errf(logger.Red, "%v\n", err)
|
||||
os.Exit(err.TaskExitCode())
|
||||
}
|
||||
if err, ok := err.(errors.TaskError); ok {
|
||||
emitCIErrorAnnotation(err)
|
||||
l.Errf(logger.Red, "%v\n", err)
|
||||
os.Exit(err.Code())
|
||||
}
|
||||
emitCIErrorAnnotation(err)
|
||||
l.Errf(logger.Red, "%v\n", err)
|
||||
os.Exit(errors.CodeUnknown)
|
||||
}
|
||||
os.Exit(errors.CodeOk)
|
||||
}
|
||||
|
||||
// emitCIErrorAnnotation emits an error annotation for supported CI providers.
|
||||
func emitCIErrorAnnotation(err error) {
|
||||
if isGA, _ := strconv.ParseBool(os.Getenv("GITHUB_ACTIONS")); !isGA {
|
||||
return
|
||||
}
|
||||
|
||||
e := task.Executor{
|
||||
Force: force,
|
||||
Watch: watch,
|
||||
}
|
||||
if err := e.ReadTaskfile(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
args := pflag.Args()
|
||||
if len(args) == 0 {
|
||||
log.Println("task: No argument given, trying default task")
|
||||
args = []string{"default"}
|
||||
}
|
||||
|
||||
if err := e.Run(args...); err != nil {
|
||||
log.Fatal(err)
|
||||
if e, ok := err.(*errors.TaskRunError); ok {
|
||||
fmt.Fprintf(os.Stdout, "::error title=Task '%s' failed::%v\n", e.TaskName, e.Err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "::error title=Task failed::%v\n", err)
|
||||
}
|
||||
|
||||
func run() error {
|
||||
log := &logger.Logger{
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Verbose: flags.Verbose,
|
||||
Color: flags.Color,
|
||||
}
|
||||
|
||||
if err := flags.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := experiments.Validate(); err != nil {
|
||||
log.Warnf("%s\n", err.Error())
|
||||
}
|
||||
|
||||
if flags.Version {
|
||||
fmt.Println(version.GetVersionWithBuildInfo())
|
||||
return nil
|
||||
}
|
||||
|
||||
if flags.Help {
|
||||
pflag.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
if flags.Experiments {
|
||||
return log.PrintExperiments()
|
||||
}
|
||||
|
||||
if flags.Init {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args, _, err := args.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := wd
|
||||
if len(args) > 0 {
|
||||
name := args[0]
|
||||
if filepathext.IsExtOnly(name) {
|
||||
name = filepathext.SmartJoin(filepath.Dir(name), "Taskfile"+filepath.Ext(name))
|
||||
}
|
||||
path = filepathext.SmartJoin(wd, name)
|
||||
}
|
||||
finalPath, err := task.InitTaskfile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !flags.Silent {
|
||||
if flags.Verbose {
|
||||
log.Outf(logger.Default, "%s\n", task.DefaultTaskfile)
|
||||
}
|
||||
log.Outf(logger.Green, "Taskfile created: %s\n", filepathext.TryAbsToRel(finalPath))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if flags.Completion != "" {
|
||||
script, err := task.Completion(flags.Completion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(script)
|
||||
return nil
|
||||
}
|
||||
|
||||
e := task.NewExecutor(
|
||||
flags.WithFlags(),
|
||||
task.WithVersionCheck(true),
|
||||
)
|
||||
if err := e.Setup(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if flags.ClearCache {
|
||||
cachePath := filepath.Join(e.TempDir.Remote, "remote")
|
||||
return os.RemoveAll(cachePath)
|
||||
}
|
||||
|
||||
listOptions := task.NewListOptions(
|
||||
flags.List,
|
||||
flags.ListAll,
|
||||
flags.ListJson,
|
||||
flags.NoStatus,
|
||||
flags.Nested,
|
||||
)
|
||||
if listOptions.ShouldListTasks() {
|
||||
if flags.Silent {
|
||||
return e.ListTaskNames(flags.ListAll)
|
||||
}
|
||||
foundTasks, err := e.ListTasks(listOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !foundTasks {
|
||||
os.Exit(errors.CodeUnknown)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse the remaining arguments
|
||||
cliArgsPreDash, cliArgsPostDash, err := args.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
calls, globals := args.Parse(cliArgsPreDash...)
|
||||
|
||||
// If there are no calls, run the default task instead
|
||||
if len(calls) == 0 {
|
||||
calls = append(calls, &task.Call{Task: "default"})
|
||||
}
|
||||
|
||||
// Merge CLI variables first (e.g. FOO=bar) so they take priority over Taskfile defaults
|
||||
e.Taskfile.Vars.Merge(globals, nil)
|
||||
|
||||
// Then ReverseMerge special variables so they're available for templating
|
||||
cliArgsPostDashQuoted, err := args.ToQuotedString(cliArgsPostDash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
specialVars := ast.NewVars()
|
||||
specialVars.Set("CLI_ARGS", ast.Var{Value: cliArgsPostDashQuoted})
|
||||
specialVars.Set("CLI_ARGS_LIST", ast.Var{Value: cliArgsPostDash})
|
||||
specialVars.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
||||
specialVars.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||
specialVars.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||
specialVars.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
|
||||
specialVars.Set("CLI_ASSUME_YES", ast.Var{Value: flags.AssumeYes})
|
||||
e.Taskfile.Vars.ReverseMerge(specialVars, nil)
|
||||
if !flags.Watch {
|
||||
e.InterceptInterruptSignals()
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if flags.Status {
|
||||
return e.Status(ctx, calls...)
|
||||
}
|
||||
|
||||
return e.Run(ctx, calls...)
|
||||
}
|
||||
|
||||
231
compiler.go
Normal file
231
compiler.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/internal/version"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
type Compiler struct {
|
||||
Dir string
|
||||
Entrypoint string
|
||||
UserWorkingDir string
|
||||
|
||||
TaskfileEnv *ast.Vars
|
||||
TaskfileVars *ast.Vars
|
||||
|
||||
Logger *logger.Logger
|
||||
|
||||
dynamicCache map[string]string
|
||||
muDynamicCache sync.Mutex
|
||||
}
|
||||
|
||||
func (c *Compiler) GetTaskfileVariables() (*ast.Vars, error) {
|
||||
return c.getVariables(nil, nil, true)
|
||||
}
|
||||
|
||||
func (c *Compiler) GetVariables(t *ast.Task, call *Call) (*ast.Vars, error) {
|
||||
return c.getVariables(t, call, true)
|
||||
}
|
||||
|
||||
func (c *Compiler) FastGetVariables(t *ast.Task, call *Call) (*ast.Vars, error) {
|
||||
return c.getVariables(t, call, false)
|
||||
}
|
||||
|
||||
func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*ast.Vars, error) {
|
||||
result := env.GetEnviron()
|
||||
specialVars, err := c.getSpecialVars(t, call)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range specialVars {
|
||||
result.Set(k, ast.Var{Value: v})
|
||||
}
|
||||
|
||||
getRangeFunc := func(dir string) func(k string, v ast.Var) error {
|
||||
return func(k string, v ast.Var) error {
|
||||
cache := &templater.Cache{Vars: result}
|
||||
// Replace values
|
||||
newVar := templater.ReplaceVar(v, cache)
|
||||
// If the variable should not be evaluated, but is nil, set it to an empty string
|
||||
// This stops empty interface errors when using the templater to replace values later
|
||||
// Preserve the Sh field so it can be displayed in summary
|
||||
if !evaluateShVars && newVar.Value == nil {
|
||||
result.Set(k, ast.Var{Value: "", Sh: newVar.Sh})
|
||||
return nil
|
||||
}
|
||||
// If the variable should not be evaluated and it is set, we can set it and return
|
||||
if !evaluateShVars {
|
||||
result.Set(k, ast.Var{Value: newVar.Value, Sh: newVar.Sh})
|
||||
return nil
|
||||
}
|
||||
// Now we can check for errors since we've handled all the cases when we don't want to evaluate
|
||||
if err := cache.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
// If the variable is already set, we can set it and return
|
||||
if newVar.Value != nil || newVar.Sh == nil {
|
||||
result.Set(k, ast.Var{Value: newVar.Value})
|
||||
return nil
|
||||
}
|
||||
// If the variable is dynamic, we need to resolve it first
|
||||
static, err := c.HandleDynamicVar(newVar, dir, env.GetFromVars(result))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.Set(k, ast.Var{Value: static})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
rangeFunc := getRangeFunc(c.Dir)
|
||||
|
||||
var taskRangeFunc func(k string, v ast.Var) error
|
||||
if t != nil {
|
||||
// NOTE(@andreynering): We're manually joining these paths here because
|
||||
// this is the raw task, not the compiled one.
|
||||
cache := &templater.Cache{Vars: result}
|
||||
dir := templater.Replace(t.Dir, cache)
|
||||
if err := cache.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir = filepathext.SmartJoin(c.Dir, dir)
|
||||
taskRangeFunc = getRangeFunc(dir)
|
||||
}
|
||||
|
||||
for k, v := range c.TaskfileEnv.All() {
|
||||
if err := rangeFunc(k, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for k, v := range c.TaskfileVars.All() {
|
||||
if err := rangeFunc(k, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if t != nil {
|
||||
for k, v := range t.IncludeVars.All() {
|
||||
if err := rangeFunc(k, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for k, v := range t.IncludedTaskfileVars.All() {
|
||||
if err := taskRangeFunc(k, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if t == nil || call == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
for k, v := range call.Vars.All() {
|
||||
if err := rangeFunc(k, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for k, v := range t.Vars.All() {
|
||||
if err := taskRangeFunc(k, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *Compiler) HandleDynamicVar(v ast.Var, dir string, e []string) (string, error) {
|
||||
c.muDynamicCache.Lock()
|
||||
defer c.muDynamicCache.Unlock()
|
||||
|
||||
// If the variable is not dynamic or it is empty, return an empty string
|
||||
if v.Sh == nil || *v.Sh == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if c.dynamicCache == nil {
|
||||
c.dynamicCache = make(map[string]string, 30)
|
||||
}
|
||||
if result, ok := c.dynamicCache[*v.Sh]; ok {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// NOTE(@andreynering): If a var have a specific dir, use this instead
|
||||
if v.Dir != "" {
|
||||
dir = v.Dir
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
opts := &execext.RunCommandOptions{
|
||||
Command: *v.Sh,
|
||||
Dir: dir,
|
||||
Stdout: &stdout,
|
||||
Stderr: c.Logger.Stderr,
|
||||
Env: e,
|
||||
}
|
||||
if err := execext.RunCommand(context.Background(), opts); err != nil {
|
||||
return "", fmt.Errorf(`task: Command "%s" failed: %s`, opts.Command, err)
|
||||
}
|
||||
|
||||
// Trim a single trailing newline from the result to make most command
|
||||
// output easier to use in shell commands.
|
||||
result := strings.TrimSuffix(stdout.String(), "\r\n")
|
||||
result = strings.TrimSuffix(result, "\n")
|
||||
|
||||
c.dynamicCache[*v.Sh] = result
|
||||
c.Logger.VerboseErrf(logger.Magenta, "task: dynamic variable: %q result: %q\n", *v.Sh, result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ResetCache clear the dynamic variables cache
|
||||
func (c *Compiler) ResetCache() {
|
||||
c.muDynamicCache.Lock()
|
||||
defer c.muDynamicCache.Unlock()
|
||||
|
||||
c.dynamicCache = nil
|
||||
}
|
||||
|
||||
func (c *Compiler) getSpecialVars(t *ast.Task, call *Call) (map[string]string, error) {
|
||||
// Use filepath.ToSlash for all paths to ensure consistent forward slashes
|
||||
// across platforms. This prevents issues with backslashes being interpreted
|
||||
// as escape sequences when paths are used in shell commands on Windows.
|
||||
allVars := map[string]string{
|
||||
"TASK_EXE": filepath.ToSlash(os.Args[0]),
|
||||
"ROOT_TASKFILE": filepath.ToSlash(filepathext.SmartJoin(c.Dir, c.Entrypoint)),
|
||||
"ROOT_DIR": filepath.ToSlash(c.Dir),
|
||||
"USER_WORKING_DIR": filepath.ToSlash(c.UserWorkingDir),
|
||||
"TASK_VERSION": version.GetVersion(),
|
||||
"PATH_LIST_SEPARATOR": string(os.PathListSeparator),
|
||||
"FILE_PATH_SEPARATOR": string(os.PathSeparator),
|
||||
}
|
||||
if t != nil {
|
||||
allVars["TASK"] = t.Task
|
||||
allVars["TASK_DIR"] = filepath.ToSlash(filepathext.SmartJoin(c.Dir, t.Dir))
|
||||
allVars["TASKFILE"] = filepath.ToSlash(t.Location.Taskfile)
|
||||
allVars["TASKFILE_DIR"] = filepath.ToSlash(filepath.Dir(t.Location.Taskfile))
|
||||
} else {
|
||||
allVars["TASK"] = ""
|
||||
allVars["TASK_DIR"] = ""
|
||||
allVars["TASKFILE"] = ""
|
||||
allVars["TASKFILE_DIR"] = ""
|
||||
}
|
||||
if call != nil {
|
||||
allVars["ALIAS"] = call.Task
|
||||
} else {
|
||||
allVars["ALIAS"] = ""
|
||||
}
|
||||
|
||||
return allVars, nil
|
||||
}
|
||||
34
completion.go
Normal file
34
completion.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//go:embed completion/bash/task.bash
|
||||
var completionBash string
|
||||
|
||||
//go:embed completion/fish/task.fish
|
||||
var completionFish string
|
||||
|
||||
//go:embed completion/ps/task.ps1
|
||||
var completionPowershell string
|
||||
|
||||
//go:embed completion/zsh/_task
|
||||
var completionZsh string
|
||||
|
||||
func Completion(completion string) (string, error) {
|
||||
// Get the file extension for the selected shell
|
||||
switch completion {
|
||||
case "bash":
|
||||
return completionBash, nil
|
||||
case "fish":
|
||||
return completionFish, nil
|
||||
case "powershell":
|
||||
return completionPowershell, nil
|
||||
case "zsh":
|
||||
return completionZsh, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown shell: %s", completion)
|
||||
}
|
||||
}
|
||||
60
completion/bash/task.bash
Normal file
60
completion/bash/task.bash
Normal file
@@ -0,0 +1,60 @@
|
||||
# vim: set tabstop=2 shiftwidth=2 expandtab:
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION='--list-all'
|
||||
TASK_CMD="${TASK_EXE:-task}"
|
||||
|
||||
function _task()
|
||||
{
|
||||
local cur prev words cword
|
||||
_init_completion -n : || return
|
||||
|
||||
# Check for `--` within command-line and quit or strip suffix.
|
||||
local i
|
||||
for i in "${!words[@]}"; do
|
||||
if [ "${words[$i]}" == "--" ]; then
|
||||
# Do not complete words following `--` passed to CLI_ARGS.
|
||||
[ $cword -gt $i ] && return
|
||||
# Remove the words following `--` to not put --list in CLI_ARGS.
|
||||
words=( "${words[@]:0:$i}" )
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Handle special arguments of options.
|
||||
case "$prev" in
|
||||
-d|--dir|--remote-cache-dir)
|
||||
_filedir -d
|
||||
return $?
|
||||
;;
|
||||
--cacert|--cert|--cert-key)
|
||||
_filedir
|
||||
return $?
|
||||
;;
|
||||
-t|--taskfile)
|
||||
_filedir yaml || return $?
|
||||
_filedir yml
|
||||
return $?
|
||||
;;
|
||||
-o|--output)
|
||||
COMPREPLY=( $( compgen -W "interleaved group prefixed" -- $cur ) )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Handle normal options.
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "$(_parse_help $1)" -- $cur ) )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Prepare task name completions.
|
||||
local tasks=( $( "${words[@]}" --silent $_GO_TASK_COMPLETION_LIST_OPTION 2> /dev/null ) )
|
||||
COMPREPLY=( $( compgen -W "${tasks[*]}" -- "$cur" ) )
|
||||
|
||||
# Post-process because task names might contain colons.
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
complete -F _task "$TASK_CMD"
|
||||
120
completion/fish/task.fish
Normal file
120
completion/fish/task.fish
Normal file
@@ -0,0 +1,120 @@
|
||||
set -l GO_TASK_PROGNAME (if set -q GO_TASK_PROGNAME; echo $GO_TASK_PROGNAME; else if set -q TASK_EXE; echo $TASK_EXE; else; echo task; end)
|
||||
|
||||
# Cache variables for experiments (global)
|
||||
set -g __task_experiments_cache ""
|
||||
set -g __task_experiments_cache_time 0
|
||||
|
||||
# Helper function to get experiments with 1-second cache
|
||||
function __task_get_experiments --inherit-variable GO_TASK_PROGNAME
|
||||
set -l now (date +%s)
|
||||
set -l ttl 1 # Cache for 1 second only
|
||||
|
||||
# Return cached value if still valid
|
||||
if test (math "$now - $__task_experiments_cache_time") -lt $ttl
|
||||
printf '%s\n' $__task_experiments_cache
|
||||
return
|
||||
end
|
||||
|
||||
# Refresh cache
|
||||
set -g __task_experiments_cache ($GO_TASK_PROGNAME --experiments 2>/dev/null)
|
||||
set -g __task_experiments_cache_time $now
|
||||
printf '%s\n' $__task_experiments_cache
|
||||
end
|
||||
|
||||
# Helper function to check if an experiment is enabled
|
||||
function __task_is_experiment_enabled
|
||||
set -l experiment $argv[1]
|
||||
__task_get_experiments | string match -qr "^\* $experiment:.*on"
|
||||
end
|
||||
|
||||
function __task_get_tasks --description "Prints all available tasks with their description" --inherit-variable GO_TASK_PROGNAME
|
||||
# Check if the global task is requested
|
||||
set -l global_task false
|
||||
commandline --current-process | read --tokenize --list --local cmd_args
|
||||
for arg in $cmd_args
|
||||
if test "_$arg" = "_--"
|
||||
break # ignore arguments to be passed to the task
|
||||
end
|
||||
if test "_$arg" = "_--global" -o "_$arg" = "_-g"
|
||||
set global_task true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Read the list of tasks (and potential errors)
|
||||
if $global_task
|
||||
$GO_TASK_PROGNAME --global --list-all
|
||||
else
|
||||
$GO_TASK_PROGNAME --list-all
|
||||
end 2>&1 | read -lz rawOutput
|
||||
|
||||
# Return on non-zero exit code (for cases when there is no Taskfile found or etc.)
|
||||
if test $status -ne 0
|
||||
return
|
||||
end
|
||||
|
||||
# Grab names and descriptions (if any) of the tasks
|
||||
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):[[:space:]]\{2,\}\(.*\)[[:space:]]\{2,\}(\(aliases.*\))/\1\t\2\t\3/' -e 's/\* \(.*\):[[:space:]]\{2,\}\(.*\)/\1\t\2/'| string split0)
|
||||
if test $output
|
||||
echo $output
|
||||
end
|
||||
end
|
||||
|
||||
complete -c $GO_TASK_PROGNAME \
|
||||
-d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was specified.' \
|
||||
-xa "(__task_get_tasks)" \
|
||||
-n "not __fish_seen_subcommand_from --"
|
||||
|
||||
# Standard flags
|
||||
complete -c $GO_TASK_PROGNAME -s a -l list-all -d 'list all tasks'
|
||||
complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)'
|
||||
complete -c $GO_TASK_PROGNAME -s C -l concurrency -d 'limit number of concurrent tasks'
|
||||
complete -c $GO_TASK_PROGNAME -l completion -d 'generate shell completion script' -xa "bash zsh fish powershell"
|
||||
complete -c $GO_TASK_PROGNAME -s d -l dir -d 'set directory of execution'
|
||||
complete -c $GO_TASK_PROGNAME -l disable-fuzzy -d 'disable fuzzy matching for task names'
|
||||
complete -c $GO_TASK_PROGNAME -s n -l dry -d 'compile and print tasks without executing'
|
||||
complete -c $GO_TASK_PROGNAME -s x -l exit-code -d 'pass-through exit code of task command'
|
||||
complete -c $GO_TASK_PROGNAME -l experiments -d 'list available experiments'
|
||||
complete -c $GO_TASK_PROGNAME -s F -l failfast -d 'when running tasks in parallel, stop all tasks if one fails'
|
||||
complete -c $GO_TASK_PROGNAME -s f -l force -d 'force execution even when up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -s g -l global -d 'run global Taskfile from home directory'
|
||||
complete -c $GO_TASK_PROGNAME -s h -l help -d 'show help'
|
||||
complete -c $GO_TASK_PROGNAME -s i -l init -d 'create new Taskfile'
|
||||
complete -c $GO_TASK_PROGNAME -l insecure -d 'allow insecure Taskfile downloads'
|
||||
complete -c $GO_TASK_PROGNAME -s I -l interval -d 'interval to watch for changes'
|
||||
complete -c $GO_TASK_PROGNAME -s j -l json -d 'format task list as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -s l -l list -d 'list tasks with descriptions'
|
||||
complete -c $GO_TASK_PROGNAME -l nested -d 'nest namespaces when listing as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -l no-status -d 'ignore status when listing as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -l interactive -d 'prompt for missing required variables'
|
||||
complete -c $GO_TASK_PROGNAME -s o -l output -d 'set output style' -xa "interleaved group prefixed"
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-begin -d 'message template before grouped output'
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-end -d 'message template after grouped output'
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-error-only -d 'hide output from successful tasks'
|
||||
complete -c $GO_TASK_PROGNAME -s p -l parallel -d 'execute tasks in parallel'
|
||||
complete -c $GO_TASK_PROGNAME -s s -l silent -d 'disable echoing'
|
||||
complete -c $GO_TASK_PROGNAME -l sort -d 'set task sorting order' -xa "default alphanumeric none"
|
||||
complete -c $GO_TASK_PROGNAME -l status -d 'exit non-zero if tasks not up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -l summary -d 'show task summary'
|
||||
complete -c $GO_TASK_PROGNAME -s t -l taskfile -d 'choose Taskfile to run'
|
||||
complete -c $GO_TASK_PROGNAME -s v -l verbose -d 'verbose output'
|
||||
complete -c $GO_TASK_PROGNAME -l version -d 'show version'
|
||||
complete -c $GO_TASK_PROGNAME -s w -l watch -d 'watch mode, re-run on changes'
|
||||
complete -c $GO_TASK_PROGNAME -s y -l yes -d 'assume yes to all prompts'
|
||||
|
||||
# Experimental flags (dynamically checked at completion time via -n condition)
|
||||
# GentleForce experiment
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled GENTLE_FORCE" -l force-all -d 'force execution of task and all dependencies'
|
||||
|
||||
# RemoteTaskfiles experiment - Options
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l offline -d 'use only local or cached Taskfiles'
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l timeout -d 'timeout for remote Taskfile downloads'
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l expiry -d 'cache expiry duration'
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l remote-cache-dir -d 'directory to cache remote Taskfiles' -xa "(__fish_complete_directories)"
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cacert -d 'custom CA certificate for TLS' -r
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cert -d 'client certificate for mTLS' -r
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cert-key -d 'client certificate private key' -r
|
||||
|
||||
# RemoteTaskfiles experiment - Operations
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l download -d 'download remote Taskfile'
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l clear-cache -d 'clear remote Taskfile cache'
|
||||
92
completion/ps/task.ps1
Normal file
92
completion/ps/task.ps1
Normal file
@@ -0,0 +1,92 @@
|
||||
using namespace System.Management.Automation
|
||||
|
||||
Register-ArgumentCompleter -CommandName task -ScriptBlock {
|
||||
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
|
||||
|
||||
if ($commandName.StartsWith('-')) {
|
||||
$completions = @(
|
||||
# Standard flags (alphabetical order)
|
||||
[CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('--list-all', '--list-all', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('-c', '-c', [CompletionResultType]::ParameterName, 'colored output'),
|
||||
[CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'colored output'),
|
||||
[CompletionResult]::new('-C', '-C', [CompletionResultType]::ParameterName, 'limit concurrent tasks'),
|
||||
[CompletionResult]::new('--concurrency', '--concurrency', [CompletionResultType]::ParameterName, 'limit concurrent tasks'),
|
||||
[CompletionResult]::new('--completion', '--completion', [CompletionResultType]::ParameterName, 'generate shell completion'),
|
||||
[CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'set directory'),
|
||||
[CompletionResult]::new('--dir', '--dir', [CompletionResultType]::ParameterName, 'set directory'),
|
||||
[CompletionResult]::new('--disable-fuzzy', '--disable-fuzzy', [CompletionResultType]::ParameterName, 'disable fuzzy matching'),
|
||||
[CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'dry run'),
|
||||
[CompletionResult]::new('--dry', '--dry', [CompletionResultType]::ParameterName, 'dry run'),
|
||||
[CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'pass-through exit code'),
|
||||
[CompletionResult]::new('--exit-code', '--exit-code', [CompletionResultType]::ParameterName, 'pass-through exit code'),
|
||||
[CompletionResult]::new('--experiments', '--experiments', [CompletionResultType]::ParameterName, 'list experiments'),
|
||||
[CompletionResult]::new('-F', '-F', [CompletionResultType]::ParameterName, 'fail fast on pallalel tasks'),
|
||||
[CompletionResult]::new('--failfast', '--failfast', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('--force', '--force', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'run global Taskfile'),
|
||||
[CompletionResult]::new('--global', '--global', [CompletionResultType]::ParameterName, 'run global Taskfile'),
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'show help'),
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'show help'),
|
||||
[CompletionResult]::new('-i', '-i', [CompletionResultType]::ParameterName, 'create new Taskfile'),
|
||||
[CompletionResult]::new('--init', '--init', [CompletionResultType]::ParameterName, 'create new Taskfile'),
|
||||
[CompletionResult]::new('--insecure', '--insecure', [CompletionResultType]::ParameterName, 'allow insecure downloads'),
|
||||
[CompletionResult]::new('-I', '-I', [CompletionResultType]::ParameterName, 'watch interval'),
|
||||
[CompletionResult]::new('--interval', '--interval', [CompletionResultType]::ParameterName, 'watch interval'),
|
||||
[CompletionResult]::new('-j', '-j', [CompletionResultType]::ParameterName, 'format as JSON'),
|
||||
[CompletionResult]::new('--json', '--json', [CompletionResultType]::ParameterName, 'format as JSON'),
|
||||
[CompletionResult]::new('-l', '-l', [CompletionResultType]::ParameterName, 'list tasks'),
|
||||
[CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'list tasks'),
|
||||
[CompletionResult]::new('--nested', '--nested', [CompletionResultType]::ParameterName, 'nest namespaces in JSON'),
|
||||
[CompletionResult]::new('--no-status', '--no-status', [CompletionResultType]::ParameterName, 'ignore status in JSON'),
|
||||
[CompletionResult]::new('--interactive', '--interactive', [CompletionResultType]::ParameterName, 'prompt for missing required variables'),
|
||||
[CompletionResult]::new('-o', '-o', [CompletionResultType]::ParameterName, 'set output style'),
|
||||
[CompletionResult]::new('--output', '--output', [CompletionResultType]::ParameterName, 'set output style'),
|
||||
[CompletionResult]::new('--output-group-begin', '--output-group-begin', [CompletionResultType]::ParameterName, 'template before group'),
|
||||
[CompletionResult]::new('--output-group-end', '--output-group-end', [CompletionResultType]::ParameterName, 'template after group'),
|
||||
[CompletionResult]::new('--output-group-error-only', '--output-group-error-only', [CompletionResultType]::ParameterName, 'hide successful output'),
|
||||
[CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'execute in parallel'),
|
||||
[CompletionResult]::new('--parallel', '--parallel', [CompletionResultType]::ParameterName, 'execute in parallel'),
|
||||
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'silent mode'),
|
||||
[CompletionResult]::new('--silent', '--silent', [CompletionResultType]::ParameterName, 'silent mode'),
|
||||
[CompletionResult]::new('--sort', '--sort', [CompletionResultType]::ParameterName, 'task sorting order'),
|
||||
[CompletionResult]::new('--status', '--status', [CompletionResultType]::ParameterName, 'check task status'),
|
||||
[CompletionResult]::new('--summary', '--summary', [CompletionResultType]::ParameterName, 'show task summary'),
|
||||
[CompletionResult]::new('-t', '-t', [CompletionResultType]::ParameterName, 'choose Taskfile'),
|
||||
[CompletionResult]::new('--taskfile', '--taskfile', [CompletionResultType]::ParameterName, 'choose Taskfile'),
|
||||
[CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'verbose output'),
|
||||
[CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'verbose output'),
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'show version'),
|
||||
[CompletionResult]::new('-w', '-w', [CompletionResultType]::ParameterName, 'watch mode'),
|
||||
[CompletionResult]::new('--watch', '--watch', [CompletionResultType]::ParameterName, 'watch mode'),
|
||||
[CompletionResult]::new('-y', '-y', [CompletionResultType]::ParameterName, 'assume yes'),
|
||||
[CompletionResult]::new('--yes', '--yes', [CompletionResultType]::ParameterName, 'assume yes')
|
||||
)
|
||||
|
||||
# Experimental flags (dynamically added based on enabled experiments)
|
||||
$experiments = & task --experiments 2>$null | Out-String
|
||||
|
||||
if ($experiments -match '\* GENTLE_FORCE:.*on') {
|
||||
$completions += [CompletionResult]::new('--force-all', '--force-all', [CompletionResultType]::ParameterName, 'force all dependencies')
|
||||
}
|
||||
|
||||
if ($experiments -match '\* REMOTE_TASKFILES:.*on') {
|
||||
# Options
|
||||
$completions += [CompletionResult]::new('--offline', '--offline', [CompletionResultType]::ParameterName, 'use cached Taskfiles')
|
||||
$completions += [CompletionResult]::new('--timeout', '--timeout', [CompletionResultType]::ParameterName, 'download timeout')
|
||||
$completions += [CompletionResult]::new('--expiry', '--expiry', [CompletionResultType]::ParameterName, 'cache expiry')
|
||||
$completions += [CompletionResult]::new('--remote-cache-dir', '--remote-cache-dir', [CompletionResultType]::ParameterName, 'cache directory')
|
||||
$completions += [CompletionResult]::new('--cacert', '--cacert', [CompletionResultType]::ParameterName, 'custom CA certificate')
|
||||
$completions += [CompletionResult]::new('--cert', '--cert', [CompletionResultType]::ParameterName, 'client certificate')
|
||||
$completions += [CompletionResult]::new('--cert-key', '--cert-key', [CompletionResultType]::ParameterName, 'client private key')
|
||||
# Operations
|
||||
$completions += [CompletionResult]::new('--download', '--download', [CompletionResultType]::ParameterName, 'download remote Taskfile')
|
||||
$completions += [CompletionResult]::new('--clear-cache', '--clear-cache', [CompletionResultType]::ParameterName, 'clear cache')
|
||||
}
|
||||
|
||||
return $completions.Where{ $_.CompletionText.StartsWith($commandName) }
|
||||
}
|
||||
|
||||
return $(task --list-all --silent) | Where-Object { $_.StartsWith($commandName) } | ForEach-Object { return $_ + " " }
|
||||
}
|
||||
154
completion/zsh/_task
Executable file
154
completion/zsh/_task
Executable file
@@ -0,0 +1,154 @@
|
||||
#compdef task
|
||||
typeset -A opt_args
|
||||
TASK_CMD="${TASK_EXE:-task}"
|
||||
compdef _task "$TASK_CMD"
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}"
|
||||
|
||||
# Check if an experiment is enabled
|
||||
function __task_is_experiment_enabled() {
|
||||
local experiment=$1
|
||||
task --experiments 2>/dev/null | grep -q "^\* ${experiment}:.*on"
|
||||
}
|
||||
|
||||
# Listing commands from Taskfile.yml
|
||||
function __task_list() {
|
||||
local -a scripts cmd
|
||||
local -i enabled=0
|
||||
local taskfile item task desc
|
||||
|
||||
cmd=($TASK_CMD)
|
||||
taskfile=${(Qv)opt_args[(i)-t|--taskfile]}
|
||||
taskfile=${taskfile//\~/$HOME}
|
||||
|
||||
for arg in "${words[@]:0:$CURRENT}"; do
|
||||
if [[ "$arg" = "--" ]]; then
|
||||
# Use default completion for words after `--` as they are CLI_ARGS.
|
||||
_default
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$taskfile" && -f "$taskfile" ]]; then
|
||||
cmd+=(--taskfile "$taskfile")
|
||||
fi
|
||||
|
||||
# Check if global flag is set
|
||||
if (( ${+opt_args[-g]} || ${+opt_args[--global]} )); then
|
||||
cmd+=(--global)
|
||||
fi
|
||||
|
||||
if output=$("${cmd[@]}" $_GO_TASK_COMPLETION_LIST_OPTION 2>/dev/null); then
|
||||
enabled=1
|
||||
fi
|
||||
|
||||
(( enabled )) || return 0
|
||||
|
||||
scripts=()
|
||||
|
||||
# Read zstyle verbose option (default = true via -T)
|
||||
local show_desc
|
||||
zstyle -T ":completion:${curcontext}:" verbose && show_desc=true || show_desc=false
|
||||
|
||||
for item in "${(@)${(f)output}[2,-1]#\* }"; do
|
||||
task="${item%%:[[:space:]]*}"
|
||||
|
||||
if [[ "$show_desc" == "true" ]]; then
|
||||
local desc="${item##[^[:space:]]##[[:space:]]##}"
|
||||
scripts+=( "${task//:/\\:}:$desc" )
|
||||
else
|
||||
scripts+=( "$task" )
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$show_desc" == "true" ]]; then
|
||||
_describe 'Task to run' scripts
|
||||
else
|
||||
compadd -Q -a scripts
|
||||
fi
|
||||
}
|
||||
|
||||
_task() {
|
||||
local -a standard_args operation_args
|
||||
|
||||
standard_args=(
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: '
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]'
|
||||
'(-F --failfast)'{-F,--failfast}'[when running tasks in parallel, stop all tasks if one fails]'
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]'
|
||||
'(-c --color)'{-c,--color}'[colored output]'
|
||||
'(--completion)--completion[generate shell completion script]:shell:(bash zsh fish powershell)'
|
||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs'
|
||||
'(--disable-fuzzy)--disable-fuzzy[disable fuzzy matching for task names]'
|
||||
'(-n --dry)'{-n,--dry}'[compiles and prints tasks without executing]'
|
||||
'(--dry)--dry[dry-run mode, compile and print tasks only]'
|
||||
'(-x --exit-code)'{-x,--exit-code}'[pass-through exit code of task command]'
|
||||
'(--experiments)--experiments[list available experiments]'
|
||||
'(-g --global)'{-g,--global}'[run global Taskfile from home directory]'
|
||||
'(--insecure)--insecure[allow insecure Taskfile downloads]'
|
||||
'(-I --interval)'{-I,--interval}'[interval to watch for changes]:duration: '
|
||||
'(-j --json)'{-j,--json}'[format task list as JSON]'
|
||||
'(--nested)--nested[nest namespaces when listing as JSON]'
|
||||
'(--no-status)--no-status[ignore status when listing as JSON]'
|
||||
'(--interactive)--interactive[prompt for missing required variables]'
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)'
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: '
|
||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: '
|
||||
'(--output-group-error-only)--output-group-error-only[hide output from successful tasks]'
|
||||
'(-s --silent)'{-s,--silent}'[disable echoing]'
|
||||
'(--sort)--sort[set task sorting order]:order:(default alphanumeric none)'
|
||||
'(--status)--status[exit non-zero if supplied tasks not up-to-date]'
|
||||
'(--summary)--summary[show summary\: field from tasks instead of running them]'
|
||||
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files'
|
||||
'(-v --verbose)'{-v,--verbose}'[verbose mode]'
|
||||
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]'
|
||||
'(-y --yes)'{-y,--yes}'[assume yes to all prompts]'
|
||||
)
|
||||
|
||||
# Experimental flags (dynamically added based on enabled experiments)
|
||||
# Options (modify behavior)
|
||||
if __task_is_experiment_enabled "GENTLE_FORCE"; then
|
||||
standard_args+=('(--force-all)--force-all[force execution of task and all dependencies]')
|
||||
fi
|
||||
|
||||
if __task_is_experiment_enabled "REMOTE_TASKFILES"; then
|
||||
standard_args+=(
|
||||
'(--offline --download)--offline[use only local or cached Taskfiles]'
|
||||
'(--timeout)--timeout[timeout for remote Taskfile downloads]:duration: '
|
||||
'(--expiry)--expiry[cache expiry duration]:duration: '
|
||||
'(--remote-cache-dir)--remote-cache-dir[directory to cache remote Taskfiles]:cache dir:_dirs'
|
||||
'(--cacert)--cacert[custom CA certificate for TLS]:file:_files'
|
||||
'(--cert)--cert[client certificate for mTLS]:file:_files'
|
||||
'(--cert-key)--cert-key[client certificate private key]:file:_files'
|
||||
)
|
||||
fi
|
||||
|
||||
operation_args=(
|
||||
# Task names completion (can be specified multiple times)
|
||||
'(operation)*: :__task_list'
|
||||
# Operational args completion (mutually exclusive)
|
||||
+ '(operation)'
|
||||
'(*)'{-l,--list}'[list describable tasks]'
|
||||
'(*)'{-a,--list-all}'[list all tasks]'
|
||||
'(*)'{-i,--init}'[create new Taskfile.yml]'
|
||||
'(- *)'{-h,--help}'[show help]'
|
||||
'(- *)--version[show version and exit]'
|
||||
)
|
||||
|
||||
# Experimental operations (dynamically added based on enabled experiments)
|
||||
if __task_is_experiment_enabled "REMOTE_TASKFILES"; then
|
||||
standard_args+=(
|
||||
'(--offline --clear-cache)--download[download remote Taskfile]'
|
||||
)
|
||||
operation_args+=(
|
||||
'(* --download)--clear-cache[clear remote Taskfile cache]'
|
||||
)
|
||||
fi
|
||||
|
||||
_arguments -S $standard_args $operation_args
|
||||
}
|
||||
|
||||
# don't run the completion function when being source-ed or eval-ed
|
||||
if [ "$funcstack[1]" = "_task" ]; then
|
||||
_task "$@"
|
||||
fi
|
||||
25
concurrency.go
Normal file
25
concurrency.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package task
|
||||
|
||||
func (e *Executor) acquireConcurrencyLimit() func() {
|
||||
if e.concurrencySemaphore == nil {
|
||||
return emptyFunc
|
||||
}
|
||||
|
||||
e.concurrencySemaphore <- struct{}{}
|
||||
return func() {
|
||||
<-e.concurrencySemaphore
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) releaseConcurrencyLimit() func() {
|
||||
if e.concurrencySemaphore == nil {
|
||||
return emptyFunc
|
||||
}
|
||||
|
||||
<-e.concurrencySemaphore
|
||||
return func() {
|
||||
e.concurrencySemaphore <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func emptyFunc() {}
|
||||
29
cyclic.go
29
cyclic.go
@@ -1,29 +0,0 @@
|
||||
package task
|
||||
|
||||
// HasCyclicDep checks if a task tree has any cyclic dependency
|
||||
func (e *Executor) HasCyclicDep() bool {
|
||||
visits := make(map[string]struct{}, len(e.Tasks))
|
||||
|
||||
var checkCyclicDep func(string, *Task) bool
|
||||
checkCyclicDep = func(name string, t *Task) bool {
|
||||
if _, ok := visits[name]; ok {
|
||||
return false
|
||||
}
|
||||
visits[name] = struct{}{}
|
||||
defer delete(visits, name)
|
||||
|
||||
for _, d := range t.Deps {
|
||||
if !checkCyclicDep(d, e.Tasks[d]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
for k, v := range e.Tasks {
|
||||
if !checkCyclicDep(k, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package task_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task"
|
||||
)
|
||||
|
||||
func TestCyclicDepCheck(t *testing.T) {
|
||||
isCyclic := &task.Executor{
|
||||
Tasks: task.Tasks{
|
||||
"task-a": &task.Task{
|
||||
Deps: []string{"task-b"},
|
||||
},
|
||||
"task-b": &task.Task{
|
||||
Deps: []string{"task-a"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !isCyclic.HasCyclicDep() {
|
||||
t.Error("Task should be cyclic")
|
||||
}
|
||||
|
||||
isNotCyclic := &task.Executor{
|
||||
Tasks: task.Tasks{
|
||||
"task-a": &task.Task{
|
||||
Deps: []string{"task-c"},
|
||||
},
|
||||
"task-b": &task.Task{
|
||||
Deps: []string{"task-c"},
|
||||
},
|
||||
"task-c": &task.Task{},
|
||||
},
|
||||
}
|
||||
|
||||
if isNotCyclic.HasCyclicDep() {
|
||||
t.Error("Task should not be cyclic")
|
||||
}
|
||||
}
|
||||
54
errors.go
54
errors.go
@@ -1,54 +0,0 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCyclicDependencyDetected is returned when a cyclic dependency was found in the Taskfile
|
||||
ErrCyclicDependencyDetected = errors.New("task: cyclic dependency detected")
|
||||
// ErrTaskfileAlreadyExists is returned on creating a Taskfile if one already exists
|
||||
ErrTaskfileAlreadyExists = errors.New("task: A Taskfile already exists")
|
||||
)
|
||||
|
||||
type taskFileNotFound struct {
|
||||
taskFile string
|
||||
}
|
||||
|
||||
func (err taskFileNotFound) Error() string {
|
||||
return fmt.Sprintf(`task: No task file found (is it named "%s"?). Use "task --init" to create a new one`, err.taskFile)
|
||||
}
|
||||
|
||||
type taskNotFoundError struct {
|
||||
taskName string
|
||||
}
|
||||
|
||||
func (err *taskNotFoundError) Error() string {
|
||||
return fmt.Sprintf(`task: Task "%s" not found`, err.taskName)
|
||||
}
|
||||
|
||||
type taskRunError struct {
|
||||
taskName string
|
||||
err error
|
||||
}
|
||||
|
||||
func (err *taskRunError) Error() string {
|
||||
return fmt.Sprintf(`task: Failed to run task "%s": %v`, err.taskName, err.err)
|
||||
}
|
||||
|
||||
type cyclicDepError struct {
|
||||
taskName string
|
||||
}
|
||||
|
||||
func (err *cyclicDepError) Error() string {
|
||||
return fmt.Sprintf(`task: Cyclic dependency of task "%s" detected`, err.taskName)
|
||||
}
|
||||
|
||||
type cantWatchNoSourcesError struct {
|
||||
taskName string
|
||||
}
|
||||
|
||||
func (err *cantWatchNoSourcesError) Error() string {
|
||||
return fmt.Sprintf(`task: Can't watch task "%s" because it has no specified sources`, err.taskName)
|
||||
}
|
||||
127
errors/error_taskfile_decode.go
Normal file
127
errors/error_taskfile_decode.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
type (
|
||||
TaskfileDecodeError struct {
|
||||
Message string
|
||||
Location string
|
||||
Line int
|
||||
Column int
|
||||
Tag string
|
||||
Snippet string
|
||||
Err error
|
||||
}
|
||||
)
|
||||
|
||||
func NewTaskfileDecodeError(err error, node *yaml.Node) *TaskfileDecodeError {
|
||||
// If the error is already a DecodeError, return it
|
||||
taskfileInvalidErr := &TaskfileDecodeError{}
|
||||
if errors.As(err, &taskfileInvalidErr) {
|
||||
return taskfileInvalidErr
|
||||
}
|
||||
return &TaskfileDecodeError{
|
||||
Line: node.Line,
|
||||
Column: node.Column,
|
||||
Tag: node.ShortTag(),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) Error() string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
// Print the error message
|
||||
if err.Message != "" {
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", err.Message))
|
||||
} else {
|
||||
// Extract the errors from the TypeError
|
||||
te := &yaml.TypeError{}
|
||||
if errors.As(err.Err, &te) {
|
||||
if len(te.Errors) > 1 {
|
||||
fmt.Fprintln(buf, color.RedString("errs:"))
|
||||
for _, message := range te.Errors {
|
||||
fmt.Fprintln(buf, color.RedString("- %s", message))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", te.Errors[0]))
|
||||
}
|
||||
} else {
|
||||
// Otherwise print the error message normally
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", err.Err))
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(buf, color.RedString("file: %s:%d:%d", err.Location, err.Line, err.Column))
|
||||
fmt.Fprint(buf, err.Snippet)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) Debug() string {
|
||||
const indentWidth = 2
|
||||
buf := &bytes.Buffer{}
|
||||
fmt.Fprintln(buf, "TaskfileDecodeError:")
|
||||
|
||||
// Recursively loop through the error chain and print any details
|
||||
var debug func(error, int)
|
||||
debug = func(err error, indent int) {
|
||||
indentStr := strings.Repeat(" ", indent*indentWidth)
|
||||
|
||||
// Nothing left to unwrap
|
||||
if err == nil {
|
||||
fmt.Fprintf(buf, "%sEnd of chain\n", indentStr)
|
||||
return
|
||||
}
|
||||
|
||||
// Taskfile decode error
|
||||
decodeErr := &TaskfileDecodeError{}
|
||||
if errors.As(err, &decodeErr) {
|
||||
fmt.Fprintf(buf, "%s%s (%s:%d:%d)\n",
|
||||
indentStr,
|
||||
cmp.Or(decodeErr.Message, "<no_message>"),
|
||||
decodeErr.Location,
|
||||
decodeErr.Line,
|
||||
decodeErr.Column,
|
||||
)
|
||||
debug(errors.Unwrap(err), indent+1)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(buf, "%s%s\n", indentStr, err)
|
||||
debug(errors.Unwrap(err), indent+1)
|
||||
}
|
||||
debug(err, 0)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) Unwrap() error {
|
||||
return err.Err
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) Code() int {
|
||||
return CodeTaskfileDecode
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) WithMessage(format string, a ...any) *TaskfileDecodeError {
|
||||
err.Message = fmt.Sprintf(format, a...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) WithTypeMessage(t string) *TaskfileDecodeError {
|
||||
err.Message = fmt.Sprintf("cannot unmarshal %s into %s", err.Tag, t)
|
||||
return err
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) WithFileInfo(location string, snippet string) *TaskfileDecodeError {
|
||||
err.Location = location
|
||||
err.Snippet = snippet
|
||||
return err
|
||||
}
|
||||
72
errors/errors.go
Normal file
72
errors/errors.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package errors
|
||||
|
||||
import "errors"
|
||||
|
||||
// General exit codes
|
||||
const (
|
||||
CodeOk int = iota // Used when the program exits without errors
|
||||
CodeUnknown // Used when no other exit code is appropriate
|
||||
)
|
||||
|
||||
// TaskRC related exit codes
|
||||
const (
|
||||
CodeTaskRCNotFoundError int = iota + 50
|
||||
)
|
||||
|
||||
// Taskfile related exit codes
|
||||
const (
|
||||
CodeTaskfileNotFound int = iota + 100
|
||||
CodeTaskfileAlreadyExists
|
||||
CodeTaskfileDecode
|
||||
CodeTaskfileFetchFailed
|
||||
CodeTaskfileNotTrusted
|
||||
CodeTaskfileNotSecure
|
||||
CodeTaskfileCacheNotFound
|
||||
CodeTaskfileVersionCheckError
|
||||
CodeTaskfileNetworkTimeout
|
||||
CodeTaskfileInvalid
|
||||
CodeTaskfileCycle
|
||||
CodeTaskfileDoesNotMatchChecksum
|
||||
)
|
||||
|
||||
// Task related exit codes
|
||||
const (
|
||||
CodeTaskNotFound int = iota + 200
|
||||
CodeTaskRunError
|
||||
CodeTaskInternal
|
||||
CodeTaskNameConflict
|
||||
CodeTaskCalledTooManyTimes
|
||||
CodeTaskCancelled
|
||||
CodeTaskMissingRequiredVars
|
||||
CodeTaskNotAllowedVars
|
||||
)
|
||||
|
||||
// TaskError extends the standard error interface with a Code method. This code will
|
||||
// be used as the exit code of the program which allows the user to distinguish
|
||||
// between different types of errors.
|
||||
type TaskError interface {
|
||||
error
|
||||
Code() int
|
||||
}
|
||||
|
||||
// New returns an error that formats as the given text. Each call to New returns
|
||||
// a distinct error value even if the text is identical. This wraps the standard
|
||||
// errors.New function so that we don't need to alias that package.
|
||||
func New(text string) error {
|
||||
return errors.New(text)
|
||||
}
|
||||
|
||||
// Is wraps the standard errors.Is function so that we don't need to alias that package.
|
||||
func Is(err, target error) bool {
|
||||
return errors.Is(err, target)
|
||||
}
|
||||
|
||||
// As wraps the standard errors.As function so that we don't need to alias that package.
|
||||
func As(err error, target any) bool {
|
||||
return errors.As(err, target)
|
||||
}
|
||||
|
||||
// Unwrap wraps the standard errors.Unwrap function so that we don't need to alias that package.
|
||||
func Unwrap(err error) error {
|
||||
return errors.Unwrap(err)
|
||||
}
|
||||
208
errors/errors_task.go
Normal file
208
errors/errors_task.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
)
|
||||
|
||||
// TaskNotFoundError is returned when the specified task is not found in the
|
||||
// Taskfile.
|
||||
type TaskNotFoundError struct {
|
||||
TaskName string
|
||||
DidYouMean string
|
||||
}
|
||||
|
||||
func (err *TaskNotFoundError) Error() string {
|
||||
if err.DidYouMean != "" {
|
||||
return fmt.Sprintf(
|
||||
`task: Task %q does not exist. Did you mean %q?`,
|
||||
err.TaskName,
|
||||
err.DidYouMean,
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`task: Task %q does not exist`, err.TaskName)
|
||||
}
|
||||
|
||||
func (err *TaskNotFoundError) Code() int {
|
||||
return CodeTaskNotFound
|
||||
}
|
||||
|
||||
// TaskRunError is returned when a command in a task returns a non-zero exit
|
||||
// code.
|
||||
type TaskRunError struct {
|
||||
TaskName string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (err *TaskRunError) Error() string {
|
||||
return fmt.Sprintf(`task: Failed to run task %q: %v`, err.TaskName, err.Err)
|
||||
}
|
||||
|
||||
func (err *TaskRunError) Code() int {
|
||||
return CodeTaskRunError
|
||||
}
|
||||
|
||||
func (err *TaskRunError) TaskExitCode() int {
|
||||
var exit interp.ExitStatus
|
||||
if errors.As(err.Err, &exit) {
|
||||
return int(exit)
|
||||
}
|
||||
return err.Code()
|
||||
}
|
||||
|
||||
func (err *TaskRunError) Unwrap() error {
|
||||
return err.Err
|
||||
}
|
||||
|
||||
// TaskInternalError when the user attempts to invoke a task that is internal.
|
||||
type TaskInternalError struct {
|
||||
TaskName string
|
||||
}
|
||||
|
||||
func (err *TaskInternalError) Error() string {
|
||||
return fmt.Sprintf(`task: Task "%s" is internal`, err.TaskName)
|
||||
}
|
||||
|
||||
func (err *TaskInternalError) Code() int {
|
||||
return CodeTaskInternal
|
||||
}
|
||||
|
||||
// TaskNameConflictError is returned when multiple tasks with a matching name or
|
||||
// alias are found.
|
||||
type TaskNameConflictError struct {
|
||||
Call string
|
||||
TaskNames []string
|
||||
}
|
||||
|
||||
func (err *TaskNameConflictError) Error() string {
|
||||
return fmt.Sprintf(`task: Found multiple tasks (%s) that match %q`, strings.Join(err.TaskNames, ", "), err.Call)
|
||||
}
|
||||
|
||||
func (err *TaskNameConflictError) Code() int {
|
||||
return CodeTaskNameConflict
|
||||
}
|
||||
|
||||
type TaskNameFlattenConflictError struct {
|
||||
TaskName string
|
||||
Include string
|
||||
}
|
||||
|
||||
func (err *TaskNameFlattenConflictError) Error() string {
|
||||
return fmt.Sprintf(`task: Found multiple tasks (%s) included by "%s""`, err.TaskName, err.Include)
|
||||
}
|
||||
|
||||
func (err *TaskNameFlattenConflictError) Code() int {
|
||||
return CodeTaskNameConflict
|
||||
}
|
||||
|
||||
// TaskCalledTooManyTimesError is returned when the maximum task call limit is
|
||||
// exceeded. This is to prevent infinite loops and cyclic dependencies.
|
||||
type TaskCalledTooManyTimesError struct {
|
||||
TaskName string
|
||||
MaximumTaskCall int
|
||||
}
|
||||
|
||||
func (err *TaskCalledTooManyTimesError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: Maximum task call exceeded (%d) for task %q: probably an cyclic dep or infinite loop`,
|
||||
err.MaximumTaskCall,
|
||||
err.TaskName,
|
||||
)
|
||||
}
|
||||
|
||||
func (err *TaskCalledTooManyTimesError) Code() int {
|
||||
return CodeTaskCalledTooManyTimes
|
||||
}
|
||||
|
||||
// TaskCancelledByUserError is returned when the user does not accept an optional prompt to continue.
|
||||
type TaskCancelledByUserError struct {
|
||||
TaskName string
|
||||
}
|
||||
|
||||
func (err *TaskCancelledByUserError) Error() string {
|
||||
return fmt.Sprintf(`task: Task %q cancelled by user`, err.TaskName)
|
||||
}
|
||||
|
||||
func (err *TaskCancelledByUserError) Code() int {
|
||||
return CodeTaskCancelled
|
||||
}
|
||||
|
||||
// TaskCancelledNoTerminalError is returned when trying to run a task with a prompt in a non-terminal environment.
|
||||
type TaskCancelledNoTerminalError struct {
|
||||
TaskName string
|
||||
}
|
||||
|
||||
func (err *TaskCancelledNoTerminalError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: Task %q cancelled because it has a prompt and the environment is not a terminal. Use --yes (-y) to run anyway.`,
|
||||
err.TaskName,
|
||||
)
|
||||
}
|
||||
|
||||
func (err *TaskCancelledNoTerminalError) Code() int {
|
||||
return CodeTaskCancelled
|
||||
}
|
||||
|
||||
// TaskMissingRequiredVarsError is returned when a task is missing required variables.
|
||||
|
||||
type MissingVar struct {
|
||||
Name string
|
||||
AllowedValues []string
|
||||
}
|
||||
type TaskMissingRequiredVarsError struct {
|
||||
TaskName string
|
||||
MissingVars []MissingVar
|
||||
}
|
||||
|
||||
func (v MissingVar) String() string {
|
||||
if len(v.AllowedValues) == 0 {
|
||||
return v.Name
|
||||
}
|
||||
return fmt.Sprintf("%s (allowed values: %v)", v.Name, v.AllowedValues)
|
||||
}
|
||||
|
||||
func (err *TaskMissingRequiredVarsError) Error() string {
|
||||
vars := make([]string, 0, len(err.MissingVars))
|
||||
for _, v := range err.MissingVars {
|
||||
vars = append(vars, v.String())
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
`task: Task %q cancelled because it is missing required variables: %s`,
|
||||
err.TaskName,
|
||||
strings.Join(vars, ", "))
|
||||
}
|
||||
|
||||
func (err *TaskMissingRequiredVarsError) Code() int {
|
||||
return CodeTaskMissingRequiredVars
|
||||
}
|
||||
|
||||
type NotAllowedVar struct {
|
||||
Value string
|
||||
Enum []string
|
||||
Name string
|
||||
}
|
||||
|
||||
type TaskNotAllowedVarsError struct {
|
||||
TaskName string
|
||||
NotAllowedVars []NotAllowedVar
|
||||
}
|
||||
|
||||
func (err *TaskNotAllowedVarsError) Error() string {
|
||||
var builder strings.Builder
|
||||
|
||||
builder.WriteString(fmt.Sprintf("task: Task %q cancelled because it is missing required variables:\n", err.TaskName)) //nolint:staticcheck
|
||||
for _, s := range err.NotAllowedVars {
|
||||
builder.WriteString(fmt.Sprintf(" - %s has an invalid value : '%s' (allowed values : %v)\n", s.Name, s.Value, s.Enum)) //nolint:staticcheck
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func (err *TaskNotAllowedVarsError) Code() int {
|
||||
return CodeTaskNotAllowedVars
|
||||
}
|
||||
218
errors/errors_taskfile.go
Normal file
218
errors/errors_taskfile.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
)
|
||||
|
||||
// TaskfileNotFoundError is returned when no appropriate Taskfile is found when
|
||||
// searching the filesystem.
|
||||
type TaskfileNotFoundError struct {
|
||||
URI string
|
||||
Walk bool
|
||||
AskInit bool
|
||||
OwnerChange bool
|
||||
}
|
||||
|
||||
func (err TaskfileNotFoundError) Error() string {
|
||||
var walkText string
|
||||
if err.OwnerChange {
|
||||
walkText = " (or any of the parent directories until ownership changed)."
|
||||
} else if err.Walk {
|
||||
walkText = " (or any of the parent directories)."
|
||||
}
|
||||
if err.AskInit {
|
||||
walkText += " Run `task --init` to create a new Taskfile."
|
||||
}
|
||||
return fmt.Sprintf(`task: No Taskfile found at %q%s`, filepath.ToSlash(err.URI), walkText)
|
||||
}
|
||||
|
||||
func (err TaskfileNotFoundError) Code() int {
|
||||
return CodeTaskfileNotFound
|
||||
}
|
||||
|
||||
// TaskfileAlreadyExistsError is returned on creating a Taskfile if one already
|
||||
// exists.
|
||||
type TaskfileAlreadyExistsError struct{}
|
||||
|
||||
func (err TaskfileAlreadyExistsError) Error() string {
|
||||
return "task: A Taskfile already exists"
|
||||
}
|
||||
|
||||
func (err TaskfileAlreadyExistsError) Code() int {
|
||||
return CodeTaskfileAlreadyExists
|
||||
}
|
||||
|
||||
// TaskfileInvalidError is returned when the Taskfile contains syntax errors or
|
||||
// cannot be parsed for some reason.
|
||||
type TaskfileInvalidError struct {
|
||||
URI string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (err TaskfileInvalidError) Error() string {
|
||||
return fmt.Sprintf("task: Failed to parse %s:\n%v", filepath.ToSlash(err.URI), err.Err)
|
||||
}
|
||||
|
||||
func (err TaskfileInvalidError) Code() int {
|
||||
return CodeTaskfileInvalid
|
||||
}
|
||||
|
||||
// TaskfileFetchFailedError is returned when no appropriate Taskfile is found when
|
||||
// searching the filesystem.
|
||||
type TaskfileFetchFailedError struct {
|
||||
URI string
|
||||
HTTPStatusCode int
|
||||
}
|
||||
|
||||
func (err TaskfileFetchFailedError) Error() string {
|
||||
var statusText string
|
||||
if err.HTTPStatusCode != 0 {
|
||||
statusText = fmt.Sprintf(" with status code %d (%s)", err.HTTPStatusCode, http.StatusText(err.HTTPStatusCode))
|
||||
}
|
||||
return fmt.Sprintf(`task: Download of %q failed%s`, filepath.ToSlash(err.URI), statusText)
|
||||
}
|
||||
|
||||
func (err TaskfileFetchFailedError) Code() int {
|
||||
return CodeTaskfileFetchFailed
|
||||
}
|
||||
|
||||
// TaskfileNotTrustedError is returned when the user does not accept the trust
|
||||
// prompt when downloading a remote Taskfile.
|
||||
type TaskfileNotTrustedError struct {
|
||||
URI string
|
||||
}
|
||||
|
||||
func (err *TaskfileNotTrustedError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: Taskfile %q not trusted by user`,
|
||||
filepath.ToSlash(err.URI),
|
||||
)
|
||||
}
|
||||
|
||||
func (err *TaskfileNotTrustedError) Code() int {
|
||||
return CodeTaskfileNotTrusted
|
||||
}
|
||||
|
||||
// TaskfileNotSecureError is returned when the user attempts to download a
|
||||
// remote Taskfile over an insecure connection.
|
||||
type TaskfileNotSecureError struct {
|
||||
URI string
|
||||
}
|
||||
|
||||
func (err *TaskfileNotSecureError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: Taskfile %q cannot be downloaded over an insecure connection. You can override this by using the --insecure flag`,
|
||||
filepath.ToSlash(err.URI),
|
||||
)
|
||||
}
|
||||
|
||||
func (err *TaskfileNotSecureError) Code() int {
|
||||
return CodeTaskfileNotSecure
|
||||
}
|
||||
|
||||
// TaskfileCacheNotFoundError is returned when the user attempts to use an offline
|
||||
// (cached) Taskfile but the files does not exist in the cache.
|
||||
type TaskfileCacheNotFoundError struct {
|
||||
URI string
|
||||
}
|
||||
|
||||
func (err *TaskfileCacheNotFoundError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: Taskfile %q was not found in the cache. Remove the --offline flag to use a remote copy or download it using the --download flag`,
|
||||
filepath.ToSlash(err.URI),
|
||||
)
|
||||
}
|
||||
|
||||
func (err *TaskfileCacheNotFoundError) Code() int {
|
||||
return CodeTaskfileCacheNotFound
|
||||
}
|
||||
|
||||
// TaskfileVersionCheckError is returned when the user attempts to run a
|
||||
// Taskfile that does not contain a Taskfile schema version key or if they try
|
||||
// to use a feature that is not supported by the schema version.
|
||||
type TaskfileVersionCheckError struct {
|
||||
URI string
|
||||
SchemaVersion *semver.Version
|
||||
Message string
|
||||
}
|
||||
|
||||
func (err *TaskfileVersionCheckError) Error() string {
|
||||
if err.SchemaVersion == nil {
|
||||
return fmt.Sprintf(
|
||||
`task: Missing schema version in Taskfile %q`,
|
||||
filepath.ToSlash(err.URI),
|
||||
)
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"task: Invalid schema version in Taskfile %q:\nSchema version (%s) %s",
|
||||
filepath.ToSlash(err.URI),
|
||||
err.SchemaVersion.String(),
|
||||
err.Message,
|
||||
)
|
||||
}
|
||||
|
||||
func (err *TaskfileVersionCheckError) Code() int {
|
||||
return CodeTaskfileVersionCheckError
|
||||
}
|
||||
|
||||
// TaskfileNetworkTimeoutError is returned when the user attempts to use a remote
|
||||
// Taskfile but a network connection could not be established within the timeout.
|
||||
type TaskfileNetworkTimeoutError struct {
|
||||
URI string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func (err *TaskfileNetworkTimeoutError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: Network connection timed out after %s while attempting to download Taskfile %q`,
|
||||
err.Timeout, filepath.ToSlash(err.URI),
|
||||
)
|
||||
}
|
||||
|
||||
func (err *TaskfileNetworkTimeoutError) Code() int {
|
||||
return CodeTaskfileNetworkTimeout
|
||||
}
|
||||
|
||||
// TaskfileCycleError is returned when we detect that a Taskfile includes a
|
||||
// set of Taskfiles that include each other in a cycle.
|
||||
type TaskfileCycleError struct {
|
||||
Source string
|
||||
Destination string
|
||||
}
|
||||
|
||||
func (err TaskfileCycleError) Error() string {
|
||||
return fmt.Sprintf("task: include cycle detected between %s <--> %s",
|
||||
filepath.ToSlash(err.Source),
|
||||
filepath.ToSlash(err.Destination),
|
||||
)
|
||||
}
|
||||
|
||||
func (err TaskfileCycleError) Code() int {
|
||||
return CodeTaskfileCycle
|
||||
}
|
||||
|
||||
// TaskfileDoesNotMatchChecksum is returned when a Taskfile's checksum does not
|
||||
// match the one pinned in the parent Taskfile.
|
||||
type TaskfileDoesNotMatchChecksum struct {
|
||||
URI string
|
||||
ExpectedChecksum string
|
||||
ActualChecksum string
|
||||
}
|
||||
|
||||
func (err *TaskfileDoesNotMatchChecksum) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"task: The checksum of the Taskfile at %q does not match!\ngot: %q\nwant: %q",
|
||||
filepath.ToSlash(err.URI),
|
||||
err.ActualChecksum,
|
||||
err.ExpectedChecksum,
|
||||
)
|
||||
}
|
||||
|
||||
func (err *TaskfileDoesNotMatchChecksum) Code() int {
|
||||
return CodeTaskfileDoesNotMatchChecksum
|
||||
}
|
||||
20
errors/errors_taskrc.go
Normal file
20
errors/errors_taskrc.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
type TaskRCNotFoundError struct {
|
||||
URI string
|
||||
Walk bool
|
||||
}
|
||||
|
||||
func (err TaskRCNotFoundError) Error() string {
|
||||
var walkText string
|
||||
if err.Walk {
|
||||
walkText = " (or any of the parent directories)"
|
||||
}
|
||||
return fmt.Sprintf(`task: No Task config file found at %q%s`, err.URI, walkText)
|
||||
}
|
||||
|
||||
func (err TaskRCNotFoundError) Code() int {
|
||||
return CodeTaskRCNotFoundError
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"hello": {
|
||||
"cmds": [
|
||||
"echo \"I am going to write a file named 'output.txt' now.\"",
|
||||
"echo \"hello\" > output.txt"
|
||||
],
|
||||
"generates": [
|
||||
"output.txt"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
[hello]
|
||||
cmds = [
|
||||
"echo \"I am going to write a file named 'output.txt' now.\"",
|
||||
"echo \"hello\" > output.txt"
|
||||
]
|
||||
generates = ["output.txt"]
|
||||
@@ -1,6 +0,0 @@
|
||||
hello:
|
||||
cmds:
|
||||
- echo "I am going to write a file named 'output.txt' now."
|
||||
- echo "hello" > output.txt
|
||||
generates:
|
||||
- output.txt
|
||||
@@ -1,53 +0,0 @@
|
||||
package execext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/mvdan/sh/interp"
|
||||
"github.com/mvdan/sh/syntax"
|
||||
)
|
||||
|
||||
// RunCommandOptions is the options for the RunCommand func
|
||||
type RunCommandOptions struct {
|
||||
Context context.Context
|
||||
Command string
|
||||
Dir string
|
||||
Env []string
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrNilOptions is returned when a nil options is given
|
||||
ErrNilOptions = errors.New("execext: nil options given")
|
||||
)
|
||||
|
||||
// RunCommand runs a shell command
|
||||
func RunCommand(opts *RunCommandOptions) error {
|
||||
if opts == nil {
|
||||
return ErrNilOptions
|
||||
}
|
||||
|
||||
p, err := syntax.NewParser().Parse(strings.NewReader(opts.Command), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := interp.Runner{
|
||||
Context: opts.Context,
|
||||
File: p,
|
||||
Dir: opts.Dir,
|
||||
Env: opts.Env,
|
||||
Stdin: opts.Stdin,
|
||||
Stdout: opts.Stdout,
|
||||
Stderr: opts.Stderr,
|
||||
}
|
||||
if err = r.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
619
executor.go
Normal file
619
executor.go
Normal file
@@ -0,0 +1,619 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/sajari/fuzzy"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/internal/sort"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
type (
|
||||
// An ExecutorOption is any type that can apply a configuration to an
|
||||
// [Executor].
|
||||
ExecutorOption interface {
|
||||
ApplyToExecutor(*Executor)
|
||||
}
|
||||
// An Executor is used for processing Taskfile(s) and executing the task(s)
|
||||
// within them.
|
||||
Executor struct {
|
||||
// Flags
|
||||
Dir string
|
||||
Entrypoint string
|
||||
TempDir TempDir
|
||||
Force bool
|
||||
ForceAll bool
|
||||
Insecure bool
|
||||
Download bool
|
||||
Offline bool
|
||||
TrustedHosts []string
|
||||
Timeout time.Duration
|
||||
CacheExpiryDuration time.Duration
|
||||
RemoteCacheDir string
|
||||
CACert string
|
||||
Cert string
|
||||
CertKey string
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
DisableFuzzy bool
|
||||
AssumeYes bool
|
||||
AssumeTerm bool // Used for testing
|
||||
Interactive bool
|
||||
Dry bool
|
||||
Summary bool
|
||||
Parallel bool
|
||||
Color bool
|
||||
Concurrency int
|
||||
Interval time.Duration
|
||||
Failfast bool
|
||||
|
||||
// I/O
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
|
||||
// Internal
|
||||
Taskfile *ast.Taskfile
|
||||
Logger *logger.Logger
|
||||
Compiler *Compiler
|
||||
Output output.Output
|
||||
OutputStyle ast.Output
|
||||
TaskSorter sort.Sorter
|
||||
UserWorkingDir string
|
||||
EnableVersionCheck bool
|
||||
|
||||
fuzzyModel *fuzzy.Model
|
||||
fuzzyModelOnce sync.Once
|
||||
|
||||
promptedVars *ast.Vars // vars collected via interactive prompts
|
||||
concurrencySemaphore chan struct{}
|
||||
taskCallCount map[string]*int32
|
||||
mkdirMutexMap map[string]*sync.Mutex
|
||||
executionHashes map[string]context.Context
|
||||
executionHashesMutex sync.Mutex
|
||||
watchedDirs *xsync.Map[string, bool]
|
||||
}
|
||||
TempDir struct {
|
||||
Remote string
|
||||
Fingerprint string
|
||||
}
|
||||
)
|
||||
|
||||
// NewExecutor creates a new [Executor] and applies the given functional options
|
||||
// to it.
|
||||
func NewExecutor(opts ...ExecutorOption) *Executor {
|
||||
e := &Executor{
|
||||
Timeout: time.Second * 10,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Logger: nil,
|
||||
Compiler: nil,
|
||||
Output: nil,
|
||||
OutputStyle: ast.Output{},
|
||||
TaskSorter: sort.AlphaNumericWithRootTasksFirst,
|
||||
UserWorkingDir: "",
|
||||
fuzzyModel: nil,
|
||||
concurrencySemaphore: nil,
|
||||
taskCallCount: map[string]*int32{},
|
||||
mkdirMutexMap: map[string]*sync.Mutex{},
|
||||
executionHashes: map[string]context.Context{},
|
||||
executionHashesMutex: sync.Mutex{},
|
||||
}
|
||||
e.Options(opts...)
|
||||
return e
|
||||
}
|
||||
|
||||
// Options loops through the given [ExecutorOption] functions and applies them
|
||||
// to the [Executor].
|
||||
func (e *Executor) Options(opts ...ExecutorOption) {
|
||||
for _, opt := range opts {
|
||||
opt.ApplyToExecutor(e)
|
||||
}
|
||||
}
|
||||
|
||||
// WithDir sets the working directory of the [Executor]. By default, the
|
||||
// directory is set to the user's current working directory.
|
||||
func WithDir(dir string) ExecutorOption {
|
||||
return &dirOption{dir}
|
||||
}
|
||||
|
||||
type dirOption struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
func (o *dirOption) ApplyToExecutor(e *Executor) {
|
||||
e.Dir = o.dir
|
||||
}
|
||||
|
||||
// WithEntrypoint sets the entrypoint (main Taskfile) of the [Executor]. By
|
||||
// default, Task will search for one of the default Taskfiles in the given
|
||||
// directory.
|
||||
func WithEntrypoint(entrypoint string) ExecutorOption {
|
||||
return &entrypointOption{entrypoint}
|
||||
}
|
||||
|
||||
type entrypointOption struct {
|
||||
entrypoint string
|
||||
}
|
||||
|
||||
func (o *entrypointOption) ApplyToExecutor(e *Executor) {
|
||||
e.Entrypoint = o.entrypoint
|
||||
}
|
||||
|
||||
// WithTempDir sets the temporary directory that will be used by [Executor] for
|
||||
// storing temporary files like checksums and cached remote files. By default,
|
||||
// the temporary directory is set to the user's temporary directory.
|
||||
func WithTempDir(tempDir TempDir) ExecutorOption {
|
||||
return &tempDirOption{tempDir}
|
||||
}
|
||||
|
||||
type tempDirOption struct {
|
||||
tempDir TempDir
|
||||
}
|
||||
|
||||
func (o *tempDirOption) ApplyToExecutor(e *Executor) {
|
||||
e.TempDir = o.tempDir
|
||||
}
|
||||
|
||||
// WithForce ensures that the [Executor] always runs a task, even when
|
||||
// fingerprinting or prompts would normally stop it.
|
||||
func WithForce(force bool) ExecutorOption {
|
||||
return &forceOption{force}
|
||||
}
|
||||
|
||||
type forceOption struct {
|
||||
force bool
|
||||
}
|
||||
|
||||
func (o *forceOption) ApplyToExecutor(e *Executor) {
|
||||
e.Force = o.force
|
||||
}
|
||||
|
||||
// WithForceAll ensures that the [Executor] always runs all tasks (including
|
||||
// subtasks), even when fingerprinting or prompts would normally stop them.
|
||||
func WithForceAll(forceAll bool) ExecutorOption {
|
||||
return &forceAllOption{forceAll}
|
||||
}
|
||||
|
||||
type forceAllOption struct {
|
||||
forceAll bool
|
||||
}
|
||||
|
||||
func (o *forceAllOption) ApplyToExecutor(e *Executor) {
|
||||
e.ForceAll = o.forceAll
|
||||
}
|
||||
|
||||
// WithInsecure allows the [Executor] to make insecure connections when reading
|
||||
// remote taskfiles. By default, insecure connections are rejected.
|
||||
func WithInsecure(insecure bool) ExecutorOption {
|
||||
return &insecureOption{insecure}
|
||||
}
|
||||
|
||||
type insecureOption struct {
|
||||
insecure bool
|
||||
}
|
||||
|
||||
func (o *insecureOption) ApplyToExecutor(e *Executor) {
|
||||
e.Insecure = o.insecure
|
||||
}
|
||||
|
||||
// WithDownload forces the [Executor] to download a fresh copy of the taskfile
|
||||
// from the remote source.
|
||||
func WithDownload(download bool) ExecutorOption {
|
||||
return &downloadOption{download}
|
||||
}
|
||||
|
||||
type downloadOption struct {
|
||||
download bool
|
||||
}
|
||||
|
||||
func (o *downloadOption) ApplyToExecutor(e *Executor) {
|
||||
e.Download = o.download
|
||||
}
|
||||
|
||||
// WithOffline stops the [Executor] from being able to make network connections.
|
||||
// It will still be able to read local files and cached copies of remote files.
|
||||
func WithOffline(offline bool) ExecutorOption {
|
||||
return &offlineOption{offline}
|
||||
}
|
||||
|
||||
type offlineOption struct {
|
||||
offline bool
|
||||
}
|
||||
|
||||
func (o *offlineOption) ApplyToExecutor(e *Executor) {
|
||||
e.Offline = o.offline
|
||||
}
|
||||
|
||||
// WithTrustedHosts configures the [Executor] with a list of trusted hosts for remote
|
||||
// Taskfiles. Hosts in this list will not prompt for user confirmation.
|
||||
func WithTrustedHosts(trustedHosts []string) ExecutorOption {
|
||||
return &trustedHostsOption{trustedHosts}
|
||||
}
|
||||
|
||||
type trustedHostsOption struct {
|
||||
trustedHosts []string
|
||||
}
|
||||
|
||||
func (o *trustedHostsOption) ApplyToExecutor(e *Executor) {
|
||||
e.TrustedHosts = o.trustedHosts
|
||||
}
|
||||
|
||||
// WithTimeout sets the [Executor]'s timeout for fetching remote taskfiles. By
|
||||
// default, the timeout is set to 10 seconds.
|
||||
func WithTimeout(timeout time.Duration) ExecutorOption {
|
||||
return &timeoutOption{timeout}
|
||||
}
|
||||
|
||||
type timeoutOption struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (o *timeoutOption) ApplyToExecutor(e *Executor) {
|
||||
e.Timeout = o.timeout
|
||||
}
|
||||
|
||||
// WithCacheExpiryDuration sets the duration after which the cache is considered
|
||||
// expired. By default, the cache is 0 (disabled).
|
||||
func WithCacheExpiryDuration(duration time.Duration) ExecutorOption {
|
||||
return &cacheExpiryDurationOption{duration: duration}
|
||||
}
|
||||
|
||||
type cacheExpiryDurationOption struct {
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
func (o *cacheExpiryDurationOption) ApplyToExecutor(r *Executor) {
|
||||
r.CacheExpiryDuration = o.duration
|
||||
}
|
||||
|
||||
// WithRemoteCacheDir sets the directory where remote taskfiles are cached.
|
||||
func WithRemoteCacheDir(dir string) ExecutorOption {
|
||||
return &remoteCacheDirOption{dir: dir}
|
||||
}
|
||||
|
||||
type remoteCacheDirOption struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
func (o *remoteCacheDirOption) ApplyToExecutor(e *Executor) {
|
||||
e.RemoteCacheDir = o.dir
|
||||
}
|
||||
|
||||
// WithCACert sets the path to a custom CA certificate for TLS connections.
|
||||
func WithCACert(caCert string) ExecutorOption {
|
||||
return &caCertOption{caCert: caCert}
|
||||
}
|
||||
|
||||
type caCertOption struct {
|
||||
caCert string
|
||||
}
|
||||
|
||||
func (o *caCertOption) ApplyToExecutor(e *Executor) {
|
||||
e.CACert = o.caCert
|
||||
}
|
||||
|
||||
// WithCert sets the path to a client certificate for TLS connections.
|
||||
func WithCert(cert string) ExecutorOption {
|
||||
return &certOption{cert: cert}
|
||||
}
|
||||
|
||||
type certOption struct {
|
||||
cert string
|
||||
}
|
||||
|
||||
func (o *certOption) ApplyToExecutor(e *Executor) {
|
||||
e.Cert = o.cert
|
||||
}
|
||||
|
||||
// WithCertKey sets the path to a client certificate key for TLS connections.
|
||||
func WithCertKey(certKey string) ExecutorOption {
|
||||
return &certKeyOption{certKey: certKey}
|
||||
}
|
||||
|
||||
type certKeyOption struct {
|
||||
certKey string
|
||||
}
|
||||
|
||||
func (o *certKeyOption) ApplyToExecutor(e *Executor) {
|
||||
e.CertKey = o.certKey
|
||||
}
|
||||
|
||||
// WithWatch tells the [Executor] to keep running in the background and watch
|
||||
// for changes to the fingerprint of the tasks that are run. When changes are
|
||||
// detected, a new task run is triggered.
|
||||
func WithWatch(watch bool) ExecutorOption {
|
||||
return &watchOption{watch}
|
||||
}
|
||||
|
||||
type watchOption struct {
|
||||
watch bool
|
||||
}
|
||||
|
||||
func (o *watchOption) ApplyToExecutor(e *Executor) {
|
||||
e.Watch = o.watch
|
||||
}
|
||||
|
||||
// WithVerbose tells the [Executor] to output more information about the tasks
|
||||
// that are run.
|
||||
func WithVerbose(verbose bool) ExecutorOption {
|
||||
return &verboseOption{verbose}
|
||||
}
|
||||
|
||||
type verboseOption struct {
|
||||
verbose bool
|
||||
}
|
||||
|
||||
func (o *verboseOption) ApplyToExecutor(e *Executor) {
|
||||
e.Verbose = o.verbose
|
||||
}
|
||||
|
||||
// WithSilent tells the [Executor] to suppress all output except for the output
|
||||
// of the tasks that are run.
|
||||
func WithSilent(silent bool) ExecutorOption {
|
||||
return &silentOption{silent}
|
||||
}
|
||||
|
||||
type silentOption struct {
|
||||
silent bool
|
||||
}
|
||||
|
||||
func (o *silentOption) ApplyToExecutor(e *Executor) {
|
||||
e.Silent = o.silent
|
||||
}
|
||||
|
||||
// WithDisableFuzzy tells the [Executor] to disable fuzzy matching for task names.
|
||||
func WithDisableFuzzy(disableFuzzy bool) ExecutorOption {
|
||||
return &disableFuzzyOption{disableFuzzy}
|
||||
}
|
||||
|
||||
type disableFuzzyOption struct {
|
||||
disableFuzzy bool
|
||||
}
|
||||
|
||||
func (o *disableFuzzyOption) ApplyToExecutor(e *Executor) {
|
||||
e.DisableFuzzy = o.disableFuzzy
|
||||
}
|
||||
|
||||
// WithAssumeYes tells the [Executor] to assume "yes" for all prompts.
|
||||
func WithAssumeYes(assumeYes bool) ExecutorOption {
|
||||
return &assumeYesOption{assumeYes}
|
||||
}
|
||||
|
||||
type assumeYesOption struct {
|
||||
assumeYes bool
|
||||
}
|
||||
|
||||
func (o *assumeYesOption) ApplyToExecutor(e *Executor) {
|
||||
e.AssumeYes = o.assumeYes
|
||||
}
|
||||
|
||||
// WithAssumeTerm is used for testing purposes to simulate a terminal.
|
||||
func WithAssumeTerm(assumeTerm bool) ExecutorOption {
|
||||
return &assumeTermOption{assumeTerm}
|
||||
}
|
||||
|
||||
type assumeTermOption struct {
|
||||
assumeTerm bool
|
||||
}
|
||||
|
||||
func (o *assumeTermOption) ApplyToExecutor(e *Executor) {
|
||||
e.AssumeTerm = o.assumeTerm
|
||||
}
|
||||
|
||||
// WithInteractive tells the [Executor] to prompt for missing required variables.
|
||||
func WithInteractive(interactive bool) ExecutorOption {
|
||||
return &interactiveOption{interactive}
|
||||
}
|
||||
|
||||
type interactiveOption struct {
|
||||
interactive bool
|
||||
}
|
||||
|
||||
func (o *interactiveOption) ApplyToExecutor(e *Executor) {
|
||||
e.Interactive = o.interactive
|
||||
}
|
||||
|
||||
// WithDry tells the [Executor] to output the commands that would be run without
|
||||
// actually running them.
|
||||
func WithDry(dry bool) ExecutorOption {
|
||||
return &dryOption{dry}
|
||||
}
|
||||
|
||||
type dryOption struct {
|
||||
dry bool
|
||||
}
|
||||
|
||||
func (o *dryOption) ApplyToExecutor(e *Executor) {
|
||||
e.Dry = o.dry
|
||||
}
|
||||
|
||||
// WithSummary tells the [Executor] to output a summary of the given tasks
|
||||
// instead of running them.
|
||||
func WithSummary(summary bool) ExecutorOption {
|
||||
return &summaryOption{summary}
|
||||
}
|
||||
|
||||
type summaryOption struct {
|
||||
summary bool
|
||||
}
|
||||
|
||||
func (o *summaryOption) ApplyToExecutor(e *Executor) {
|
||||
e.Summary = o.summary
|
||||
}
|
||||
|
||||
// WithParallel tells the [Executor] to run tasks given in the same call in
|
||||
// parallel.
|
||||
func WithParallel(parallel bool) ExecutorOption {
|
||||
return ¶llelOption{parallel}
|
||||
}
|
||||
|
||||
type parallelOption struct {
|
||||
parallel bool
|
||||
}
|
||||
|
||||
func (o *parallelOption) ApplyToExecutor(e *Executor) {
|
||||
e.Parallel = o.parallel
|
||||
}
|
||||
|
||||
// WithColor tells the [Executor] whether or not to output using colorized
|
||||
// strings.
|
||||
func WithColor(color bool) ExecutorOption {
|
||||
return &colorOption{color}
|
||||
}
|
||||
|
||||
type colorOption struct {
|
||||
color bool
|
||||
}
|
||||
|
||||
func (o *colorOption) ApplyToExecutor(e *Executor) {
|
||||
e.Color = o.color
|
||||
}
|
||||
|
||||
// WithConcurrency sets the maximum number of tasks that the [Executor] can run
|
||||
// in parallel.
|
||||
func WithConcurrency(concurrency int) ExecutorOption {
|
||||
return &concurrencyOption{concurrency}
|
||||
}
|
||||
|
||||
type concurrencyOption struct {
|
||||
concurrency int
|
||||
}
|
||||
|
||||
func (o *concurrencyOption) ApplyToExecutor(e *Executor) {
|
||||
e.Concurrency = o.concurrency
|
||||
}
|
||||
|
||||
// WithInterval sets the interval at which the [Executor] will wait for
|
||||
// duplicated events before running a task.
|
||||
func WithInterval(interval time.Duration) ExecutorOption {
|
||||
return &intervalOption{interval}
|
||||
}
|
||||
|
||||
type intervalOption struct {
|
||||
interval time.Duration
|
||||
}
|
||||
|
||||
func (o *intervalOption) ApplyToExecutor(e *Executor) {
|
||||
e.Interval = o.interval
|
||||
}
|
||||
|
||||
// WithOutputStyle sets the output style of the [Executor]. By default, the
|
||||
// output style is set to the style defined in the Taskfile.
|
||||
func WithOutputStyle(outputStyle ast.Output) ExecutorOption {
|
||||
return &outputStyleOption{outputStyle}
|
||||
}
|
||||
|
||||
type outputStyleOption struct {
|
||||
outputStyle ast.Output
|
||||
}
|
||||
|
||||
func (o *outputStyleOption) ApplyToExecutor(e *Executor) {
|
||||
e.OutputStyle = o.outputStyle
|
||||
}
|
||||
|
||||
// WithTaskSorter sets the sorter that the [Executor] will use to sort tasks. By
|
||||
// default, the sorter is set to sort tasks alphabetically, but with tasks with
|
||||
// no namespace (in the root Taskfile) first.
|
||||
func WithTaskSorter(sorter sort.Sorter) ExecutorOption {
|
||||
return &taskSorterOption{sorter}
|
||||
}
|
||||
|
||||
type taskSorterOption struct {
|
||||
sorter sort.Sorter
|
||||
}
|
||||
|
||||
func (o *taskSorterOption) ApplyToExecutor(e *Executor) {
|
||||
e.TaskSorter = o.sorter
|
||||
}
|
||||
|
||||
// WithStdin sets the [Executor]'s standard input [io.Reader].
|
||||
func WithStdin(stdin io.Reader) ExecutorOption {
|
||||
return &stdinOption{stdin}
|
||||
}
|
||||
|
||||
type stdinOption struct {
|
||||
stdin io.Reader
|
||||
}
|
||||
|
||||
func (o *stdinOption) ApplyToExecutor(e *Executor) {
|
||||
e.Stdin = o.stdin
|
||||
}
|
||||
|
||||
// WithStdout sets the [Executor]'s standard output [io.Writer].
|
||||
func WithStdout(stdout io.Writer) ExecutorOption {
|
||||
return &stdoutOption{stdout}
|
||||
}
|
||||
|
||||
type stdoutOption struct {
|
||||
stdout io.Writer
|
||||
}
|
||||
|
||||
func (o *stdoutOption) ApplyToExecutor(e *Executor) {
|
||||
e.Stdout = o.stdout
|
||||
}
|
||||
|
||||
// WithStderr sets the [Executor]'s standard error [io.Writer].
|
||||
func WithStderr(stderr io.Writer) ExecutorOption {
|
||||
return &stderrOption{stderr}
|
||||
}
|
||||
|
||||
type stderrOption struct {
|
||||
stderr io.Writer
|
||||
}
|
||||
|
||||
func (o *stderrOption) ApplyToExecutor(e *Executor) {
|
||||
e.Stderr = o.stderr
|
||||
}
|
||||
|
||||
// WithIO sets the [Executor]'s standard input, output, and error to the same
|
||||
// [io.ReadWriter].
|
||||
func WithIO(rw io.ReadWriter) ExecutorOption {
|
||||
return &ioOption{rw}
|
||||
}
|
||||
|
||||
type ioOption struct {
|
||||
rw io.ReadWriter
|
||||
}
|
||||
|
||||
func (o *ioOption) ApplyToExecutor(e *Executor) {
|
||||
e.Stdin = o.rw
|
||||
e.Stdout = o.rw
|
||||
e.Stderr = o.rw
|
||||
}
|
||||
|
||||
// WithVersionCheck tells the [Executor] whether or not to check the version of
|
||||
func WithVersionCheck(enableVersionCheck bool) ExecutorOption {
|
||||
return &versionCheckOption{enableVersionCheck}
|
||||
}
|
||||
|
||||
type versionCheckOption struct {
|
||||
enableVersionCheck bool
|
||||
}
|
||||
|
||||
func (o *versionCheckOption) ApplyToExecutor(e *Executor) {
|
||||
e.EnableVersionCheck = o.enableVersionCheck
|
||||
}
|
||||
|
||||
// WithFailfast tells the [Executor] whether or not to check the version of
|
||||
func WithFailfast(failfast bool) ExecutorOption {
|
||||
return &failfastOption{failfast}
|
||||
}
|
||||
|
||||
type failfastOption struct {
|
||||
failfast bool
|
||||
}
|
||||
|
||||
func (o *failfastOption) ApplyToExecutor(e *Executor) {
|
||||
e.Failfast = o.failfast
|
||||
}
|
||||
1222
executor_test.go
Normal file
1222
executor_test.go
Normal file
File diff suppressed because it is too large
Load Diff
35
experiments/errors.go
Normal file
35
experiments/errors.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package experiments
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/internal/slicesext"
|
||||
)
|
||||
|
||||
type InvalidValueError struct {
|
||||
Name string
|
||||
AllowedValues []int
|
||||
Value int
|
||||
}
|
||||
|
||||
func (err InvalidValueError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"task: Experiment %q has an invalid value %q (allowed values: %s)",
|
||||
err.Name,
|
||||
err.Value,
|
||||
strings.Join(slicesext.Convert(err.AllowedValues, strconv.Itoa), ", "),
|
||||
)
|
||||
}
|
||||
|
||||
type InactiveError struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (err InactiveError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"task: Experiment %q is inactive and cannot be enabled",
|
||||
err.Name,
|
||||
)
|
||||
}
|
||||
67
experiments/experiment.go
Normal file
67
experiments/experiment.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package experiments
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
|
||||
type Experiment struct {
|
||||
Name string // The name of the experiment.
|
||||
AllowedValues []int // The values that can enable this experiment.
|
||||
Value int // The version of the experiment that is enabled.
|
||||
}
|
||||
|
||||
// New creates a new experiment with the given name and sets the values that can
|
||||
// enable it.
|
||||
func New(xName string, config *ast.TaskRC, allowedValues ...int) Experiment {
|
||||
var value int
|
||||
if config != nil {
|
||||
value = config.Experiments[xName]
|
||||
}
|
||||
|
||||
if value == 0 {
|
||||
value, _ = strconv.Atoi(getEnv(xName))
|
||||
}
|
||||
|
||||
x := Experiment{
|
||||
Name: xName,
|
||||
AllowedValues: allowedValues,
|
||||
Value: value,
|
||||
}
|
||||
xList = append(xList, x)
|
||||
return x
|
||||
}
|
||||
|
||||
func (x Experiment) Enabled() bool {
|
||||
return slices.Contains(x.AllowedValues, x.Value)
|
||||
}
|
||||
|
||||
func (x Experiment) Active() bool {
|
||||
return len(x.AllowedValues) > 0
|
||||
}
|
||||
|
||||
func (x Experiment) Valid() error {
|
||||
if !x.Active() && x.Value != 0 {
|
||||
return &InactiveError{
|
||||
Name: x.Name,
|
||||
}
|
||||
}
|
||||
if !x.Enabled() && x.Value != 0 {
|
||||
return &InvalidValueError{
|
||||
Name: x.Name,
|
||||
AllowedValues: x.AllowedValues,
|
||||
Value: x.Value,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x Experiment) String() string {
|
||||
if x.Enabled() {
|
||||
return fmt.Sprintf("on (%d)", x.Value)
|
||||
}
|
||||
return "off"
|
||||
}
|
||||
140
experiments/experiment_test.go
Normal file
140
experiments/experiment_test.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package experiments_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
const (
|
||||
exampleExperiment = "EXAMPLE"
|
||||
exampleExperimentEnv = "TASK_X_EXAMPLE"
|
||||
)
|
||||
tests := []struct {
|
||||
name string
|
||||
config *ast.TaskRC
|
||||
allowedValues []int
|
||||
env int
|
||||
wantEnabled bool
|
||||
wantActive bool
|
||||
wantValid error
|
||||
wantValue int
|
||||
}{
|
||||
{
|
||||
name: `[] allowed, env=""`,
|
||||
wantEnabled: false,
|
||||
wantActive: false,
|
||||
},
|
||||
{
|
||||
name: `[] allowed, env="1"`,
|
||||
env: 1,
|
||||
wantEnabled: false,
|
||||
wantActive: false,
|
||||
wantValid: &experiments.InactiveError{
|
||||
Name: exampleExperiment,
|
||||
},
|
||||
wantValue: 1,
|
||||
},
|
||||
{
|
||||
name: `[1] allowed, env=""`,
|
||||
allowedValues: []int{1},
|
||||
wantEnabled: false,
|
||||
wantActive: true,
|
||||
},
|
||||
{
|
||||
name: `[1] allowed, env="1"`,
|
||||
allowedValues: []int{1},
|
||||
env: 1,
|
||||
wantEnabled: true,
|
||||
wantActive: true,
|
||||
wantValue: 1,
|
||||
},
|
||||
{
|
||||
name: `[1] allowed, env="2"`,
|
||||
allowedValues: []int{1},
|
||||
env: 2,
|
||||
wantEnabled: false,
|
||||
wantActive: true,
|
||||
wantValid: &experiments.InvalidValueError{
|
||||
Name: exampleExperiment,
|
||||
AllowedValues: []int{1},
|
||||
Value: 2,
|
||||
},
|
||||
wantValue: 2,
|
||||
},
|
||||
{
|
||||
name: `[1, 2] allowed, env="1"`,
|
||||
allowedValues: []int{1, 2},
|
||||
env: 1,
|
||||
wantEnabled: true,
|
||||
wantActive: true,
|
||||
wantValue: 1,
|
||||
},
|
||||
{
|
||||
name: `[1, 2] allowed, env="1"`,
|
||||
allowedValues: []int{1, 2},
|
||||
env: 2,
|
||||
wantEnabled: true,
|
||||
wantActive: true,
|
||||
wantValue: 2,
|
||||
},
|
||||
{
|
||||
name: `[1] allowed, config="1"`,
|
||||
config: &ast.TaskRC{
|
||||
Experiments: map[string]int{
|
||||
exampleExperiment: 1,
|
||||
},
|
||||
},
|
||||
allowedValues: []int{1},
|
||||
wantEnabled: true,
|
||||
wantActive: true,
|
||||
wantValue: 1,
|
||||
},
|
||||
{
|
||||
name: `[1] allowed, config="2"`,
|
||||
config: &ast.TaskRC{
|
||||
Experiments: map[string]int{
|
||||
exampleExperiment: 2,
|
||||
},
|
||||
},
|
||||
allowedValues: []int{1},
|
||||
wantEnabled: false,
|
||||
wantActive: true,
|
||||
wantValid: &experiments.InvalidValueError{
|
||||
Name: exampleExperiment,
|
||||
AllowedValues: []int{1},
|
||||
Value: 2,
|
||||
},
|
||||
wantValue: 2,
|
||||
},
|
||||
{
|
||||
name: `[1, 2] allowed, env="1", config="2"`,
|
||||
config: &ast.TaskRC{
|
||||
Experiments: map[string]int{
|
||||
exampleExperiment: 2,
|
||||
},
|
||||
},
|
||||
allowedValues: []int{1, 2},
|
||||
env: 1,
|
||||
wantEnabled: true,
|
||||
wantActive: true,
|
||||
wantValue: 2,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv(exampleExperimentEnv, strconv.Itoa(tt.env))
|
||||
x := experiments.New(exampleExperiment, tt.config, tt.allowedValues...)
|
||||
assert.Equal(t, exampleExperiment, x.Name)
|
||||
assert.Equal(t, tt.wantEnabled, x.Enabled())
|
||||
assert.Equal(t, tt.wantActive, x.Active())
|
||||
assert.Equal(t, tt.wantValid, x.Valid())
|
||||
assert.Equal(t, tt.wantValue, x.Value)
|
||||
})
|
||||
}
|
||||
}
|
||||
89
experiments/experiments.go
Normal file
89
experiments/experiments.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package experiments
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"github.com/go-task/task/v3/taskrc"
|
||||
"github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
|
||||
const envPrefix = "TASK_X_"
|
||||
|
||||
// Active experiments.
|
||||
var (
|
||||
GentleForce Experiment
|
||||
RemoteTaskfiles Experiment
|
||||
EnvPrecedence Experiment
|
||||
)
|
||||
|
||||
// Inactive experiments. These are experiments that cannot be enabled, but are
|
||||
// preserved for error handling.
|
||||
var (
|
||||
AnyVariables Experiment
|
||||
MapVariables Experiment
|
||||
)
|
||||
|
||||
// An internal list of all the initialized experiments used for iterating.
|
||||
var xList []Experiment
|
||||
|
||||
func Parse(dir string) {
|
||||
config, _ := taskrc.GetConfig(dir)
|
||||
ParseWithConfig(dir, config)
|
||||
}
|
||||
|
||||
func ParseWithConfig(dir string, config *ast.TaskRC) {
|
||||
// Read any .env files
|
||||
readDotEnv(dir)
|
||||
// Initialize the experiments
|
||||
GentleForce = New("GENTLE_FORCE", config, 1)
|
||||
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)
|
||||
EnvPrecedence = New("ENV_PRECEDENCE", config, 1)
|
||||
AnyVariables = New("ANY_VARIABLES", config)
|
||||
MapVariables = New("MAP_VARIABLES", config)
|
||||
}
|
||||
|
||||
// Validate checks if any experiments have been enabled while being inactive.
|
||||
// If one is found, the function returns an error.
|
||||
func Validate() error {
|
||||
for _, x := range List() {
|
||||
if err := x.Valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func List() []Experiment {
|
||||
return xList
|
||||
}
|
||||
|
||||
func getEnv(xName string) string {
|
||||
envName := fmt.Sprintf("%s%s", envPrefix, xName)
|
||||
return os.Getenv(envName)
|
||||
}
|
||||
|
||||
func getFilePath(filename, dir string) string {
|
||||
if dir != "" {
|
||||
return filepath.Join(dir, filename)
|
||||
}
|
||||
return filename
|
||||
}
|
||||
|
||||
func readDotEnv(dir string) {
|
||||
env, err := godotenv.Read(getFilePath(".env", dir))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If the env var is an experiment, set it.
|
||||
for key, value := range env {
|
||||
if strings.HasPrefix(key, envPrefix) {
|
||||
os.Setenv(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
82
file.go
82
file.go
@@ -1,82 +0,0 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/mattn/go-zglob"
|
||||
)
|
||||
|
||||
func minTime(a, b time.Time) time.Time {
|
||||
if !a.IsZero() && a.Before(b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
func maxTime(a, b time.Time) time.Time {
|
||||
if a.After(b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func getPatternsMinTime(patterns []string) (m time.Time, err error) {
|
||||
for _, p := range patterns {
|
||||
mp, err := getPatternMinTime(p)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
m = minTime(m, mp)
|
||||
}
|
||||
return
|
||||
}
|
||||
func getPatternsMaxTime(patterns []string) (m time.Time, err error) {
|
||||
for _, p := range patterns {
|
||||
mp, err := getPatternMaxTime(p)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
m = maxTime(m, mp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getPatternMinTime(pattern string) (minTime time.Time, err error) {
|
||||
files, err := zglob.Glob(pattern)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
modTime := info.ModTime()
|
||||
if minTime.IsZero() || modTime.Before(minTime) {
|
||||
minTime = modTime
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getPatternMaxTime(pattern string) (maxTime time.Time, err error) {
|
||||
files, err := zglob.Glob(pattern)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
modTime := info.ModTime()
|
||||
if modTime.After(maxTime) {
|
||||
maxTime = modTime
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
236
formatter_test.go
Normal file
236
formatter_test.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package task_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/sebdah/goldie/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
type (
|
||||
// A FormatterTestOption is a function that configures an [FormatterTest].
|
||||
FormatterTestOption interface {
|
||||
applyToFormatterTest(*FormatterTest)
|
||||
}
|
||||
// A FormatterTest is a test wrapper around a [task.Executor] to make it
|
||||
// easy to write tests for the task formatter. See [NewFormatterTest] for
|
||||
// information on creating and running FormatterTests. These tests use
|
||||
// fixture files to assert whether the result of the output is correct. If
|
||||
// Task's behavior has been changed, the fixture files can be updated by
|
||||
// running `task gen:fixtures`.
|
||||
FormatterTest struct {
|
||||
TaskTest
|
||||
task string
|
||||
vars map[string]any
|
||||
executorOpts []task.ExecutorOption
|
||||
listOptions task.ListOptions
|
||||
wantSetupError bool
|
||||
wantListError bool
|
||||
}
|
||||
)
|
||||
|
||||
// NewFormatterTest sets up a new [task.Executor] with the given options and
|
||||
// runs a task with the given [FormatterTestOption]s. The output of the task is
|
||||
// written to a set of fixture files depending on the configuration of the test.
|
||||
func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) {
|
||||
t.Helper()
|
||||
tt := &FormatterTest{
|
||||
task: "default",
|
||||
vars: map[string]any{},
|
||||
TaskTest: TaskTest{
|
||||
experiments: map[*experiments.Experiment]int{},
|
||||
fixtureTemplateData: map[string]any{},
|
||||
},
|
||||
}
|
||||
// Apply the functional options
|
||||
for _, opt := range opts {
|
||||
opt.applyToFormatterTest(tt)
|
||||
}
|
||||
// Enable any experiments that have been set
|
||||
for x, v := range tt.experiments {
|
||||
prev := *x
|
||||
*x = experiments.Experiment{
|
||||
Name: prev.Name,
|
||||
AllowedValues: []int{v},
|
||||
Value: v,
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
*x = prev
|
||||
})
|
||||
}
|
||||
tt.run(t)
|
||||
}
|
||||
|
||||
// Functional options
|
||||
|
||||
// WithListOptions sets the list options for the formatter.
|
||||
func WithListOptions(opts task.ListOptions) FormatterTestOption {
|
||||
return &listOptionsTestOption{opts}
|
||||
}
|
||||
|
||||
type listOptionsTestOption struct {
|
||||
listOptions task.ListOptions
|
||||
}
|
||||
|
||||
func (opt *listOptionsTestOption) applyToFormatterTest(t *FormatterTest) {
|
||||
t.listOptions = opt.listOptions
|
||||
}
|
||||
|
||||
// WithListError tells the test to expect an error when running the formatter.
|
||||
// A fixture will be created with the output of any errors.
|
||||
func WithListError() FormatterTestOption {
|
||||
return &listErrorTestOption{}
|
||||
}
|
||||
|
||||
type listErrorTestOption struct{}
|
||||
|
||||
func (opt *listErrorTestOption) applyToFormatterTest(t *FormatterTest) {
|
||||
t.wantListError = true
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
// writeFixtureErrList is a wrapper for writing the output of an error when
|
||||
// running the formatter to a fixture file.
|
||||
func (tt *FormatterTest) writeFixtureErrList(
|
||||
t *testing.T,
|
||||
g *goldie.Goldie,
|
||||
err error,
|
||||
) {
|
||||
t.Helper()
|
||||
tt.writeFixture(t, g, "err-list", []byte(err.Error()))
|
||||
}
|
||||
|
||||
// run is the main function for running the test. It sets up the task executor,
|
||||
// runs the task, and writes the output to a fixture file.
|
||||
func (tt *FormatterTest) run(t *testing.T) {
|
||||
t.Helper()
|
||||
f := func(t *testing.T) {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
|
||||
opts := append(
|
||||
tt.executorOpts,
|
||||
task.WithStdout(&buf),
|
||||
task.WithStderr(&buf),
|
||||
)
|
||||
|
||||
// Set up the task executor
|
||||
e := task.NewExecutor(opts...)
|
||||
|
||||
// Create a golden fixture file for the output
|
||||
g := goldie.New(t,
|
||||
goldie.WithFixtureDir(filepath.Join(e.Dir, "testdata")),
|
||||
goldie.WithEqualFn(NormalizedEqual),
|
||||
)
|
||||
|
||||
// Call setup and check for errors
|
||||
if err := e.Setup(); tt.wantSetupError {
|
||||
require.Error(t, err)
|
||||
tt.writeFixtureErrSetup(t, g, err)
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Create the task call
|
||||
vars := ast.NewVars()
|
||||
for key, value := range tt.vars {
|
||||
vars.Set(key, ast.Var{Value: value})
|
||||
}
|
||||
|
||||
// Run the formatter and check for errors
|
||||
if _, err := e.ListTasks(tt.listOptions); tt.wantListError {
|
||||
require.Error(t, err)
|
||||
tt.writeFixtureErrList(t, g, err)
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
}
|
||||
|
||||
// Run the test (with a name if it has one)
|
||||
if tt.name != "" {
|
||||
t.Run(tt.name, f)
|
||||
} else {
|
||||
f(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoLabelInList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/label_list"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
ListOnlyTasksWithDescriptions: true,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// task -al case 1: listAll list all tasks
|
||||
func TestListAllShowsNoDesc(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/list_mixed_desc"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
ListAllTasks: true,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// task -al case 2: !listAll list some tasks (only those with desc)
|
||||
func TestListCanListDescOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/list_mixed_desc"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
ListOnlyTasksWithDescriptions: true,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func TestListDescInterpolation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/list_desc_interpolation"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
ListOnlyTasksWithDescriptions: true,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func TestJsonListFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/json_list_format"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
FormatTaskListAsJSON: true,
|
||||
}),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
136
go.mod
Normal file
136
go.mod
Normal file
@@ -0,0 +1,136 @@
|
||||
module github.com/go-task/task/v3
|
||||
|
||||
go 1.25.10
|
||||
|
||||
require (
|
||||
charm.land/bubbles/v2 v2.1.0
|
||||
charm.land/bubbletea/v2 v2.0.6
|
||||
charm.land/lipgloss/v2 v2.0.3
|
||||
github.com/Ladicle/tabwriter v1.0.0
|
||||
github.com/Masterminds/semver/v3 v3.5.0
|
||||
github.com/alecthomas/chroma/v2 v2.24.1
|
||||
github.com/chainguard-dev/git-urls v1.0.2
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/dominikbraun/graph v0.23.0
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0
|
||||
github.com/fatih/color v1.19.0
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0
|
||||
github.com/go-task/template v0.2.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/go-getter v1.8.6
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0
|
||||
github.com/sajari/fuzzy v1.0.0
|
||||
github.com/sebdah/goldie/v2 v2.8.0
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/zeebo/xxh3 v1.1.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/term v0.43.0
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997
|
||||
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.18.2 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.3 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.3 // indirect
|
||||
cloud.google.com/go/storage v1.61.3 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.7 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/dlclark/regexp2 v1.12.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-version v1.8.0 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.23 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/net v0.54.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sys v0.44.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/api v0.271.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
329
go.sum
Normal file
329
go.sum
Normal file
@@ -0,0 +1,329 @@
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g=
|
||||
charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY=
|
||||
charm.land/bubbletea/v2 v2.0.6 h1:UHN/91OyuhaOFGSrBXQ/hMZD8IO1Uc4BvHlgHXL2WJo=
|
||||
charm.land/bubbletea/v2 v2.0.6/go.mod h1:MH/D8ZLlN3op37vQvijKuU29g3rqTp+aQapURFonF9g=
|
||||
charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU=
|
||||
charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
||||
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
||||
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
||||
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
|
||||
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=
|
||||
cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8=
|
||||
cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
|
||||
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
|
||||
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
|
||||
cloud.google.com/go/storage v1.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=
|
||||
cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk=
|
||||
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
|
||||
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
|
||||
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
|
||||
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
|
||||
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
|
||||
github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM=
|
||||
github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI=
|
||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk=
|
||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
||||
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ=
|
||||
github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
|
||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 h1:Q9fO0y1Zo5KB/5Vu8JZoLGm1N3RzF9bNj3Ao3xoR+Ac=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468/go.mod h1:bAAz7dh/FTYfC+oiHavL4mX1tOIBZ0ZwYjSi3qE6ivM=
|
||||
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
|
||||
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
||||
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
|
||||
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
|
||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-task/template v0.2.0 h1:xW7ek0o65FUSTbKcSNeg2Vyf/I7wYXFgLUznptvviBE=
|
||||
github.com/go-task/template v0.2.0/go.mod h1:dbdoUb6qKnHQi1y6o+IdIrs0J4o/SEhSTA6bbzZmdtc=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
|
||||
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 h1:vTCWu1wbdYo7PEZFem/rlr01+Un+wwVmI7wiegFdRLk=
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72/go.mod h1:Vn+BBgKQHVQYdVQ4NZDICE1Brb+JfaONyDHr3q07oQc=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-getter v1.8.6 h1:9sQboWULaydVphxc4S64oAI4YqpuCk7nPmvbk131ebY=
|
||||
github.com/hashicorp/go-getter v1.8.6/go.mod h1:nVH12eOV2P58dIiL3rsU6Fh3wLeJEKBOJzhMmzlSWoo=
|
||||
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
|
||||
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0 h1:vOSWu6b57/emh+L/Cw0BeQfvxa/cogFywXHeGUxQxAg=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
||||
github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc=
|
||||
github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8 h1:cq+DjLAjz3ZPwh0+G571O/jMH0c0DzReDPLjQGL2/BA=
|
||||
github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8/go.mod h1:JNauIV2zopCBv/6o+umxcT3bKe8YUqYJaTZQYSYpKss=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.271.0 h1:cIPN4qcUc61jlh7oXu6pwOQqbJW2GqYh5PS6rB2C/JY=
|
||||
google.golang.org/api v0.271.0/go.mod h1:CGT29bhwkbF+i11qkRUJb2KMKqcJ1hdFceEIRd9u64Q=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997 h1:3bbJwtPFh98dJ6lxRdR3eLHTH1CmR3BcU6TriIMiXjE=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997/go.mod h1:Qy/zdaMDxq9sT72Gi43K3gsV+TtTohyDO3f1cyBVwuo=
|
||||
mvdan.cc/sh/v3 v3.13.2-0.20260503214111-9e7dd28c81cf h1:3mGRe/xSr7fd9m+c5FSab/CSCtADsbdMcyhYGdVR6DY=
|
||||
mvdan.cc/sh/v3 v3.13.2-0.20260503214111-9e7dd28c81cf/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0=
|
||||
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117 h1:BfzdGSjcnJBb8sPNLudpzTml8zFUxS1N0N/v9IIS6tQ=
|
||||
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0=
|
||||
@@ -1,23 +0,0 @@
|
||||
build:
|
||||
binary: task
|
||||
main: cmd/task/task.go
|
||||
goos:
|
||||
- windows
|
||||
- darwin
|
||||
- linux
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
|
||||
archive:
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
||||
replacements:
|
||||
amd64: x64
|
||||
386: x86
|
||||
darwin: macOS
|
||||
25
hash.go
Normal file
25
hash.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-task/task/v3/internal/hash"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func (e *Executor) GetHash(t *ast.Task) (string, error) {
|
||||
r := cmp.Or(t.Run, e.Taskfile.Run)
|
||||
var h hash.HashFunc
|
||||
switch r {
|
||||
case "always":
|
||||
h = hash.Empty
|
||||
case "once":
|
||||
h = hash.Name
|
||||
case "when_changed":
|
||||
h = hash.Hash
|
||||
default:
|
||||
return "", fmt.Errorf(`task: invalid run "%s"`, r)
|
||||
}
|
||||
return h(t)
|
||||
}
|
||||
199
help.go
199
help.go
@@ -1,33 +1,200 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"text/tabwriter"
|
||||
"strings"
|
||||
|
||||
"github.com/Ladicle/tabwriter"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/go-task/task/v3/internal/editors"
|
||||
"github.com/go-task/task/v3/internal/fingerprint"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/sort"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func (e *Executor) printExistingTasksHelp() {
|
||||
tasks := e.tasksWithDesc()
|
||||
if len(tasks) == 0 {
|
||||
return
|
||||
// ListOptions collects list-related options
|
||||
type ListOptions struct {
|
||||
ListOnlyTasksWithDescriptions bool
|
||||
ListAllTasks bool
|
||||
FormatTaskListAsJSON bool
|
||||
NoStatus bool
|
||||
Nested bool
|
||||
}
|
||||
|
||||
// NewListOptions creates a new ListOptions instance
|
||||
func NewListOptions(list, listAll, listAsJson, noStatus, nested bool) ListOptions {
|
||||
return ListOptions{
|
||||
ListOnlyTasksWithDescriptions: list,
|
||||
ListAllTasks: listAll,
|
||||
FormatTaskListAsJSON: listAsJson,
|
||||
NoStatus: noStatus,
|
||||
Nested: nested,
|
||||
}
|
||||
fmt.Println("Available tasks for this project:")
|
||||
}
|
||||
|
||||
// ShouldListTasks returns true if one of the options to list tasks has been set to true
|
||||
func (o ListOptions) ShouldListTasks() bool {
|
||||
return o.ListOnlyTasksWithDescriptions || o.ListAllTasks
|
||||
}
|
||||
|
||||
// Filters returns the slice of FilterFunc which filters a list
|
||||
// of ast.Task according to the given ListOptions
|
||||
func (o ListOptions) Filters() []FilterFunc {
|
||||
filters := []FilterFunc{FilterOutInternal}
|
||||
|
||||
if o.ListOnlyTasksWithDescriptions {
|
||||
filters = append(filters, FilterOutNoDesc)
|
||||
}
|
||||
|
||||
return filters
|
||||
}
|
||||
|
||||
// ListTasks prints a list of tasks.
|
||||
// Tasks that match the given filters will be excluded from the list.
|
||||
// The function returns a boolean indicating whether tasks were found
|
||||
// and an error if one was encountered while preparing the output.
|
||||
func (e *Executor) ListTasks(o ListOptions) (bool, error) {
|
||||
tasks, err := e.GetTaskList(o.Filters()...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if o.FormatTaskListAsJSON {
|
||||
output, err := e.ToEditorOutput(tasks, o.NoStatus, o.Nested)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(e.Stdout)
|
||||
encoder.SetIndent("", " ")
|
||||
if err := encoder.Encode(output); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(tasks) > 0, nil
|
||||
}
|
||||
if len(tasks) == 0 {
|
||||
if o.ListOnlyTasksWithDescriptions {
|
||||
e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks\n")
|
||||
} else if o.ListAllTasks {
|
||||
e.Logger.Outf(logger.Yellow, "task: No tasks available\n")
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
e.Logger.Outf(logger.Default, "task: Available tasks for this project:\n")
|
||||
|
||||
// Format in tab-separated columns with a tab stop of 8.
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
|
||||
w := tabwriter.NewWriter(e.Stdout, 0, 8, 6, ' ', 0)
|
||||
for _, task := range tasks {
|
||||
fmt.Fprintln(w, fmt.Sprintf("- %s:\t%s", task, e.Tasks[task].Desc))
|
||||
e.Logger.FOutf(w, logger.Yellow, "* ")
|
||||
e.Logger.FOutf(w, logger.Green, task.Task)
|
||||
desc := strings.ReplaceAll(task.Desc, "\n", " ")
|
||||
e.Logger.FOutf(w, logger.Default, ": \t%s", desc)
|
||||
if len(task.Aliases) > 0 {
|
||||
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
|
||||
}
|
||||
_, _ = fmt.Fprint(w, "\n")
|
||||
}
|
||||
w.Flush()
|
||||
if err := w.Flush(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (e *Executor) tasksWithDesc() (tasks []string) {
|
||||
for name, task := range e.Tasks {
|
||||
if task.Desc != "" {
|
||||
tasks = append(tasks, name)
|
||||
// ListTaskNames prints only the task names in a Taskfile.
|
||||
// Only tasks with a non-empty description are printed if allTasks is false.
|
||||
// Otherwise, all task names are printed.
|
||||
func (e *Executor) ListTaskNames(allTasks bool) error {
|
||||
// use stdout if no output defined
|
||||
var w io.Writer = os.Stdout
|
||||
if e.Stdout != nil {
|
||||
w = e.Stdout
|
||||
}
|
||||
|
||||
// Sort the tasks
|
||||
if e.TaskSorter == nil {
|
||||
e.TaskSorter = sort.AlphaNumericWithRootTasksFirst
|
||||
}
|
||||
|
||||
// Create a list of task names
|
||||
taskNames := make([]string, 0, e.Taskfile.Tasks.Len())
|
||||
for task := range e.Taskfile.Tasks.Values(e.TaskSorter) {
|
||||
if (allTasks || task.Desc != "") && !task.Internal {
|
||||
taskNames = append(taskNames, strings.TrimRight(task.Task, ":"))
|
||||
for _, alias := range task.Aliases {
|
||||
taskNames = append(taskNames, strings.TrimRight(alias, ":"))
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(tasks)
|
||||
return
|
||||
for _, t := range taskNames {
|
||||
fmt.Fprintln(w, t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool, nested bool) (*editors.Namespace, error) {
|
||||
var g errgroup.Group
|
||||
editorTasks := make([]editors.Task, len(tasks))
|
||||
|
||||
// Look over each task in parallel and turn it into an editor task
|
||||
for i := range tasks {
|
||||
g.Go(func() error {
|
||||
editorTask := editors.NewTask(tasks[i])
|
||||
|
||||
if noStatus {
|
||||
editorTasks[i] = editorTask
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the fingerprinting method to use
|
||||
method := e.Taskfile.Method
|
||||
if tasks[i].Method != "" {
|
||||
method = tasks[i].Method
|
||||
}
|
||||
upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), tasks[i],
|
||||
fingerprint.WithMethod(method),
|
||||
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
||||
fingerprint.WithDry(e.Dry),
|
||||
fingerprint.WithLogger(e.Logger),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
editorTask.UpToDate = &upToDate
|
||||
editorTasks[i] = editorTask
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the root namespace
|
||||
var tasksLen int
|
||||
if !nested {
|
||||
tasksLen = len(editorTasks)
|
||||
}
|
||||
rootNamespace := &editors.Namespace{
|
||||
Tasks: make([]editors.Task, tasksLen),
|
||||
Location: e.Taskfile.Location,
|
||||
}
|
||||
|
||||
// Recursively add namespaces to the root namespace or if nesting is
|
||||
// disabled add them all to the root namespace
|
||||
for i, task := range editorTasks {
|
||||
taskNamespacePath := strings.Split(task.Task, ast.NamespaceSeparator)
|
||||
if nested {
|
||||
rootNamespace.AddNamespace(taskNamespacePath, task)
|
||||
} else {
|
||||
rootNamespace.Tasks[i] = task
|
||||
}
|
||||
}
|
||||
|
||||
return rootNamespace, g.Wait()
|
||||
}
|
||||
|
||||
57
init.go
57
init.go
@@ -1,29 +1,50 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
_ "embed"
|
||||
"os"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
const defaultTaskfile = `# github.com/go-task/task
|
||||
const defaultFilename = "Taskfile.yml"
|
||||
|
||||
default:
|
||||
cmds:
|
||||
- echo "Hello, World!"
|
||||
`
|
||||
//go:embed taskfile/templates/default.yml
|
||||
var DefaultTaskfile string
|
||||
|
||||
// InitTaskfile Taskfile creates a new Taskfile
|
||||
func InitTaskfile() error {
|
||||
for _, f := range []string{"Taskfile.yml", "Taskfile.toml", "Taskfile.json"} {
|
||||
if _, err := os.Stat(f); err == nil {
|
||||
return ErrTaskfileAlreadyExists
|
||||
// InitTaskfile creates a new Taskfile at path.
|
||||
//
|
||||
// path can be either a file path or a directory path.
|
||||
// If path is a directory, path/Taskfile.yml will be created.
|
||||
//
|
||||
// The final file path is always returned and may be different from the input path.
|
||||
func InitTaskfile(path string) (string, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err == nil && !info.IsDir() {
|
||||
return path, errors.TaskfileAlreadyExistsError{}
|
||||
}
|
||||
|
||||
if info != nil && info.IsDir() {
|
||||
// path was a directory, check if there is a Taskfile already
|
||||
if hasDefaultTaskfile(path) {
|
||||
return path, errors.TaskfileAlreadyExistsError{}
|
||||
}
|
||||
path = filepathext.SmartJoin(path, defaultFilename)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, []byte(DefaultTaskfile), 0o644); err != nil {
|
||||
return path, err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func hasDefaultTaskfile(dir string) bool {
|
||||
for _, name := range taskfile.DefaultTaskfiles {
|
||||
if _, err := os.Stat(filepathext.SmartJoin(dir, name)); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile("Taskfile.yml", []byte(defaultTaskfile), 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Taskfile.yml created in the current directory")
|
||||
return nil
|
||||
return false
|
||||
}
|
||||
|
||||
52
init_test.go
Normal file
52
init_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package task_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
)
|
||||
|
||||
func TestInitDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const dir = "testdata/init"
|
||||
file := filepathext.SmartJoin(dir, "Taskfile.yml")
|
||||
|
||||
_ = os.Remove(file)
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
t.Errorf("Taskfile.yml should not exist")
|
||||
}
|
||||
|
||||
if _, err := task.InitTaskfile(dir); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
t.Errorf("Taskfile.yml should exist")
|
||||
}
|
||||
|
||||
_ = os.Remove(file)
|
||||
}
|
||||
|
||||
func TestInitFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const dir = "testdata/init"
|
||||
file := filepathext.SmartJoin(dir, "Tasks.yml")
|
||||
|
||||
_ = os.Remove(file)
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
t.Errorf("Tasks.yml should not exist")
|
||||
}
|
||||
|
||||
if _, err := task.InitTaskfile(file); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
t.Errorf("Tasks.yml should exist")
|
||||
}
|
||||
_ = os.Remove(file)
|
||||
}
|
||||
381
install-task.sh
Executable file
381
install-task.sh
Executable file
@@ -0,0 +1,381 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
# Code generated by godownloader on 2021-01-12T13:40:40Z. DO NOT EDIT.
|
||||
#
|
||||
|
||||
usage() {
|
||||
this=$1
|
||||
cat <<EOF
|
||||
$this: download go binaries for go-task/task
|
||||
|
||||
Usage: $this [-b] bindir [-d] [tag]
|
||||
-b sets bindir or installation directory, Defaults to ./bin
|
||||
-d turns on debug logging
|
||||
[tag] is a tag from
|
||||
https://github.com/go-task/task/releases
|
||||
If tag is missing, then the latest will be used.
|
||||
|
||||
Generated by godownloader
|
||||
https://github.com/goreleaser/godownloader
|
||||
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
#BINDIR is ./bin unless set be ENV
|
||||
# over-ridden by flag below
|
||||
|
||||
BINDIR=${BINDIR:-./bin}
|
||||
while getopts "b:dh?x" arg; do
|
||||
case "$arg" in
|
||||
b) BINDIR="$OPTARG" ;;
|
||||
d) log_set_priority 10 ;;
|
||||
h | \?) usage "$0" ;;
|
||||
x) set -x ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
TAG=$1
|
||||
}
|
||||
# this function wraps all the destructive operations
|
||||
# if a curl|bash cuts off the end of the script due to
|
||||
# network, either nothing will happen or will syntax error
|
||||
# out preventing half-done work
|
||||
execute() {
|
||||
tmpdir=$(mktemp -d)
|
||||
log_debug "downloading files into ${tmpdir}"
|
||||
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
|
||||
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
|
||||
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
|
||||
srcdir="${tmpdir}"
|
||||
(cd "${tmpdir}" && untar "${TARBALL}")
|
||||
test ! -d "${BINDIR}" && install -d "${BINDIR}"
|
||||
for binexe in $BINARIES; do
|
||||
if [ "$OS" = "windows" ]; then
|
||||
binexe="${binexe}.exe"
|
||||
fi
|
||||
install "${srcdir}/${binexe}" "${BINDIR}/"
|
||||
log_info "installed ${BINDIR}/${binexe}"
|
||||
done
|
||||
rm -rf "${tmpdir}"
|
||||
}
|
||||
get_binaries() {
|
||||
case "$PLATFORM" in
|
||||
darwin/amd64) BINARIES="task" ;;
|
||||
darwin/arm64) BINARIES="task" ;;
|
||||
darwin/arm) BINARIES="task" ;;
|
||||
linux/386) BINARIES="task" ;;
|
||||
linux/amd64) BINARIES="task" ;;
|
||||
linux/arm64) BINARIES="task" ;;
|
||||
linux/arm) BINARIES="task" ;;
|
||||
windows/386) BINARIES="task" ;;
|
||||
windows/amd64) BINARIES="task" ;;
|
||||
windows/arm64) BINARIES="task" ;;
|
||||
windows/arm) BINARIES="task" ;;
|
||||
*)
|
||||
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
tag_to_version() {
|
||||
if [ -z "${TAG}" ]; then
|
||||
log_info "checking GitHub for latest tag"
|
||||
else
|
||||
log_info "checking GitHub for tag '${TAG}'"
|
||||
fi
|
||||
REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
|
||||
if test -z "$REALTAG"; then
|
||||
log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
|
||||
exit 1
|
||||
fi
|
||||
# if version starts with 'v', remove it
|
||||
TAG="$REALTAG"
|
||||
VERSION=${TAG#v}
|
||||
}
|
||||
adjust_format() {
|
||||
# change format (tar.gz or zip) based on OS
|
||||
case ${OS} in
|
||||
windows) FORMAT=zip ;;
|
||||
esac
|
||||
true
|
||||
}
|
||||
adjust_os() {
|
||||
# adjust archive name based on OS
|
||||
true
|
||||
}
|
||||
adjust_arch() {
|
||||
# adjust archive name based on ARCH
|
||||
true
|
||||
}
|
||||
|
||||
cat /dev/null <<EOF
|
||||
------------------------------------------------------------------------
|
||||
https://github.com/client9/shlib - portable posix shell functions
|
||||
Public domain - http://unlicense.org
|
||||
https://github.com/client9/shlib/blob/master/LICENSE.md
|
||||
but credit (and pull requests) appreciated.
|
||||
------------------------------------------------------------------------
|
||||
EOF
|
||||
is_command() {
|
||||
command -v "$1" >/dev/null
|
||||
}
|
||||
echoerr() {
|
||||
echo "$@" 1>&2
|
||||
}
|
||||
log_prefix() {
|
||||
echo "$0"
|
||||
}
|
||||
_logp=6
|
||||
log_set_priority() {
|
||||
_logp="$1"
|
||||
}
|
||||
log_priority() {
|
||||
if test -z "$1"; then
|
||||
echo "$_logp"
|
||||
return
|
||||
fi
|
||||
[ "$1" -le "$_logp" ]
|
||||
}
|
||||
log_tag() {
|
||||
case $1 in
|
||||
0) echo "emerg" ;;
|
||||
1) echo "alert" ;;
|
||||
2) echo "crit" ;;
|
||||
3) echo "err" ;;
|
||||
4) echo "warning" ;;
|
||||
5) echo "notice" ;;
|
||||
6) echo "info" ;;
|
||||
7) echo "debug" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
log_debug() {
|
||||
log_priority 7 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
|
||||
}
|
||||
log_info() {
|
||||
log_priority 6 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
|
||||
}
|
||||
log_err() {
|
||||
log_priority 3 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
|
||||
}
|
||||
log_crit() {
|
||||
log_priority 2 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
|
||||
}
|
||||
uname_os() {
|
||||
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
case "$os" in
|
||||
cygwin_nt*) os="windows" ;;
|
||||
mingw*) os="windows" ;;
|
||||
msys_nt*) os="windows" ;;
|
||||
esac
|
||||
echo "$os"
|
||||
}
|
||||
uname_arch() {
|
||||
arch=$(uname -m)
|
||||
case $arch in
|
||||
x86_64) arch="amd64" ;;
|
||||
x86) arch="386" ;;
|
||||
i686) arch="386" ;;
|
||||
i386) arch="386" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
armv5*) arch="arm" ;;
|
||||
armv6*) arch="arm" ;;
|
||||
armv7*) arch="arm" ;;
|
||||
esac
|
||||
echo ${arch}
|
||||
}
|
||||
uname_os_check() {
|
||||
os=$(uname_os)
|
||||
case "$os" in
|
||||
darwin) return 0 ;;
|
||||
dragonfly) return 0 ;;
|
||||
freebsd) return 0 ;;
|
||||
linux) return 0 ;;
|
||||
android) return 0 ;;
|
||||
nacl) return 0 ;;
|
||||
netbsd) return 0 ;;
|
||||
openbsd) return 0 ;;
|
||||
plan9) return 0 ;;
|
||||
solaris) return 0 ;;
|
||||
windows) return 0 ;;
|
||||
esac
|
||||
log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
|
||||
return 1
|
||||
}
|
||||
uname_arch_check() {
|
||||
arch=$(uname_arch)
|
||||
case "$arch" in
|
||||
386) return 0 ;;
|
||||
amd64) return 0 ;;
|
||||
arm64) return 0 ;;
|
||||
arm) return 0 ;;
|
||||
ppc64) return 0 ;;
|
||||
ppc64le) return 0 ;;
|
||||
mips) return 0 ;;
|
||||
mipsle) return 0 ;;
|
||||
mips64) return 0 ;;
|
||||
mips64le) return 0 ;;
|
||||
s390x) return 0 ;;
|
||||
amd64p32) return 0 ;;
|
||||
esac
|
||||
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
|
||||
return 1
|
||||
}
|
||||
untar() {
|
||||
tarball=$1
|
||||
case "${tarball}" in
|
||||
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;;
|
||||
*.tar) tar --no-same-owner -xf "${tarball}" ;;
|
||||
*.zip) unzip "${tarball}" ;;
|
||||
*)
|
||||
log_err "untar unknown archive format for ${tarball}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
http_download_curl() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
if [ -z "$header" ]; then
|
||||
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
|
||||
else
|
||||
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
|
||||
fi
|
||||
if [ "$code" != "200" ]; then
|
||||
log_debug "http_download_curl received HTTP status $code"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
http_download_wget() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
if [ -z "$header" ]; then
|
||||
wget -q -O "$local_file" "$source_url"
|
||||
else
|
||||
wget -q --header "$header" -O "$local_file" "$source_url"
|
||||
fi
|
||||
}
|
||||
http_download() {
|
||||
log_debug "http_download $2"
|
||||
if is_command curl; then
|
||||
http_download_curl "$@"
|
||||
return
|
||||
elif is_command wget; then
|
||||
http_download_wget "$@"
|
||||
return
|
||||
fi
|
||||
log_crit "http_download unable to find wget or curl"
|
||||
return 1
|
||||
}
|
||||
http_copy() {
|
||||
tmp=$(mktemp)
|
||||
http_download "${tmp}" "$1" "$2" || return 1
|
||||
body=$(cat "$tmp")
|
||||
rm -f "${tmp}"
|
||||
echo "$body"
|
||||
}
|
||||
github_release() {
|
||||
owner_repo=$1
|
||||
version=$2
|
||||
test -z "$version" && version="latest"
|
||||
giturl="https://github.com/${owner_repo}/releases/${version}"
|
||||
json=$(http_copy "$giturl" "Accept:application/json")
|
||||
test -z "$json" && return 1
|
||||
version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
|
||||
test -z "$version" && return 1
|
||||
echo "$version"
|
||||
}
|
||||
hash_sha256() {
|
||||
TARGET=${1:-/dev/stdin}
|
||||
if is_command gsha256sum; then
|
||||
hash=$(gsha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command sha256sum; then
|
||||
hash=$(sha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command shasum; then
|
||||
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command openssl; then
|
||||
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f a
|
||||
else
|
||||
log_crit "hash_sha256 unable to find command to compute sha-256 hash"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
hash_sha256_verify() {
|
||||
TARGET=$1
|
||||
checksums=$2
|
||||
if [ -z "$checksums" ]; then
|
||||
log_err "hash_sha256_verify checksum file not specified in arg2"
|
||||
return 1
|
||||
fi
|
||||
BASENAME=${TARGET##*/}
|
||||
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
|
||||
if [ -z "$want" ]; then
|
||||
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
|
||||
return 1
|
||||
fi
|
||||
got=$(hash_sha256 "$TARGET")
|
||||
if [ "$want" != "$got" ]; then
|
||||
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
cat /dev/null <<EOF
|
||||
------------------------------------------------------------------------
|
||||
End of functions from https://github.com/client9/shlib
|
||||
------------------------------------------------------------------------
|
||||
EOF
|
||||
|
||||
PROJECT_NAME="task"
|
||||
OWNER=go-task
|
||||
REPO="task"
|
||||
BINARY=task
|
||||
FORMAT=tar.gz
|
||||
OS=$(uname_os)
|
||||
ARCH=$(uname_arch)
|
||||
PREFIX="$OWNER/$REPO"
|
||||
|
||||
# use in logging routines
|
||||
log_prefix() {
|
||||
echo "$PREFIX"
|
||||
}
|
||||
PLATFORM="${OS}/${ARCH}"
|
||||
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
|
||||
|
||||
uname_os_check "$OS"
|
||||
uname_arch_check "$ARCH"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
get_binaries
|
||||
|
||||
tag_to_version
|
||||
|
||||
adjust_format
|
||||
|
||||
adjust_os
|
||||
|
||||
adjust_arch
|
||||
|
||||
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
|
||||
|
||||
NAME=${BINARY}_${OS}_${ARCH}
|
||||
TARBALL=${NAME}.${FORMAT}
|
||||
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
|
||||
CHECKSUM=task_checksums.txt
|
||||
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
|
||||
|
||||
|
||||
execute
|
||||
167
internal/deepcopy/deepcopy.go
Normal file
167
internal/deepcopy/deepcopy.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package deepcopy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
)
|
||||
|
||||
type Copier[T any] interface {
|
||||
DeepCopy() T
|
||||
}
|
||||
|
||||
func Scalar[T any](orig *T) *T {
|
||||
if orig == nil {
|
||||
return nil
|
||||
} else {
|
||||
v := *orig
|
||||
return &v
|
||||
}
|
||||
}
|
||||
|
||||
func Slice[T any](orig []T) []T {
|
||||
if orig == nil {
|
||||
return nil
|
||||
}
|
||||
c := make([]T, len(orig))
|
||||
for i, v := range orig {
|
||||
if copyable, ok := any(v).(Copier[T]); ok {
|
||||
c[i] = copyable.DeepCopy()
|
||||
} else {
|
||||
c[i] = v
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func Map[K comparable, V any](orig map[K]V) map[K]V {
|
||||
if orig == nil {
|
||||
return nil
|
||||
}
|
||||
c := make(map[K]V, len(orig))
|
||||
for k, v := range orig {
|
||||
if copyable, ok := any(v).(Copier[V]); ok {
|
||||
c[k] = copyable.DeepCopy()
|
||||
} else {
|
||||
c[k] = v
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func OrderedMap[K comparable, V any](orig *orderedmap.OrderedMap[K, V]) *orderedmap.OrderedMap[K, V] {
|
||||
if orig.Len() == 0 {
|
||||
return orderedmap.NewOrderedMap[K, V]()
|
||||
}
|
||||
c := orderedmap.NewOrderedMap[K, V]()
|
||||
for pair := orig.Front(); pair != nil; pair = pair.Next() {
|
||||
if copyable, ok := any(pair.Value).(Copier[V]); ok {
|
||||
c.Set(pair.Key, copyable.DeepCopy())
|
||||
} else {
|
||||
c.Set(pair.Key, pair.Value)
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// TraverseStringsFunc runs the given function on every string in the given
|
||||
// value by traversing it recursively. If the given value is a string, the
|
||||
// function will run on a copy of the string and return it. If the value is a
|
||||
// struct, map or a slice, the function will recursively call itself for each
|
||||
// field or element of the struct, map or slice until all strings inside the
|
||||
// struct or slice are replaced.
|
||||
func TraverseStringsFunc[T any](v T, fn func(v string) (string, error)) (T, error) {
|
||||
original := reflect.ValueOf(v)
|
||||
if original.Kind() == reflect.Invalid || !original.IsValid() {
|
||||
return v, nil
|
||||
}
|
||||
copy := reflect.New(original.Type()).Elem()
|
||||
|
||||
var traverseFunc func(copy, v reflect.Value) error
|
||||
traverseFunc = func(copy, v reflect.Value) error {
|
||||
switch v.Kind() {
|
||||
|
||||
case reflect.Pointer:
|
||||
// Unwrap the pointer
|
||||
originalValue := v.Elem()
|
||||
// If the pointer is nil, do nothing
|
||||
if !originalValue.IsValid() {
|
||||
return nil
|
||||
}
|
||||
// Create an empty copy from the original value's type
|
||||
copy.Set(reflect.New(originalValue.Type()))
|
||||
// Unwrap the newly created pointer and call traverseFunc recursively
|
||||
if err := traverseFunc(copy.Elem(), originalValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case reflect.Interface:
|
||||
// Unwrap the interface
|
||||
originalValue := v.Elem()
|
||||
if !originalValue.IsValid() {
|
||||
return nil
|
||||
}
|
||||
// Create an empty copy from the original value's type
|
||||
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||
// Unwrap the newly created pointer and call traverseFunc recursively
|
||||
if err := traverseFunc(copyValue, originalValue); err != nil {
|
||||
return err
|
||||
}
|
||||
copy.Set(copyValue)
|
||||
|
||||
case reflect.Struct:
|
||||
// Loop over each field and call traverseFunc recursively
|
||||
for i := range v.NumField() {
|
||||
if err := traverseFunc(copy.Field(i), v.Field(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
// Create an empty copy from the original value's type
|
||||
copy.Set(reflect.MakeSlice(v.Type(), v.Len(), v.Cap()))
|
||||
// Loop over each element and call traverseFunc recursively
|
||||
for i := range v.Len() {
|
||||
if err := traverseFunc(copy.Index(i), v.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
// Create an empty copy from the original value's type
|
||||
copy.Set(reflect.MakeMap(v.Type()))
|
||||
// Loop over each key
|
||||
for _, key := range v.MapKeys() {
|
||||
// Create a copy of each map index
|
||||
originalValue := v.MapIndex(key)
|
||||
if originalValue.IsNil() {
|
||||
continue
|
||||
}
|
||||
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||
// Call traverseFunc recursively
|
||||
if err := traverseFunc(copyValue, originalValue); err != nil {
|
||||
return err
|
||||
}
|
||||
copy.SetMapIndex(key, copyValue)
|
||||
}
|
||||
|
||||
case reflect.String:
|
||||
rv, err := fn(v.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copy.Set(reflect.ValueOf(rv))
|
||||
|
||||
default:
|
||||
copy.Set(v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := traverseFunc(copy, original); err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
return copy.Interface().(T), nil
|
||||
}
|
||||
86
internal/editors/output.go
Normal file
86
internal/editors/output.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package editors
|
||||
|
||||
import (
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
type (
|
||||
// Namespace wraps task list output for use in editor integrations (e.g. VSCode, etc)
|
||||
Namespace struct {
|
||||
Tasks []Task `json:"tasks"`
|
||||
Namespaces map[string]*Namespace `json:"namespaces,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
}
|
||||
// Task describes a single task
|
||||
Task struct {
|
||||
Name string `json:"name"`
|
||||
Task string `json:"task"`
|
||||
Desc string `json:"desc"`
|
||||
Summary string `json:"summary"`
|
||||
Aliases []string `json:"aliases"`
|
||||
UpToDate *bool `json:"up_to_date,omitempty"`
|
||||
Location *Location `json:"location"`
|
||||
}
|
||||
// Location describes a task's location in a taskfile
|
||||
Location struct {
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
Taskfile string `json:"taskfile"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewTask(task *ast.Task) Task {
|
||||
aliases := []string{}
|
||||
if len(task.Aliases) > 0 {
|
||||
aliases = task.Aliases
|
||||
}
|
||||
return Task{
|
||||
Name: task.Name(),
|
||||
Task: task.Task,
|
||||
Desc: task.Desc,
|
||||
Summary: task.Summary,
|
||||
Aliases: aliases,
|
||||
Location: &Location{
|
||||
Line: task.Location.Line,
|
||||
Column: task.Location.Column,
|
||||
Taskfile: task.Location.Taskfile,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (parent *Namespace) AddNamespace(namespacePath []string, task Task) {
|
||||
if len(namespacePath) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If there are no child namespaces, then we have found a task and we can
|
||||
// simply add it to the current namespace
|
||||
if len(namespacePath) == 1 {
|
||||
parent.Tasks = append(parent.Tasks, task)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the key of the current namespace in the path
|
||||
namespaceKey := namespacePath[0]
|
||||
|
||||
// Add the namespace to the parent namespaces map using the namespace key
|
||||
if parent.Namespaces == nil {
|
||||
parent.Namespaces = make(map[string]*Namespace, 0)
|
||||
}
|
||||
|
||||
// Search for the current namespace in the parent namespaces map
|
||||
// If it doesn't exist, create it
|
||||
namespace, ok := parent.Namespaces[namespaceKey]
|
||||
if !ok {
|
||||
namespace = &Namespace{}
|
||||
parent.Namespaces[namespaceKey] = namespace
|
||||
}
|
||||
|
||||
// Remove the current namespace key from the namespace path.
|
||||
childNamespacePath := namespacePath[1:]
|
||||
|
||||
// If there are no child namespaces in the task name, then we have found the
|
||||
// namespace of the task and we can add it to the current namespace.
|
||||
// Otherwise, we need to go deeper
|
||||
namespace.AddNamespace(childNamespacePath, task)
|
||||
}
|
||||
125
internal/env/env.go
vendored
Normal file
125
internal/env/env.go
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
const taskVarPrefix = "TASK_"
|
||||
|
||||
// GetEnviron the all return all environment variables encapsulated on a
|
||||
// ast.Vars
|
||||
func GetEnviron() *ast.Vars {
|
||||
m := ast.NewVars()
|
||||
for _, e := range os.Environ() {
|
||||
keyVal := strings.SplitN(e, "=", 2)
|
||||
key, val := keyVal[0], keyVal[1]
|
||||
m.Set(key, ast.Var{Value: val})
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func Get(t *ast.Task) []string {
|
||||
if t.Env == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return GetFromVars(t.Env)
|
||||
}
|
||||
|
||||
func GetFromVars(env *ast.Vars) []string {
|
||||
environ := os.Environ()
|
||||
|
||||
for k, v := range env.ToCacheMap() {
|
||||
if !isTypeAllowed(v) {
|
||||
continue
|
||||
}
|
||||
if !experiments.EnvPrecedence.Enabled() {
|
||||
if _, alreadySet := os.LookupEnv(k); alreadySet {
|
||||
continue
|
||||
}
|
||||
}
|
||||
environ = append(environ, fmt.Sprintf("%s=%v", k, v))
|
||||
}
|
||||
|
||||
return environ
|
||||
}
|
||||
|
||||
func isTypeAllowed(v any) bool {
|
||||
switch v.(type) {
|
||||
case string, bool, int, float32, float64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func GetTaskEnv(key string) string {
|
||||
return os.Getenv(taskVarPrefix + key)
|
||||
}
|
||||
|
||||
// GetTaskEnvBool returns the boolean value of a TASK_ prefixed env var.
|
||||
// Returns the value and true if set and valid, or false and false if not set or invalid.
|
||||
func GetTaskEnvBool(key string) (bool, bool) {
|
||||
v := GetTaskEnv(key)
|
||||
if v == "" {
|
||||
return false, false
|
||||
}
|
||||
b, err := strconv.ParseBool(v)
|
||||
return b, err == nil
|
||||
}
|
||||
|
||||
// GetTaskEnvInt returns the integer value of a TASK_ prefixed env var.
|
||||
// Returns the value and true if set and valid, or 0 and false if not set or invalid.
|
||||
func GetTaskEnvInt(key string) (int, bool) {
|
||||
v := GetTaskEnv(key)
|
||||
if v == "" {
|
||||
return 0, false
|
||||
}
|
||||
i, err := strconv.Atoi(v)
|
||||
return i, err == nil
|
||||
}
|
||||
|
||||
// GetTaskEnvDuration returns the duration value of a TASK_ prefixed env var.
|
||||
// Returns the value and true if set and valid, or 0 and false if not set or invalid.
|
||||
func GetTaskEnvDuration(key string) (time.Duration, bool) {
|
||||
v := GetTaskEnv(key)
|
||||
if v == "" {
|
||||
return 0, false
|
||||
}
|
||||
d, err := time.ParseDuration(v)
|
||||
return d, err == nil
|
||||
}
|
||||
|
||||
// GetTaskEnvString returns the string value of a TASK_ prefixed env var.
|
||||
// Returns the value and true if set (non-empty), or empty string and false if not set.
|
||||
func GetTaskEnvString(key string) (string, bool) {
|
||||
v := GetTaskEnv(key)
|
||||
return v, v != ""
|
||||
}
|
||||
|
||||
// GetTaskEnvStringSlice returns a comma-separated list from a TASK_ prefixed env var.
|
||||
// Returns the slice and true if set (non-empty), or nil and false if not set.
|
||||
func GetTaskEnvStringSlice(key string) ([]string, bool) {
|
||||
v := GetTaskEnv(key)
|
||||
if v == "" {
|
||||
return nil, false
|
||||
}
|
||||
parts := strings.Split(v, ",")
|
||||
result := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
if trimmed := strings.TrimSpace(p); trimmed != "" {
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
20
internal/execext/coreutils.go
Normal file
20
internal/execext/coreutils.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package execext
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
)
|
||||
|
||||
var useGoCoreUtils bool
|
||||
|
||||
func init() {
|
||||
// If TASK_CORE_UTILS is set to either true or false, respect that.
|
||||
// By default, enable on Windows only.
|
||||
if v, err := strconv.ParseBool(env.GetTaskEnv("CORE_UTILS")); err == nil {
|
||||
useGoCoreUtils = v
|
||||
} else {
|
||||
useGoCoreUtils = runtime.GOOS == "windows"
|
||||
}
|
||||
}
|
||||
13
internal/execext/devnull.go
Normal file
13
internal/execext/devnull.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package execext
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
var _ io.ReadWriteCloser = devNull{}
|
||||
|
||||
type devNull struct{}
|
||||
|
||||
func (devNull) Read(p []byte) (int, error) { return 0, io.EOF }
|
||||
func (devNull) Write(p []byte) (int, error) { return len(p), nil }
|
||||
func (devNull) Close() error { return nil }
|
||||
178
internal/execext/exec.go
Normal file
178
internal/execext/exec.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package execext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"mvdan.cc/sh/moreinterp/coreutils"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
// ErrNilOptions is returned when a nil options is given
|
||||
var ErrNilOptions = errors.New("execext: nil options given")
|
||||
|
||||
// RunCommandOptions is the options for the [RunCommand] func.
|
||||
type RunCommandOptions struct {
|
||||
Command string
|
||||
Dir string
|
||||
Env []string
|
||||
PosixOpts []string
|
||||
BashOpts []string
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
// RunCommand runs a shell command
|
||||
func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
if opts == nil {
|
||||
return ErrNilOptions
|
||||
}
|
||||
|
||||
// Set "-e" or "errexit" by default
|
||||
opts.PosixOpts = append(opts.PosixOpts, "e")
|
||||
|
||||
// Format POSIX options into a slice that mvdan/sh understands
|
||||
var params []string
|
||||
for _, opt := range opts.PosixOpts {
|
||||
if len(opt) == 1 {
|
||||
params = append(params, fmt.Sprintf("-%s", opt))
|
||||
} else {
|
||||
params = append(params, "-o")
|
||||
params = append(params, opt)
|
||||
}
|
||||
}
|
||||
|
||||
environ := opts.Env
|
||||
if len(environ) == 0 {
|
||||
environ = os.Environ()
|
||||
}
|
||||
|
||||
r, err := interp.New(
|
||||
interp.Params(params...),
|
||||
interp.Env(expand.ListEnviron(environ...)),
|
||||
interp.ExecHandlers(execHandlers()...),
|
||||
interp.OpenHandler(openHandler),
|
||||
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
||||
dirOption(opts.Dir),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parser := syntax.NewParser()
|
||||
|
||||
// Run any shopt commands
|
||||
if len(opts.BashOpts) > 0 {
|
||||
shoptCmdStr := fmt.Sprintf("shopt -s %s", strings.Join(opts.BashOpts, " "))
|
||||
shoptCmd, err := parser.Parse(strings.NewReader(shoptCmdStr), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.Run(ctx, shoptCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Run the user-defined command
|
||||
p, err := parser.Parse(strings.NewReader(opts.Command), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Run(ctx, p)
|
||||
}
|
||||
|
||||
func escape(s string) string {
|
||||
s = filepath.ToSlash(s)
|
||||
s = strings.ReplaceAll(s, " ", `\ `)
|
||||
s = strings.ReplaceAll(s, "&", `\&`)
|
||||
s = strings.ReplaceAll(s, "(", `\(`)
|
||||
s = strings.ReplaceAll(s, ")", `\)`)
|
||||
return s
|
||||
}
|
||||
|
||||
// ExpandLiteral is a wrapper around [expand.Literal]. It will escape the input
|
||||
// string, expand any shell symbols (such as '~') and resolve any environment
|
||||
// variables.
|
||||
func ExpandLiteral(s string) (string, error) {
|
||||
if s == "" {
|
||||
return "", nil
|
||||
}
|
||||
p := syntax.NewParser()
|
||||
word, err := p.Document(strings.NewReader(s))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cfg := &expand.Config{
|
||||
Env: expand.FuncEnviron(os.Getenv),
|
||||
ReadDir2: os.ReadDir,
|
||||
GlobStar: true,
|
||||
}
|
||||
return expand.Literal(cfg, word)
|
||||
}
|
||||
|
||||
// ExpandFields is a wrapper around [expand.Fields]. It will escape the input
|
||||
// string, expand any shell symbols (such as '~') and resolve any environment
|
||||
// variables. It also expands brace expressions ({a.b}) and globs (*/**) and
|
||||
// returns the results as a list of strings.
|
||||
func ExpandFields(s string) ([]string, error) {
|
||||
s = escape(s)
|
||||
p := syntax.NewParser()
|
||||
var words []*syntax.Word
|
||||
for w, err := range p.WordsSeq(strings.NewReader(s)) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
words = append(words, w)
|
||||
}
|
||||
cfg := &expand.Config{
|
||||
Env: expand.FuncEnviron(os.Getenv),
|
||||
ReadDir2: os.ReadDir,
|
||||
GlobStar: true,
|
||||
NullGlob: true,
|
||||
}
|
||||
return expand.Fields(cfg, words...)
|
||||
}
|
||||
|
||||
func execHandlers() (handlers []func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc) {
|
||||
if useGoCoreUtils {
|
||||
handlers = append(handlers, coreutils.ExecHandler)
|
||||
}
|
||||
return handlers
|
||||
}
|
||||
|
||||
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||
if path == "/dev/null" {
|
||||
return devNull{}, nil
|
||||
}
|
||||
return interp.DefaultOpenHandler()(ctx, path, flag, perm)
|
||||
}
|
||||
|
||||
func dirOption(path string) interp.RunnerOption {
|
||||
return func(r *interp.Runner) error {
|
||||
err := interp.Dir(path)(r)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the specified directory doesn't exist, it will be created later.
|
||||
// Therefore, even if `interp.Dir` method returns an error, the
|
||||
// directory path should be set only when the directory cannot be found.
|
||||
if absPath, _ := filepath.Abs(path); absPath != "" {
|
||||
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
||||
r.Dir = absPath
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
63
internal/filepathext/filepathext.go
Normal file
63
internal/filepathext/filepathext.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package filepathext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SmartJoin joins two paths, but only if the second is not already an
|
||||
// absolute path.
|
||||
func SmartJoin(a, b string) string {
|
||||
if IsAbs(b) {
|
||||
return b
|
||||
}
|
||||
return filepath.Join(a, b)
|
||||
}
|
||||
|
||||
func IsAbs(path string) bool {
|
||||
// NOTE(@andreynering): If the path contains any if the special
|
||||
// variables that we know are absolute, return true.
|
||||
if isSpecialDir(path) {
|
||||
return true
|
||||
}
|
||||
|
||||
return filepath.IsAbs(path)
|
||||
}
|
||||
|
||||
var knownAbsDirs = []string{
|
||||
".ROOT_DIR",
|
||||
".TASKFILE_DIR",
|
||||
".USER_WORKING_DIR",
|
||||
}
|
||||
|
||||
func isSpecialDir(dir string) bool {
|
||||
for _, d := range knownAbsDirs {
|
||||
if strings.Contains(dir, d) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TryAbsToRel tries to convert an absolute path to relative based on the
|
||||
// process working directory. If it can't, it returns the absolute path.
|
||||
func TryAbsToRel(abs string) string {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return abs
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(wd, abs)
|
||||
if err != nil {
|
||||
return abs
|
||||
}
|
||||
|
||||
return rel
|
||||
}
|
||||
|
||||
// IsExtOnly checks whether path points to a file with no name but with
|
||||
// an extension, i.e. ".yaml"
|
||||
func IsExtOnly(path string) bool {
|
||||
return filepath.Base(path) == filepath.Ext(path)
|
||||
}
|
||||
20
internal/fingerprint/checker.go
Normal file
20
internal/fingerprint/checker.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
// StatusCheckable defines any type that can check if the status of a task is up-to-date.
|
||||
type StatusCheckable interface {
|
||||
IsUpToDate(ctx context.Context, t *ast.Task) (bool, error)
|
||||
}
|
||||
|
||||
// SourcesCheckable defines any type that can check if the sources of a task are up-to-date.
|
||||
type SourcesCheckable interface {
|
||||
IsUpToDate(t *ast.Task) (bool, error)
|
||||
Value(t *ast.Task) (any, error)
|
||||
OnError(t *ast.Task) error
|
||||
Kind() string
|
||||
}
|
||||
320
internal/fingerprint/checker_mock.go
Normal file
320
internal/fingerprint/checker_mock.go
Normal file
@@ -0,0 +1,320 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// NewMockStatusCheckable creates a new instance of MockStatusCheckable. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockStatusCheckable(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockStatusCheckable {
|
||||
mock := &MockStatusCheckable{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// MockStatusCheckable is an autogenerated mock type for the StatusCheckable type
|
||||
type MockStatusCheckable struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockStatusCheckable_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockStatusCheckable) EXPECT() *MockStatusCheckable_Expecter {
|
||||
return &MockStatusCheckable_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// IsUpToDate provides a mock function for the type MockStatusCheckable
|
||||
func (_mock *MockStatusCheckable) IsUpToDate(ctx context.Context, t *ast.Task) (bool, error) {
|
||||
ret := _mock.Called(ctx, t)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IsUpToDate")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, *ast.Task) (bool, error)); ok {
|
||||
return returnFunc(ctx, t)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, *ast.Task) bool); ok {
|
||||
r0 = returnFunc(ctx, t)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, *ast.Task) error); ok {
|
||||
r1 = returnFunc(ctx, t)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockStatusCheckable_IsUpToDate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsUpToDate'
|
||||
type MockStatusCheckable_IsUpToDate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IsUpToDate is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - t
|
||||
func (_e *MockStatusCheckable_Expecter) IsUpToDate(ctx interface{}, t interface{}) *MockStatusCheckable_IsUpToDate_Call {
|
||||
return &MockStatusCheckable_IsUpToDate_Call{Call: _e.mock.On("IsUpToDate", ctx, t)}
|
||||
}
|
||||
|
||||
func (_c *MockStatusCheckable_IsUpToDate_Call) Run(run func(ctx context.Context, t *ast.Task)) *MockStatusCheckable_IsUpToDate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(*ast.Task))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockStatusCheckable_IsUpToDate_Call) Return(b bool, err error) *MockStatusCheckable_IsUpToDate_Call {
|
||||
_c.Call.Return(b, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockStatusCheckable_IsUpToDate_Call) RunAndReturn(run func(ctx context.Context, t *ast.Task) (bool, error)) *MockStatusCheckable_IsUpToDate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockSourcesCheckable creates a new instance of MockSourcesCheckable. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockSourcesCheckable(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockSourcesCheckable {
|
||||
mock := &MockSourcesCheckable{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// MockSourcesCheckable is an autogenerated mock type for the SourcesCheckable type
|
||||
type MockSourcesCheckable struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockSourcesCheckable_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockSourcesCheckable) EXPECT() *MockSourcesCheckable_Expecter {
|
||||
return &MockSourcesCheckable_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// IsUpToDate provides a mock function for the type MockSourcesCheckable
|
||||
func (_mock *MockSourcesCheckable) IsUpToDate(t *ast.Task) (bool, error) {
|
||||
ret := _mock.Called(t)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IsUpToDate")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(*ast.Task) (bool, error)); ok {
|
||||
return returnFunc(t)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(*ast.Task) bool); ok {
|
||||
r0 = returnFunc(t)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(*ast.Task) error); ok {
|
||||
r1 = returnFunc(t)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockSourcesCheckable_IsUpToDate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsUpToDate'
|
||||
type MockSourcesCheckable_IsUpToDate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IsUpToDate is a helper method to define mock.On call
|
||||
// - t
|
||||
func (_e *MockSourcesCheckable_Expecter) IsUpToDate(t interface{}) *MockSourcesCheckable_IsUpToDate_Call {
|
||||
return &MockSourcesCheckable_IsUpToDate_Call{Call: _e.mock.On("IsUpToDate", t)}
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_IsUpToDate_Call) Run(run func(t *ast.Task)) *MockSourcesCheckable_IsUpToDate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*ast.Task))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_IsUpToDate_Call) Return(b bool, err error) *MockSourcesCheckable_IsUpToDate_Call {
|
||||
_c.Call.Return(b, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_IsUpToDate_Call) RunAndReturn(run func(t *ast.Task) (bool, error)) *MockSourcesCheckable_IsUpToDate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Kind provides a mock function for the type MockSourcesCheckable
|
||||
func (_mock *MockSourcesCheckable) Kind() string {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Kind")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if returnFunc, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockSourcesCheckable_Kind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Kind'
|
||||
type MockSourcesCheckable_Kind_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Kind is a helper method to define mock.On call
|
||||
func (_e *MockSourcesCheckable_Expecter) Kind() *MockSourcesCheckable_Kind_Call {
|
||||
return &MockSourcesCheckable_Kind_Call{Call: _e.mock.On("Kind")}
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_Kind_Call) Run(run func()) *MockSourcesCheckable_Kind_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_Kind_Call) Return(s string) *MockSourcesCheckable_Kind_Call {
|
||||
_c.Call.Return(s)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_Kind_Call) RunAndReturn(run func() string) *MockSourcesCheckable_Kind_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// OnError provides a mock function for the type MockSourcesCheckable
|
||||
func (_mock *MockSourcesCheckable) OnError(t *ast.Task) error {
|
||||
ret := _mock.Called(t)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for OnError")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(*ast.Task) error); ok {
|
||||
r0 = returnFunc(t)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockSourcesCheckable_OnError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OnError'
|
||||
type MockSourcesCheckable_OnError_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// OnError is a helper method to define mock.On call
|
||||
// - t
|
||||
func (_e *MockSourcesCheckable_Expecter) OnError(t interface{}) *MockSourcesCheckable_OnError_Call {
|
||||
return &MockSourcesCheckable_OnError_Call{Call: _e.mock.On("OnError", t)}
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_OnError_Call) Run(run func(t *ast.Task)) *MockSourcesCheckable_OnError_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*ast.Task))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_OnError_Call) Return(err error) *MockSourcesCheckable_OnError_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_OnError_Call) RunAndReturn(run func(t *ast.Task) error) *MockSourcesCheckable_OnError_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Value provides a mock function for the type MockSourcesCheckable
|
||||
func (_mock *MockSourcesCheckable) Value(t *ast.Task) (any, error) {
|
||||
ret := _mock.Called(t)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Value")
|
||||
}
|
||||
|
||||
var r0 any
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(*ast.Task) (any, error)); ok {
|
||||
return returnFunc(t)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(*ast.Task) any); ok {
|
||||
r0 = returnFunc(t)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(any)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(*ast.Task) error); ok {
|
||||
r1 = returnFunc(t)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockSourcesCheckable_Value_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Value'
|
||||
type MockSourcesCheckable_Value_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Value is a helper method to define mock.On call
|
||||
// - t
|
||||
func (_e *MockSourcesCheckable_Expecter) Value(t interface{}) *MockSourcesCheckable_Value_Call {
|
||||
return &MockSourcesCheckable_Value_Call{Call: _e.mock.On("Value", t)}
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_Value_Call) Run(run func(t *ast.Task)) *MockSourcesCheckable_Value_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*ast.Task))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_Value_Call) Return(v any, err error) *MockSourcesCheckable_Value_Call {
|
||||
_c.Call.Return(v, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSourcesCheckable_Value_Call) RunAndReturn(run func(t *ast.Task) (any, error)) *MockSourcesCheckable_Value_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
60
internal/fingerprint/glob.go
Normal file
60
internal/fingerprint/glob.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func Globs(dir string, globs []*ast.Glob) ([]string, error) {
|
||||
resultMap := make(map[string]bool)
|
||||
for _, g := range globs {
|
||||
matches, err := glob(dir, g.Glob)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, match := range matches {
|
||||
resultMap[match] = !g.Negate
|
||||
}
|
||||
}
|
||||
return collectKeys(resultMap), nil
|
||||
}
|
||||
|
||||
func glob(dir string, g string) ([]string, error) {
|
||||
g = filepathext.SmartJoin(dir, g)
|
||||
|
||||
fs, err := execext.ExpandFields(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make(map[string]bool, len(fs))
|
||||
|
||||
for _, f := range fs {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.IsDir() {
|
||||
continue
|
||||
}
|
||||
results[f] = true
|
||||
}
|
||||
return collectKeys(results), nil
|
||||
}
|
||||
|
||||
func collectKeys(m map[string]bool) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k, v := range m {
|
||||
if v {
|
||||
// Normalize path separators for consistent sorting across platforms
|
||||
keys = append(keys, filepath.ToSlash(k))
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
16
internal/fingerprint/sources.go
Normal file
16
internal/fingerprint/sources.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package fingerprint
|
||||
|
||||
import "fmt"
|
||||
|
||||
func NewSourcesChecker(method, tempDir string, dry bool) (SourcesCheckable, error) {
|
||||
switch method {
|
||||
case "timestamp":
|
||||
return NewTimestampChecker(tempDir, dry), nil
|
||||
case "checksum":
|
||||
return NewChecksumChecker(tempDir, dry), nil
|
||||
case "none":
|
||||
return NoneChecker{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf(`task: invalid method "%s"`, method)
|
||||
}
|
||||
}
|
||||
127
internal/fingerprint/sources_checksum.go
Normal file
127
internal/fingerprint/sources_checksum.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/zeebo/xxh3"
|
||||
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
// ChecksumChecker validates if a task is up to date by calculating its source
|
||||
// files checksum
|
||||
type ChecksumChecker struct {
|
||||
tempDir string
|
||||
dry bool
|
||||
}
|
||||
|
||||
func NewChecksumChecker(tempDir string, dry bool) *ChecksumChecker {
|
||||
return &ChecksumChecker{
|
||||
tempDir: tempDir,
|
||||
dry: dry,
|
||||
}
|
||||
}
|
||||
|
||||
func (checker *ChecksumChecker) IsUpToDate(t *ast.Task) (bool, error) {
|
||||
if len(t.Sources) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
checksumFile := checker.checksumFilePath(t)
|
||||
|
||||
data, _ := os.ReadFile(checksumFile)
|
||||
oldHash := strings.TrimSpace(string(data))
|
||||
|
||||
newHash, err := checker.checksum(t)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !checker.dry && oldHash != newHash {
|
||||
_ = os.MkdirAll(filepathext.SmartJoin(checker.tempDir, "checksum"), 0o755)
|
||||
if err = os.WriteFile(checksumFile, []byte(newHash+"\n"), 0o644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.Generates) > 0 {
|
||||
// For each specified 'generates' field, check whether the files actually exist
|
||||
for _, g := range t.Generates {
|
||||
// Exclusion patterns don't represent output files; skip them.
|
||||
if g.Negate {
|
||||
continue
|
||||
}
|
||||
generates, err := glob(t.Dir, g.Glob)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(generates) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return oldHash == newHash, nil
|
||||
}
|
||||
|
||||
func (checker *ChecksumChecker) Value(t *ast.Task) (any, error) {
|
||||
return checker.checksum(t)
|
||||
}
|
||||
|
||||
func (checker *ChecksumChecker) OnError(t *ast.Task) error {
|
||||
if len(t.Sources) == 0 {
|
||||
return nil
|
||||
}
|
||||
return os.Remove(checker.checksumFilePath(t))
|
||||
}
|
||||
|
||||
func (*ChecksumChecker) Kind() string {
|
||||
return "checksum"
|
||||
}
|
||||
|
||||
func (c *ChecksumChecker) checksum(t *ast.Task) (string, error) {
|
||||
sources, err := Globs(t.Dir, t.Sources)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h := xxh3.New()
|
||||
buf := make([]byte, 128*1024)
|
||||
for _, f := range sources {
|
||||
// also sum the filename, so checksum changes for renaming a file
|
||||
if _, err := io.CopyBuffer(h, strings.NewReader(filepath.Base(f)), buf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
f, err := os.Open(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err = io.CopyBuffer(h, f, buf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
hash := h.Sum128()
|
||||
return fmt.Sprintf("%x%x", hash.Hi, hash.Lo), nil
|
||||
}
|
||||
|
||||
func (checker *ChecksumChecker) checksumFilePath(t *ast.Task) string {
|
||||
return filepath.Join(checker.tempDir, "checksum", normalizeFilename(t.Name()))
|
||||
}
|
||||
|
||||
var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")
|
||||
|
||||
// replaces invalid characters on filenames with "-"
|
||||
func normalizeFilename(f string) string {
|
||||
return checksumFilenameRegexp.ReplaceAllString(f, "-")
|
||||
}
|
||||
23
internal/fingerprint/sources_checksum_test.go
Normal file
23
internal/fingerprint/sources_checksum_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNormalizeFilename(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
In, Out string
|
||||
}{
|
||||
{"foobarbaz", "foobarbaz"},
|
||||
{"foo/bar/baz", "foo-bar-baz"},
|
||||
{"foo@bar/baz", "foo-bar-baz"},
|
||||
{"foo1bar2baz3", "foo1bar2baz3"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
assert.Equal(t, test.Out, normalizeFilename(test.In))
|
||||
}
|
||||
}
|
||||
23
internal/fingerprint/sources_none.go
Normal file
23
internal/fingerprint/sources_none.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package fingerprint
|
||||
|
||||
import "github.com/go-task/task/v3/taskfile/ast"
|
||||
|
||||
// NoneChecker is a no-op Checker.
|
||||
// It will always report that the task is not up-to-date.
|
||||
type NoneChecker struct{}
|
||||
|
||||
func (NoneChecker) IsUpToDate(t *ast.Task) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (NoneChecker) Value(t *ast.Task) (any, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (NoneChecker) OnError(t *ast.Task) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NoneChecker) Kind() string {
|
||||
return "none"
|
||||
}
|
||||
173
internal/fingerprint/sources_timestamp.go
Normal file
173
internal/fingerprint/sources_timestamp.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
// TimestampChecker checks if any source change compared with the generated files,
|
||||
// using file modifications timestamps.
|
||||
type TimestampChecker struct {
|
||||
tempDir string
|
||||
dry bool
|
||||
}
|
||||
|
||||
func NewTimestampChecker(tempDir string, dry bool) *TimestampChecker {
|
||||
return &TimestampChecker{
|
||||
tempDir: tempDir,
|
||||
dry: dry,
|
||||
}
|
||||
}
|
||||
|
||||
// IsUpToDate implements the Checker interface
|
||||
func (checker *TimestampChecker) IsUpToDate(t *ast.Task) (bool, error) {
|
||||
if len(t.Sources) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
sources, err := Globs(t.Dir, t.Sources)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// If generates are declared, ensure they all exist. A missing generated
|
||||
// file means the task must run regardless of timestamps.
|
||||
if len(t.Generates) > 0 {
|
||||
for _, g := range t.Generates {
|
||||
// Exclusion patterns don't represent output files; skip them.
|
||||
if g.Negate {
|
||||
continue
|
||||
}
|
||||
files, err := glob(t.Dir, g.Glob)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(files) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generates, err := Globs(t.Dir, t.Generates)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
timestampFile := checker.timestampFilePath(t)
|
||||
|
||||
// If the file exists, add the file path to the generates.
|
||||
// If the generate file is old, the task will be executed.
|
||||
_, err = os.Stat(timestampFile)
|
||||
if err == nil {
|
||||
generates = append(generates, timestampFile)
|
||||
} else {
|
||||
// Create the timestamp file for the next execution when the file does not exist.
|
||||
if !checker.dry {
|
||||
if err := os.MkdirAll(filepath.Dir(timestampFile), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
f, err := os.Create(timestampFile)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
taskTime := time.Now()
|
||||
|
||||
// Compare the time of the generates and sources. If the generates are old, the task will be executed.
|
||||
|
||||
// Get the max time of the generates.
|
||||
generateMaxTime, err := getMaxTime(generates...)
|
||||
if err != nil || generateMaxTime.IsZero() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Check if any of the source files is newer than the max time of the generates.
|
||||
shouldUpdate, err := anyFileNewerThan(sources, generateMaxTime)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Modify the metadata of the file to the the current time.
|
||||
if !checker.dry {
|
||||
if err := os.Chtimes(timestampFile, taskTime, taskTime); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return !shouldUpdate, nil
|
||||
}
|
||||
|
||||
func (checker *TimestampChecker) Kind() string {
|
||||
return "timestamp"
|
||||
}
|
||||
|
||||
// Value implements the Checker Interface
|
||||
func (checker *TimestampChecker) Value(t *ast.Task) (any, error) {
|
||||
sources, err := Globs(t.Dir, t.Sources)
|
||||
if err != nil {
|
||||
return time.Now(), err
|
||||
}
|
||||
|
||||
sourcesMaxTime, err := getMaxTime(sources...)
|
||||
if err != nil {
|
||||
return time.Now(), err
|
||||
}
|
||||
|
||||
if sourcesMaxTime.IsZero() {
|
||||
return time.Unix(0, 0), nil
|
||||
}
|
||||
|
||||
return sourcesMaxTime, nil
|
||||
}
|
||||
|
||||
func getMaxTime(files ...string) (time.Time, error) {
|
||||
var t time.Time
|
||||
for _, f := range files {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
t = maxTime(t, info.ModTime())
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func maxTime(a, b time.Time) time.Time {
|
||||
if a.After(b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// If the modification time of any of the files is newer than the the given time, returns true.
|
||||
// This function is lazy, as it stops when it finds a file newer than the given time.
|
||||
func anyFileNewerThan(files []string, givenTime time.Time) (bool, error) {
|
||||
for _, f := range files {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if info.ModTime().After(givenTime) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// OnError implements the Checker interface
|
||||
func (*TimestampChecker) OnError(t *ast.Task) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (checker *TimestampChecker) timestampFilePath(t *ast.Task) string {
|
||||
return filepath.Join(checker.tempDir, "timestamp", normalizeFilename(t.Task))
|
||||
}
|
||||
36
internal/fingerprint/status.go
Normal file
36
internal/fingerprint/status.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
type StatusChecker struct {
|
||||
logger *logger.Logger
|
||||
}
|
||||
|
||||
func NewStatusChecker(logger *logger.Logger) StatusCheckable {
|
||||
return &StatusChecker{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (checker *StatusChecker) IsUpToDate(ctx context.Context, t *ast.Task) (bool, error) {
|
||||
for _, s := range t.Status {
|
||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: s,
|
||||
Dir: t.Dir,
|
||||
Env: env.Get(t),
|
||||
})
|
||||
if err != nil {
|
||||
checker.logger.VerboseOutf(logger.Yellow, "task: status command %s exited non-zero: %s\n", s, err)
|
||||
return false, nil
|
||||
}
|
||||
checker.logger.VerboseOutf(logger.Yellow, "task: status command %s exited zero\n", s)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
132
internal/fingerprint/task.go
Normal file
132
internal/fingerprint/task.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
type (
|
||||
CheckerOption func(*CheckerConfig)
|
||||
CheckerConfig struct {
|
||||
method string
|
||||
dry bool
|
||||
tempDir string
|
||||
logger *logger.Logger
|
||||
statusChecker StatusCheckable
|
||||
sourcesChecker SourcesCheckable
|
||||
}
|
||||
)
|
||||
|
||||
func WithMethod(method string) CheckerOption {
|
||||
return func(config *CheckerConfig) {
|
||||
config.method = method
|
||||
}
|
||||
}
|
||||
|
||||
func WithDry(dry bool) CheckerOption {
|
||||
return func(config *CheckerConfig) {
|
||||
config.dry = dry
|
||||
}
|
||||
}
|
||||
|
||||
func WithTempDir(tempDir string) CheckerOption {
|
||||
return func(config *CheckerConfig) {
|
||||
config.tempDir = tempDir
|
||||
}
|
||||
}
|
||||
|
||||
func WithLogger(logger *logger.Logger) CheckerOption {
|
||||
return func(config *CheckerConfig) {
|
||||
config.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
func WithStatusChecker(checker StatusCheckable) CheckerOption {
|
||||
return func(config *CheckerConfig) {
|
||||
config.statusChecker = checker
|
||||
}
|
||||
}
|
||||
|
||||
func WithSourcesChecker(checker SourcesCheckable) CheckerOption {
|
||||
return func(config *CheckerConfig) {
|
||||
config.sourcesChecker = checker
|
||||
}
|
||||
}
|
||||
|
||||
func IsTaskUpToDate(
|
||||
ctx context.Context,
|
||||
t *ast.Task,
|
||||
opts ...CheckerOption,
|
||||
) (bool, error) {
|
||||
var statusUpToDate bool
|
||||
var sourcesUpToDate bool
|
||||
var err error
|
||||
|
||||
// Default config
|
||||
config := &CheckerConfig{
|
||||
method: "none",
|
||||
tempDir: "",
|
||||
dry: false,
|
||||
logger: nil,
|
||||
statusChecker: nil,
|
||||
sourcesChecker: nil,
|
||||
}
|
||||
|
||||
// Apply functional options
|
||||
for _, opt := range opts {
|
||||
opt(config)
|
||||
}
|
||||
|
||||
// If no status checker was given, set up the default one
|
||||
if config.statusChecker == nil {
|
||||
config.statusChecker = NewStatusChecker(config.logger)
|
||||
}
|
||||
|
||||
// If no sources checker was given, set up the default one
|
||||
if config.sourcesChecker == nil {
|
||||
config.sourcesChecker, err = NewSourcesChecker(config.method, config.tempDir, config.dry)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
statusIsSet := len(t.Status) != 0
|
||||
sourcesIsSet := len(t.Sources) != 0
|
||||
|
||||
// If status is set, check if it is up-to-date
|
||||
if statusIsSet {
|
||||
statusUpToDate, err = config.statusChecker.IsUpToDate(ctx, t)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
// If sources is set, check if they are up-to-date
|
||||
if sourcesIsSet {
|
||||
sourcesUpToDate, err = config.sourcesChecker.IsUpToDate(t)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
// If both status and sources are set, the task is up-to-date if both are up-to-date
|
||||
if statusIsSet && sourcesIsSet {
|
||||
return statusUpToDate && sourcesUpToDate, nil
|
||||
}
|
||||
|
||||
// If only status is set, the task is up-to-date if the status is up-to-date
|
||||
if statusIsSet {
|
||||
return statusUpToDate, nil
|
||||
}
|
||||
|
||||
// If only sources is set, the task is up-to-date if the sources are up-to-date
|
||||
if sourcesIsSet {
|
||||
return sourcesUpToDate, nil
|
||||
}
|
||||
|
||||
// If no status or sources are set, the task should always run
|
||||
// i.e. it is never considered "up-to-date"
|
||||
return false, nil
|
||||
}
|
||||
175
internal/fingerprint/task_test.go
Normal file
175
internal/fingerprint/task_test.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
// TruthTable
|
||||
//
|
||||
// | Status up-to-date | Sources up-to-date | Task is up-to-date |
|
||||
// | ----------------- | ------------------ | ------------------ |
|
||||
// | not set | not set | false |
|
||||
// | not set | true | true |
|
||||
// | not set | false | false |
|
||||
// | true | not set | true |
|
||||
// | true | true | true |
|
||||
// | true | false | false |
|
||||
// | false | not set | false |
|
||||
// | false | true | false |
|
||||
// | false | false | false |
|
||||
func TestIsTaskUpToDate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
task *ast.Task
|
||||
setupMockStatusChecker func(m *MockStatusCheckable)
|
||||
setupMockSourcesChecker func(m *MockSourcesCheckable)
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "expect FALSE when no status or sources are defined",
|
||||
task: &ast.Task{
|
||||
Status: nil,
|
||||
Sources: nil,
|
||||
},
|
||||
setupMockStatusChecker: nil,
|
||||
setupMockSourcesChecker: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "expect TRUE when no status is defined and sources are up-to-date",
|
||||
task: &ast.Task{
|
||||
Status: nil,
|
||||
Sources: []*ast.Glob{{Glob: "sources"}},
|
||||
},
|
||||
setupMockStatusChecker: nil,
|
||||
setupMockSourcesChecker: func(m *MockSourcesCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything).Return(true, nil)
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "expect FALSE when no status is defined and sources are NOT up-to-date",
|
||||
task: &ast.Task{
|
||||
Status: nil,
|
||||
Sources: []*ast.Glob{{Glob: "sources"}},
|
||||
},
|
||||
setupMockStatusChecker: nil,
|
||||
setupMockSourcesChecker: func(m *MockSourcesCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything).Return(false, nil)
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "expect TRUE when status is up-to-date and sources are not defined",
|
||||
task: &ast.Task{
|
||||
Status: []string{"status"},
|
||||
Sources: nil,
|
||||
},
|
||||
setupMockStatusChecker: func(m *MockStatusCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything, mock.Anything).Return(true, nil)
|
||||
},
|
||||
setupMockSourcesChecker: nil,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "expect TRUE when status and sources are up-to-date",
|
||||
task: &ast.Task{
|
||||
Status: []string{"status"},
|
||||
Sources: []*ast.Glob{{Glob: "sources"}},
|
||||
},
|
||||
setupMockStatusChecker: func(m *MockStatusCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything, mock.Anything).Return(true, nil)
|
||||
},
|
||||
setupMockSourcesChecker: func(m *MockSourcesCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything).Return(true, nil)
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "expect FALSE when status is up-to-date, but sources are NOT up-to-date",
|
||||
task: &ast.Task{
|
||||
Status: []string{"status"},
|
||||
Sources: []*ast.Glob{{Glob: "sources"}},
|
||||
},
|
||||
setupMockStatusChecker: func(m *MockStatusCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything, mock.Anything).Return(true, nil)
|
||||
},
|
||||
setupMockSourcesChecker: func(m *MockSourcesCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything).Return(false, nil)
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "expect FALSE when status is NOT up-to-date and sources are not defined",
|
||||
task: &ast.Task{
|
||||
Status: []string{"status"},
|
||||
Sources: nil,
|
||||
},
|
||||
setupMockStatusChecker: func(m *MockStatusCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything, mock.Anything).Return(false, nil)
|
||||
},
|
||||
setupMockSourcesChecker: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "expect FALSE when status is NOT up-to-date, but sources are up-to-date",
|
||||
task: &ast.Task{
|
||||
Status: []string{"status"},
|
||||
Sources: []*ast.Glob{{Glob: "sources"}},
|
||||
},
|
||||
setupMockStatusChecker: func(m *MockStatusCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything, mock.Anything).Return(false, nil)
|
||||
},
|
||||
setupMockSourcesChecker: func(m *MockSourcesCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything).Return(true, nil)
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "expect FALSE when status and sources are NOT up-to-date",
|
||||
task: &ast.Task{
|
||||
Status: []string{"status"},
|
||||
Sources: []*ast.Glob{{Glob: "sources"}},
|
||||
},
|
||||
setupMockStatusChecker: func(m *MockStatusCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything, mock.Anything).Return(false, nil)
|
||||
},
|
||||
setupMockSourcesChecker: func(m *MockSourcesCheckable) {
|
||||
m.EXPECT().IsUpToDate(mock.Anything).Return(false, nil)
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mockStatusChecker := NewMockStatusCheckable(t)
|
||||
if tt.setupMockStatusChecker != nil {
|
||||
tt.setupMockStatusChecker(mockStatusChecker)
|
||||
}
|
||||
|
||||
mockSourcesChecker := NewMockSourcesCheckable(t)
|
||||
if tt.setupMockSourcesChecker != nil {
|
||||
tt.setupMockSourcesChecker(mockSourcesChecker)
|
||||
}
|
||||
|
||||
result, err := IsTaskUpToDate(
|
||||
t.Context(),
|
||||
tt.task,
|
||||
WithStatusChecker(mockStatusChecker),
|
||||
WithSourcesChecker(mockSourcesChecker),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
355
internal/flags/flags.go
Normal file
355
internal/flags/flags.go
Normal file
@@ -0,0 +1,355 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/sort"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
"github.com/go-task/task/v3/taskrc"
|
||||
taskrcast "github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
|
||||
const usage = `Usage: task [flags...] [task...]
|
||||
|
||||
Runs the specified task(s). Falls back to the "default" task if no task name
|
||||
was specified, or lists all tasks if an unknown task name was specified.
|
||||
|
||||
Example: 'task hello' with the following 'Taskfile.yml' file will generate an
|
||||
'output.txt' file with the content "hello".
|
||||
|
||||
'''
|
||||
version: '3'
|
||||
tasks:
|
||||
hello:
|
||||
cmds:
|
||||
- echo "I am going to write a file named 'output.txt' now."
|
||||
- echo "hello" > output.txt
|
||||
generates:
|
||||
- output.txt
|
||||
'''
|
||||
|
||||
Options:
|
||||
`
|
||||
|
||||
var (
|
||||
Version bool
|
||||
Help bool
|
||||
Init bool
|
||||
Completion string
|
||||
List bool
|
||||
ListAll bool
|
||||
ListJson bool
|
||||
TaskSort string
|
||||
Status bool
|
||||
NoStatus bool
|
||||
Nested bool
|
||||
Insecure bool
|
||||
Force bool
|
||||
ForceAll bool
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
DisableFuzzy bool
|
||||
AssumeYes bool
|
||||
Dry bool
|
||||
Summary bool
|
||||
ExitCode bool
|
||||
Parallel bool
|
||||
Concurrency int
|
||||
Dir string
|
||||
Entrypoint string
|
||||
Output ast.Output
|
||||
Color bool
|
||||
Interval time.Duration
|
||||
Failfast bool
|
||||
Global bool
|
||||
Experiments bool
|
||||
Download bool
|
||||
Offline bool
|
||||
TrustedHosts []string
|
||||
ClearCache bool
|
||||
Timeout time.Duration
|
||||
CacheExpiryDuration time.Duration
|
||||
RemoteCacheDir string
|
||||
CACert string
|
||||
Cert string
|
||||
CertKey string
|
||||
Interactive bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Config files can enable experiments which alter the availability and/or
|
||||
// behavior of some flags, so we need to parse the experiments before the
|
||||
// flags. However, we need the --taskfile and --dir flags before we can
|
||||
// parse the experiments as they can alter the location of the config files.
|
||||
// Because of this circular dependency, we parse the flags twice. First, we
|
||||
// get the --taskfile and --dir flags, then we parse the experiments, then
|
||||
// we parse the flags again to get the full set. We use a flagset here so
|
||||
// that we can parse a subset of flags without exiting on error.
|
||||
var dir, entrypoint string
|
||||
fs := pflag.NewFlagSet("experiments", pflag.ContinueOnError)
|
||||
fs.StringVarP(&dir, "dir", "d", "", "")
|
||||
fs.StringVarP(&entrypoint, "taskfile", "t", "", "")
|
||||
fs.Usage = func() {}
|
||||
_ = fs.Parse(os.Args[1:])
|
||||
|
||||
// Parse the experiments
|
||||
dir = cmp.Or(dir, filepath.Dir(entrypoint))
|
||||
|
||||
config, _ := taskrc.GetConfig(dir)
|
||||
experiments.ParseWithConfig(dir, config)
|
||||
|
||||
// Parse the rest of the flags
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(os.Stderr)
|
||||
pflag.Usage = func() {
|
||||
log.Print(usage)
|
||||
pflag.PrintDefaults()
|
||||
}
|
||||
|
||||
pflag.BoolVar(&Version, "version", false, "Show Task version.")
|
||||
pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
|
||||
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
|
||||
pflag.StringVar(&Completion, "completion", "", "Generates shell completion script.")
|
||||
pflag.BoolVarP(&List, "list", "l", false, "Lists tasks with description of current Taskfile.")
|
||||
pflag.BoolVarP(&ListAll, "list-all", "a", false, "Lists tasks with or without a description.")
|
||||
pflag.BoolVarP(&ListJson, "json", "j", false, "Formats task list as JSON.")
|
||||
pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].")
|
||||
pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.")
|
||||
pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON")
|
||||
pflag.BoolVar(&Nested, "nested", false, "Nest namespaces when listing tasks as JSON")
|
||||
pflag.BoolVar(&Insecure, "insecure", getConfig(config, "REMOTE_INSECURE", func() *bool { return config.Remote.Insecure }, false), "Forces Task to download Taskfiles over insecure connections.")
|
||||
pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.")
|
||||
pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, "VERBOSE", func() *bool { return config.Verbose }, false), "Enables verbose mode.")
|
||||
pflag.BoolVarP(&Silent, "silent", "s", getConfig(config, "SILENT", func() *bool { return config.Silent }, false), "Disables echoing.")
|
||||
pflag.BoolVar(&DisableFuzzy, "disable-fuzzy", getConfig(config, "DISABLE_FUZZY", func() *bool { return config.DisableFuzzy }, false), "Disables fuzzy matching for task names.")
|
||||
pflag.BoolVarP(&AssumeYes, "yes", "y", getConfig(config, "ASSUME_YES", func() *bool { return nil }, false), "Assume \"yes\" as answer to all prompts.")
|
||||
pflag.BoolVar(&Interactive, "interactive", getConfig(config, "INTERACTIVE", func() *bool { return config.Interactive }, false), "Prompt for missing required variables.")
|
||||
pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.")
|
||||
pflag.BoolVarP(&Dry, "dry", "n", getConfig(config, "DRY", func() *bool { return nil }, false), "Compiles and prints tasks in the order that they would be run, without executing them.")
|
||||
pflag.BoolVar(&Summary, "summary", false, "Show summary about a task.")
|
||||
pflag.BoolVarP(&ExitCode, "exit-code", "x", false, "Pass-through the exit code of the task command.")
|
||||
pflag.StringVarP(&Dir, "dir", "d", "", "Sets the directory in which Task will execute and look for a Taskfile.")
|
||||
pflag.StringVarP(&Entrypoint, "taskfile", "t", "", `Choose which Taskfile to run. Defaults to "Taskfile.yml".`)
|
||||
pflag.StringVarP(&Output.Name, "output", "o", "", "Sets output style: [interleaved|group|prefixed].")
|
||||
pflag.StringVar(&Output.Group.Begin, "output-group-begin", "", "Message template to print before a task's grouped output.")
|
||||
pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.")
|
||||
pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.")
|
||||
pflag.BoolVarP(&Color, "color", "c", getConfig(config, "COLOR", func() *bool { return config.Color }, true), "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
|
||||
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, "CONCURRENCY", func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.")
|
||||
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
|
||||
pflag.BoolVarP(&Failfast, "failfast", "F", getConfig(config, "FAILFAST", func() *bool { return &config.Failfast }, false), "When running tasks in parallel, stop all tasks if one fails.")
|
||||
pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
|
||||
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
|
||||
|
||||
// Gentle force experiment will override the force flag and add a new force-all flag
|
||||
if experiments.GentleForce.Enabled() {
|
||||
pflag.BoolVarP(&Force, "force", "f", false, "Forces execution of the directly called task.")
|
||||
pflag.BoolVar(&ForceAll, "force-all", false, "Forces execution of the called task and all its dependant tasks.")
|
||||
} else {
|
||||
pflag.BoolVarP(&ForceAll, "force", "f", false, "Forces execution even when the task is up-to-date.")
|
||||
}
|
||||
|
||||
// Remote Taskfiles experiment will adds the "download" and "offline" flags
|
||||
if experiments.RemoteTaskfiles.Enabled() {
|
||||
pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
|
||||
pflag.BoolVar(&Offline, "offline", getConfig(config, "REMOTE_OFFLINE", func() *bool { return config.Remote.Offline }, false), "Forces Task to only use local or cached Taskfiles.")
|
||||
pflag.StringSliceVar(&TrustedHosts, "trusted-hosts", getConfig(config, "REMOTE_TRUSTED_HOSTS", func() *[]string { return &config.Remote.TrustedHosts }, nil), "List of trusted hosts for remote Taskfiles (comma-separated).")
|
||||
pflag.DurationVar(&Timeout, "timeout", getConfig(config, "REMOTE_TIMEOUT", func() *time.Duration { return config.Remote.Timeout }, time.Second*10), "Timeout for downloading remote Taskfiles.")
|
||||
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
|
||||
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, "REMOTE_CACHE_EXPIRY", func() *time.Duration { return config.Remote.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.")
|
||||
pflag.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, "REMOTE_CACHE_DIR", func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
|
||||
pflag.StringVar(&CACert, "cacert", getConfig(config, "REMOTE_CACERT", func() *string { return config.Remote.CACert }, ""), "Path to a custom CA certificate for HTTPS connections.")
|
||||
pflag.StringVar(&Cert, "cert", getConfig(config, "REMOTE_CERT", func() *string { return config.Remote.Cert }, ""), "Path to a client certificate for HTTPS connections.")
|
||||
pflag.StringVar(&CertKey, "cert-key", getConfig(config, "REMOTE_CERT_KEY", func() *string { return config.Remote.CertKey }, ""), "Path to a client certificate key for HTTPS connections.")
|
||||
}
|
||||
pflag.Parse()
|
||||
|
||||
// Auto-detect color based on environment when not explicitly configured
|
||||
// Priority: CLI flag > TASK_COLOR env > taskrc config > NO_COLOR > FORCE_COLOR/CI > default
|
||||
colorExplicitlySet := pflag.Lookup("color").Changed || env.GetTaskEnv("COLOR") != "" || (config != nil && config.Color != nil)
|
||||
if !colorExplicitlySet {
|
||||
if os.Getenv("NO_COLOR") != "" {
|
||||
Color = false
|
||||
color.NoColor = true
|
||||
} else if os.Getenv("FORCE_COLOR") != "" || isCI() {
|
||||
Color = true
|
||||
color.NoColor = false // Force colors even without TTY
|
||||
}
|
||||
// Otherwise, let fatih/color auto-detect TTY
|
||||
} else {
|
||||
// Explicit config: sync with fatih/color
|
||||
color.NoColor = !Color
|
||||
}
|
||||
}
|
||||
|
||||
// isCI returns true if running in a CI environment
|
||||
func isCI() bool {
|
||||
ci, _ := strconv.ParseBool(os.Getenv("CI"))
|
||||
return ci
|
||||
}
|
||||
|
||||
func Validate() error {
|
||||
if Download && Offline {
|
||||
return errors.New("task: You can't set both --download and --offline flags")
|
||||
}
|
||||
|
||||
if Download && ClearCache {
|
||||
return errors.New("task: You can't set both --download and --clear-cache flags")
|
||||
}
|
||||
|
||||
if Global && Dir != "" {
|
||||
return errors.New("task: You can't set both --global and --dir")
|
||||
}
|
||||
|
||||
if Output.Name != "group" {
|
||||
if Output.Group.Begin != "" {
|
||||
return errors.New("task: You can't set --output-group-begin without --output=group")
|
||||
}
|
||||
if Output.Group.End != "" {
|
||||
return errors.New("task: You can't set --output-group-end without --output=group")
|
||||
}
|
||||
if Output.Group.ErrorOnly {
|
||||
return errors.New("task: You can't set --output-group-error-only without --output=group")
|
||||
}
|
||||
}
|
||||
|
||||
if List && ListAll {
|
||||
return errors.New("task: cannot use --list and --list-all at the same time")
|
||||
}
|
||||
|
||||
if ListJson && !List && !ListAll {
|
||||
return errors.New("task: --json only applies to --list or --list-all")
|
||||
}
|
||||
|
||||
if NoStatus && !ListJson {
|
||||
return errors.New("task: --no-status only applies to --json with --list or --list-all")
|
||||
}
|
||||
|
||||
if Nested && !ListJson {
|
||||
return errors.New("task: --nested only applies to --json with --list or --list-all")
|
||||
}
|
||||
|
||||
// Validate certificate flags
|
||||
if (Cert != "" && CertKey == "") || (Cert == "" && CertKey != "") {
|
||||
return errors.New("task: --cert and --cert-key must be provided together")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithFlags is a special internal functional option that is used to pass flags
|
||||
// from the CLI into any constructor that accepts functional options.
|
||||
func WithFlags() task.ExecutorOption {
|
||||
return &flagsOption{}
|
||||
}
|
||||
|
||||
type flagsOption struct{}
|
||||
|
||||
func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
||||
// Set the sorter
|
||||
var sorter sort.Sorter
|
||||
switch TaskSort {
|
||||
case "none":
|
||||
sorter = sort.NoSort
|
||||
case "alphanumeric":
|
||||
sorter = sort.AlphaNumeric
|
||||
}
|
||||
|
||||
// Change the directory to the user's home directory if the global flag is set
|
||||
dir := Dir
|
||||
if Global {
|
||||
home, err := os.UserHomeDir()
|
||||
if err == nil {
|
||||
dir = home
|
||||
}
|
||||
}
|
||||
|
||||
e.Options(
|
||||
task.WithDir(dir),
|
||||
task.WithEntrypoint(Entrypoint),
|
||||
task.WithForce(Force),
|
||||
task.WithForceAll(ForceAll),
|
||||
task.WithInsecure(Insecure),
|
||||
task.WithDownload(Download),
|
||||
task.WithOffline(Offline),
|
||||
task.WithTrustedHosts(TrustedHosts),
|
||||
task.WithTimeout(Timeout),
|
||||
task.WithCacheExpiryDuration(CacheExpiryDuration),
|
||||
task.WithRemoteCacheDir(RemoteCacheDir),
|
||||
task.WithCACert(CACert),
|
||||
task.WithCert(Cert),
|
||||
task.WithCertKey(CertKey),
|
||||
task.WithWatch(Watch),
|
||||
task.WithVerbose(Verbose),
|
||||
task.WithSilent(Silent),
|
||||
task.WithDisableFuzzy(DisableFuzzy),
|
||||
task.WithAssumeYes(AssumeYes),
|
||||
task.WithInteractive(Interactive),
|
||||
task.WithDry(Dry || Status),
|
||||
task.WithSummary(Summary),
|
||||
task.WithParallel(Parallel),
|
||||
task.WithColor(Color),
|
||||
task.WithConcurrency(Concurrency),
|
||||
task.WithInterval(Interval),
|
||||
task.WithOutputStyle(Output),
|
||||
task.WithTaskSorter(sorter),
|
||||
task.WithVersionCheck(true),
|
||||
task.WithFailfast(Failfast),
|
||||
)
|
||||
}
|
||||
|
||||
// getConfig extracts a config value with priority: env var > taskrc config > fallback
|
||||
func getConfig[T any](config *taskrcast.TaskRC, envKey string, fieldFunc func() *T, fallback T) T {
|
||||
if envKey != "" {
|
||||
if val, ok := getEnvAs[T](envKey); ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
if config != nil {
|
||||
if field := fieldFunc(); field != nil {
|
||||
return *field
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// getEnvAs parses a TASK_ prefixed env var as type T
|
||||
func getEnvAs[T any](envKey string) (T, bool) {
|
||||
var zero T
|
||||
switch any(zero).(type) {
|
||||
case bool:
|
||||
if val, ok := env.GetTaskEnvBool(envKey); ok {
|
||||
return any(val).(T), true
|
||||
}
|
||||
case int:
|
||||
if val, ok := env.GetTaskEnvInt(envKey); ok {
|
||||
return any(val).(T), true
|
||||
}
|
||||
case time.Duration:
|
||||
if val, ok := env.GetTaskEnvDuration(envKey); ok {
|
||||
return any(val).(T), true
|
||||
}
|
||||
case string:
|
||||
if val, ok := env.GetTaskEnvString(envKey); ok {
|
||||
return any(val).(T), true
|
||||
}
|
||||
case []string:
|
||||
if val, ok := env.GetTaskEnvStringSlice(envKey); ok {
|
||||
return any(val).(T), true
|
||||
}
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
208
internal/fsext/fs.go
Normal file
208
internal/fsext/fs.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package fsext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/sysinfo"
|
||||
)
|
||||
|
||||
// DefaultDir will return the default directory given an entrypoint or
|
||||
// directory. If the directory is set, it will ensure it is an absolute path and
|
||||
// return it. If the entrypoint is set, but the directory is not, it will leave
|
||||
// the directory blank. If both are empty, it will default the directory to the
|
||||
// current working directory.
|
||||
func DefaultDir(entrypoint, dir string) string {
|
||||
// If the directory is set, ensure it is an absolute path
|
||||
if dir != "" {
|
||||
var err error
|
||||
dir, err = filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// If the entrypoint and dir are empty, we default the directory to the current working directory
|
||||
if entrypoint == "" {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return wd
|
||||
}
|
||||
|
||||
// If the entrypoint is set, but the directory is not, we leave the directory blank
|
||||
return ""
|
||||
}
|
||||
|
||||
// ResolveDir returns an absolute path to the directory that the task should be
|
||||
// run in. If the entrypoint and dir are BOTH set, then the Taskfile will not
|
||||
// sit inside the directory specified by dir and we should ensure that the dir
|
||||
// is absolute. Otherwise, the dir will always be the parent directory of the
|
||||
// resolved entrypoint, so we should return that parent directory.
|
||||
func ResolveDir(entrypoint, resolvedEntrypoint, dir string) (string, error) {
|
||||
if entrypoint != "" && dir != "" {
|
||||
return filepath.Abs(dir)
|
||||
}
|
||||
return filepath.Dir(resolvedEntrypoint), nil
|
||||
}
|
||||
|
||||
// Search looks for files with the given possible filenames using the given
|
||||
// entrypoint and directory. If the entrypoint is set, it checks if the
|
||||
// entrypoint matches a file or if it matches a directory containing one of the
|
||||
// possible filenames. Otherwise, it walks up the file tree starting at the
|
||||
// given directory and performs a search in each directory for the possible
|
||||
// filenames until it finds a match or reaches the root directory. If the
|
||||
// entrypoint and directory are both empty, it defaults the directory to the
|
||||
// current working directory and performs a recursive search starting there. If
|
||||
// a match is found, the absolute path to the file is returned with its
|
||||
// directory. If no match is found, an error is returned.
|
||||
func Search(entrypoint, dir string, possibleFilenames []string) (string, error) {
|
||||
var err error
|
||||
if entrypoint != "" {
|
||||
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return entrypoint, nil
|
||||
}
|
||||
if dir == "" {
|
||||
dir, err = os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
entrypoint, err = SearchPathRecursively(dir, possibleFilenames)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return entrypoint, nil
|
||||
}
|
||||
|
||||
// SearchAll looks for files with the given possible filenames using the given
|
||||
// entrypoint and directory. If the entrypoint is set, it checks if the
|
||||
// entrypoint matches a file or if it matches a directory containing one of the
|
||||
// possible filenames and add it to a list of matches. It then walks up the file
|
||||
// tree starting at the given directory and performs a search in each directory
|
||||
// for the possible filenames until it finds a match or reaches the root
|
||||
// directory. If the entrypoint and directory are both empty, it defaults the
|
||||
// directory to the current working directory and performs a recursive search
|
||||
// starting there. If matches are found, the absolute path to each file is added
|
||||
// to the list and returned.
|
||||
func SearchAll(entrypoint, dir string, possibleFilenames []string) ([]string, error) {
|
||||
var err error
|
||||
var entrypoints []string
|
||||
if entrypoint != "" {
|
||||
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entrypoints = append(entrypoints, entrypoint)
|
||||
}
|
||||
if dir == "" {
|
||||
dir, err = os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
paths, err := SearchNPathRecursively(dir, possibleFilenames, -1)
|
||||
// The call to SearchNPathRecursively is ambiguous and may return
|
||||
// os.ErrPermission if its search ends, however it may have still
|
||||
// returned valid paths. Caller may choose to ignore that error.
|
||||
return append(entrypoints, paths...), err
|
||||
}
|
||||
|
||||
// SearchPath will check if a file at the given path exists or not. If it does,
|
||||
// it will return the path to it. If it does not, it will search for any files
|
||||
// at the given path with any of the given possible names. If any of these match
|
||||
// a file, the first matching path will be returned. If no files are found, an
|
||||
// error will be returned.
|
||||
func SearchPath(path string, possibleFilenames []string) (string, error) {
|
||||
// Get file info about the path
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// If the path exists and is a regular file, device, symlink, or named pipe,
|
||||
// return the absolute path to it
|
||||
if fi.Mode().IsRegular() ||
|
||||
fi.Mode()&os.ModeDevice != 0 ||
|
||||
fi.Mode()&os.ModeSymlink != 0 ||
|
||||
fi.Mode()&os.ModeNamedPipe != 0 {
|
||||
return filepath.Abs(path)
|
||||
}
|
||||
|
||||
// If the path is a directory, check if any of the possible names exist
|
||||
// in that directory
|
||||
for _, filename := range possibleFilenames {
|
||||
alt := filepathext.SmartJoin(path, filename)
|
||||
if _, err := os.Stat(alt); err == nil {
|
||||
return filepath.Abs(alt)
|
||||
}
|
||||
}
|
||||
|
||||
return "", os.ErrNotExist
|
||||
}
|
||||
|
||||
// SearchPathRecursively walks up the directory tree starting at the given
|
||||
// path, calling the Search function in each directory until it finds a matching
|
||||
// file or reaches the root directory. On supported operating systems, it will
|
||||
// also check if the user ID of the directory changes and abort if it does.
|
||||
func SearchPathRecursively(path string, possibleFilenames []string) (string, error) {
|
||||
paths, err := SearchNPathRecursively(path, possibleFilenames, 1)
|
||||
if len(paths) > 0 {
|
||||
// Regardless of the error, return the first possible filename.
|
||||
return paths[0], nil
|
||||
} else {
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return "", os.ErrNotExist
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SearchNPathRecursively walks up the directory tree starting at the given
|
||||
// path, calling the Search function in each directory and adding each matching
|
||||
// file that it finds to a list until it reaches the root directory or the
|
||||
// length of the list exceeds n. On supported operating systems, it will also
|
||||
// check if the user ID of the directory changes and abort if it does.
|
||||
func SearchNPathRecursively(path string, possibleFilenames []string, n int) ([]string, error) {
|
||||
var paths []string
|
||||
|
||||
owner, err := sysinfo.Owner(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for n == -1 || len(paths) < n {
|
||||
fpath, err := SearchPath(path, possibleFilenames)
|
||||
if err == nil {
|
||||
paths = append(paths, fpath)
|
||||
}
|
||||
|
||||
// Get the parent path/user id
|
||||
parentPath := filepath.Dir(path)
|
||||
parentOwner, err := sysinfo.Owner(parentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the user id of the directory changes indicate a permission error, otherwise
|
||||
// the calling code will infer an error condition based on the accumulated
|
||||
// contents of paths.
|
||||
if path == parentPath {
|
||||
return paths, nil
|
||||
} else if parentOwner != owner {
|
||||
return paths, os.ErrPermission
|
||||
}
|
||||
|
||||
owner = parentOwner
|
||||
path = parentPath
|
||||
}
|
||||
|
||||
return paths, nil
|
||||
}
|
||||
224
internal/fsext/fs_test.go
Normal file
224
internal/fsext/fs_test.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package fsext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDefaultDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
entrypoint string
|
||||
dir string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "default to current working directory",
|
||||
entrypoint: "",
|
||||
dir: "",
|
||||
expected: wd,
|
||||
},
|
||||
{
|
||||
name: "resolves relative dir path",
|
||||
entrypoint: "",
|
||||
dir: "./dir",
|
||||
expected: filepath.Join(wd, "dir"),
|
||||
},
|
||||
{
|
||||
name: "return entrypoint if set",
|
||||
entrypoint: filepath.Join(wd, "entrypoint"),
|
||||
dir: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "if entrypoint and dir are set",
|
||||
entrypoint: filepath.Join(wd, "entrypoint"),
|
||||
dir: filepath.Join(wd, "dir"),
|
||||
expected: filepath.Join(wd, "dir"),
|
||||
},
|
||||
{
|
||||
name: "if entrypoint and dir are set and dir is relative",
|
||||
entrypoint: filepath.Join(wd, "entrypoint"),
|
||||
dir: "./dir",
|
||||
expected: filepath.Join(wd, "dir"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, tt.expected, DefaultDir(tt.entrypoint, tt.dir))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
entrypoint string
|
||||
dir string
|
||||
possibleFilenames []string
|
||||
expectedEntrypoint string
|
||||
}{
|
||||
{
|
||||
name: "find foo.txt using relative entrypoint",
|
||||
entrypoint: "./testdata/foo.txt",
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using absolute entrypoint",
|
||||
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using relative dir",
|
||||
dir: "./testdata",
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using absolute dir",
|
||||
dir: filepath.Join(wd, "testdata"),
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using relative dir and relative entrypoint",
|
||||
entrypoint: "./testdata/foo.txt",
|
||||
dir: "./testdata/some/other/dir",
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
},
|
||||
{
|
||||
name: "find fs.go using no entrypoint or dir",
|
||||
entrypoint: "",
|
||||
dir: "",
|
||||
possibleFilenames: []string{"fs.go"},
|
||||
expectedEntrypoint: filepath.Join(wd, "fs.go"),
|
||||
},
|
||||
{
|
||||
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
|
||||
entrypoint: "",
|
||||
dir: "",
|
||||
possibleFilenames: []string{"Taskfile.yml"},
|
||||
expectedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt first if listed first in possible filenames",
|
||||
entrypoint: "./testdata",
|
||||
possibleFilenames: []string{"foo.txt", "bar.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
},
|
||||
{
|
||||
name: "find bar.txt first if listed first in possible filenames",
|
||||
entrypoint: "./testdata",
|
||||
possibleFilenames: []string{"bar.txt", "foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
entrypoint, err := Search(tt.entrypoint, tt.dir, tt.possibleFilenames)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedEntrypoint, entrypoint)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
entrypoint string
|
||||
resolvedEntrypoint string
|
||||
dir string
|
||||
expectedDir string
|
||||
}{
|
||||
{
|
||||
name: "find foo.txt using relative entrypoint",
|
||||
entrypoint: "./testdata/foo.txt",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using absolute entrypoint",
|
||||
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using relative dir",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
dir: "./testdata",
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using absolute dir",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
dir: filepath.Join(wd, "testdata"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using relative dir and relative entrypoint",
|
||||
entrypoint: "./testdata/foo.txt",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
dir: "./testdata/some/other/dir",
|
||||
expectedDir: filepath.Join(wd, "testdata", "some", "other", "dir"),
|
||||
},
|
||||
{
|
||||
name: "find fs.go using no entrypoint or dir",
|
||||
entrypoint: "",
|
||||
resolvedEntrypoint: filepath.Join(wd, "fs.go"),
|
||||
dir: "",
|
||||
expectedDir: wd,
|
||||
},
|
||||
{
|
||||
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
|
||||
entrypoint: "",
|
||||
resolvedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
|
||||
dir: "",
|
||||
expectedDir: filepath.Join(wd, "..", ".."),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt first if listed first in possible filenames",
|
||||
entrypoint: "./testdata",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find bar.txt first if listed first in possible filenames",
|
||||
entrypoint: "./testdata",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dir, err := ResolveDir(tt.entrypoint, tt.resolvedEntrypoint, tt.dir)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedDir, dir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
0
internal/fsext/testdata/bar.txt
vendored
Normal file
0
internal/fsext/testdata/bar.txt
vendored
Normal file
0
internal/fsext/testdata/foo.txt
vendored
Normal file
0
internal/fsext/testdata/foo.txt
vendored
Normal file
51
internal/fsnotifyext/fsnotify_dedup.go
Normal file
51
internal/fsnotifyext/fsnotify_dedup.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package fsnotifyext
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
type Deduper struct {
|
||||
w *fsnotify.Watcher
|
||||
waitTime time.Duration
|
||||
}
|
||||
|
||||
func NewDeduper(w *fsnotify.Watcher, waitTime time.Duration) *Deduper {
|
||||
return &Deduper{
|
||||
w: w,
|
||||
waitTime: waitTime,
|
||||
}
|
||||
}
|
||||
|
||||
// GetChan returns a chan of deduplicated [fsnotify.Event].
|
||||
//
|
||||
// [fsnotify.Chmod] operations will be skipped.
|
||||
func (d *Deduper) GetChan() <-chan fsnotify.Event {
|
||||
channel := make(chan fsnotify.Event)
|
||||
|
||||
go func() {
|
||||
timers := make(map[string]*time.Timer)
|
||||
for {
|
||||
event, ok := <-d.w.Events
|
||||
switch {
|
||||
case !ok:
|
||||
return
|
||||
case event.Has(fsnotify.Chmod):
|
||||
continue
|
||||
}
|
||||
|
||||
timer, ok := timers[event.String()]
|
||||
if !ok {
|
||||
timer = time.AfterFunc(math.MaxInt64, func() { channel <- event })
|
||||
timer.Stop()
|
||||
timers[event.String()] = timer
|
||||
}
|
||||
|
||||
timer.Reset(d.waitTime)
|
||||
}
|
||||
}()
|
||||
|
||||
return channel
|
||||
}
|
||||
63
internal/goext/meta.go
Normal file
63
internal/goext/meta.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package goext
|
||||
|
||||
// NOTE(@andreynering): The lists in this file were copied from:
|
||||
//
|
||||
// https://github.com/golang/go/blob/master/src/go/build/syslist.go
|
||||
|
||||
func IsKnownOS(str string) bool {
|
||||
_, known := knownOS[str]
|
||||
return known
|
||||
}
|
||||
|
||||
func IsKnownArch(str string) bool {
|
||||
_, known := knownArch[str]
|
||||
return known
|
||||
}
|
||||
|
||||
var knownOS = map[string]struct{}{
|
||||
"aix": {},
|
||||
"android": {},
|
||||
"darwin": {},
|
||||
"dragonfly": {},
|
||||
"freebsd": {},
|
||||
"hurd": {},
|
||||
"illumos": {},
|
||||
"ios": {},
|
||||
"js": {},
|
||||
"linux": {},
|
||||
"nacl": {},
|
||||
"netbsd": {},
|
||||
"openbsd": {},
|
||||
"plan9": {},
|
||||
"solaris": {},
|
||||
"windows": {},
|
||||
"zos": {},
|
||||
"__test__": {},
|
||||
}
|
||||
|
||||
var knownArch = map[string]struct{}{
|
||||
"386": {},
|
||||
"amd64": {},
|
||||
"amd64p32": {},
|
||||
"arm": {},
|
||||
"armbe": {},
|
||||
"arm64": {},
|
||||
"arm64be": {},
|
||||
"loong64": {},
|
||||
"mips": {},
|
||||
"mipsle": {},
|
||||
"mips64": {},
|
||||
"mips64le": {},
|
||||
"mips64p32": {},
|
||||
"mips64p32le": {},
|
||||
"ppc": {},
|
||||
"ppc64": {},
|
||||
"ppc64le": {},
|
||||
"riscv": {},
|
||||
"riscv64": {},
|
||||
"s390": {},
|
||||
"s390x": {},
|
||||
"sparc": {},
|
||||
"sparc64": {},
|
||||
"wasm": {},
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user