Compare commits
1233 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f59be465ea | ||
|
27305317ce | ||
|
8378320e50 | ||
|
86a69952db | ||
|
e212ebfdb9 | ||
|
f235125d48 | ||
|
8efa815eb9 | ||
|
e54d42576b | ||
|
1e18539862 | ||
|
673fdf4721 | ||
|
57d6bfba51 | ||
|
8fa23aecda | ||
|
f9c10533e8 | ||
|
c891c3723e | ||
|
a0625ef2e7 | ||
|
b2774abb54 | ||
|
fa089bbc40 | ||
|
8437d1763f | ||
|
0532b0f9df | ||
|
5f8eb853f0 | ||
|
9125a85382 | ||
|
5b3dc44c72 | ||
|
ee5147f111 | ||
|
f67137fd5e | ||
|
b67db4a896 | ||
|
067018828c | ||
|
e990530ada | ||
|
45f863a72a | ||
|
519febbeb2 | ||
|
854b1a4caf | ||
|
08eb95e73a | ||
|
66f8fbf08c | ||
|
86a30bbb29 | ||
|
966c453c27 | ||
|
c7bcaf0193 | ||
|
0ac91e36a0 | ||
|
6f1299ce9e | ||
|
83ca29d649 | ||
|
d0206044dc | ||
|
d4623e4175 | ||
|
68ef03377d | ||
|
d3f9d3d033 | ||
|
a6ccd04471 | ||
|
0fd8174e77 | ||
|
ebc63f479a | ||
|
570c39c20a | ||
|
1d57257cf5 | ||
|
82d7baee0b | ||
|
1d91e1f1f7 | ||
|
15e8894614 | ||
|
0ae96918e9 | ||
|
e970880059 | ||
|
69ae86aba8 | ||
|
8142e1be49 | ||
|
cbc7612451 | ||
|
09e6a7b7cc | ||
|
082e35668a | ||
|
c9e78c837b | ||
|
2d171db672 | ||
|
a8b11abec8 | ||
|
b08333dccd | ||
|
98bad48971 | ||
|
53375bb536 | ||
|
88798093e1 | ||
|
45a28751af | ||
|
1670c44464 | ||
|
a286ac85dc | ||
|
1606adc0ab | ||
|
b4ce557d92 | ||
|
2ba7fff8d4 | ||
|
98efc93610 | ||
|
2680507eae | ||
|
df72392ad2 | ||
|
a6acb15a00 | ||
|
ba7242a815 | ||
|
99740c1324 | ||
|
c04b20d1fa | ||
|
e127067878 | ||
|
6f03b72368 | ||
|
7bd3ee9340 | ||
|
c94db606e5 | ||
|
4d2474226b | ||
|
1ffc4f538b | ||
|
6702ac957b | ||
|
ba42b2e77d | ||
|
fba18ca40a | ||
|
3a2a7a1476 | ||
|
3c6d6b90bc | ||
|
47dc9ee304 | ||
|
0045bf206c | ||
|
669a1a6953 | ||
|
e28452cc7b | ||
|
5ceac03db1 | ||
|
96215e5950 | ||
|
7a030b9224 | ||
|
e743478a4d | ||
|
bc29c80d44 | ||
|
82b8a474d7 | ||
|
99851b297d | ||
|
e3a500e12c | ||
|
378666e3cc | ||
|
b65ad1ebd7 | ||
|
933a821b5f | ||
|
159337ddbd | ||
|
2e25a4333a | ||
|
64fafb0795 | ||
|
5927e0bb3b | ||
|
e21596db83 | ||
|
8a608c3c3e | ||
|
76f9db8516 | ||
|
90f4809b7c | ||
|
3ff1af9fd6 | ||
|
711dd520f7 | ||
|
99503c836a | ||
|
2c8bc51862 | ||
|
5e7aa15232 | ||
|
d0761869b6 | ||
|
bb99b89228 | ||
|
7b88beb0b5 | ||
|
82c630bd0e | ||
|
bb4f11e1b8 | ||
|
3182d20b7c | ||
|
a6f15e2474 | ||
|
0c3f03680c | ||
|
a66e8dfc9f | ||
|
556232aac4 | ||
|
7fcfaadd91 | ||
|
18d388e1e2 | ||
|
7de6622d74 | ||
|
9af1f90990 | ||
|
15ab43963d | ||
|
6b7465a4b0 | ||
|
d64bdf02de | ||
|
4a2c94993a | ||
|
86c318d86b | ||
|
bffc1ad41d | ||
|
2a685c116f | ||
|
0e271d2924 | ||
|
7127c4d590 | ||
|
a82929996b | ||
|
3f01f52c7b | ||
|
9c94494622 | ||
|
c8a508f35c | ||
|
4ec73ef1c3 | ||
|
3465b79e5b | ||
|
726a0a33ae | ||
|
ba05810d80 | ||
|
10bb53e7de | ||
|
dd8a083546 | ||
|
84131a71c9 | ||
|
4909a11896 | ||
|
4069357b81 | ||
|
e6e87bcc40 | ||
|
7ba22045e8 | ||
|
d4040b61c4 | ||
|
7534f8fc37 | ||
|
7ced009e92 | ||
|
cc13c71e40 | ||
|
d2f09209a7 | ||
|
a241b04d99 | ||
|
4f35d907cb | ||
|
9e50d144a3 | ||
|
8514bb48c4 | ||
|
3ae633a2a1 | ||
|
2799cb9118 | ||
|
d20346b6ac | ||
|
2f00666a68 | ||
|
80f4570093 | ||
|
3f0a2ba48f | ||
|
fc90094e8a | ||
|
20d580ade8 | ||
|
290a024a9e | ||
|
64a283c3a6 | ||
|
3e93f0ecbc | ||
|
b12bcc1c49 | ||
|
05d7313dcc | ||
|
c0b6e549f0 | ||
|
31f9bf219e | ||
|
4906ca7059 | ||
|
b963a7a0e5 | ||
|
5d6dadda76 | ||
|
77f69fd223 | ||
|
7f5052bc87 | ||
|
3009c1f4f6 | ||
|
5425872bba | ||
|
0759e17295 | ||
|
56a59d25df | ||
|
887f92babe | ||
|
197f942b3f | ||
|
a5be7a35e9 | ||
|
e347675265 | ||
|
02a084d571 | ||
|
7fbcc23d94 | ||
|
bda87167a3 | ||
|
34f53c287e | ||
|
9ab07f37d7 | ||
|
36399b39fb | ||
|
1e015287a1 | ||
|
a590aa8485 | ||
|
c0582fde66 | ||
|
d8d75b4afa | ||
|
9d4942723c | ||
|
e5b1ece5c0 | ||
|
1b25bfbf5a | ||
|
7c64d9f42a | ||
|
61f9f4ef58 | ||
|
b8eac56213 | ||
|
ab5fb5749b | ||
|
e0fb9a1b25 | ||
|
97e717f646 | ||
|
caa82ad1e6 | ||
|
7ec251ea6d | ||
|
675b0c3cca | ||
|
05e54d4b7f | ||
|
b4527c90e5 | ||
|
dbc626734d | ||
|
3f2ce3cb80 | ||
|
0820d6d6fb | ||
|
a8c74e39c2 | ||
|
d58c29907d | ||
|
2322733427 | ||
|
d76e8fe85a | ||
|
0c2e4fe34a | ||
|
30b6c87a49 | ||
|
540221467a | ||
|
d44d331a78 | ||
|
595554f18a | ||
|
84d3c3f7eb | ||
|
890f55c9dc | ||
|
575a14e1f3 | ||
|
91074bebd6 | ||
|
3459a16b48 | ||
|
88c9b6849f | ||
|
d87bb25baf | ||
|
34cb796505 | ||
|
1eb34e0662 | ||
|
01d631033f | ||
|
84b2c07340 | ||
|
a86eeb636d | ||
|
417a5a8214 | ||
|
bcf40dde8c | ||
|
cad87484c7 | ||
|
b8ad641ed6 | ||
|
ae8197be8b | ||
|
eafb0274a7 | ||
|
71a23a4e02 | ||
|
26fd90dfa3 | ||
|
e393ebede2 | ||
|
2981bb3f19 | ||
|
6e9f05abb1 | ||
|
9df1115380 | ||
|
f22e360cbb | ||
|
67769af6f4 | ||
|
b1a9a1d6d9 | ||
|
cf0606ecb7 | ||
|
7287edcd6f | ||
|
e0d26203dd | ||
|
7e3a85e9da | ||
|
5a0fed9c93 | ||
|
1f1e743912 | ||
|
b4301ed0d5 | ||
|
b5391560fc | ||
|
718989cbcf | ||
|
d0aee76962 | ||
|
fb08af96bd | ||
|
510a0c5e70 | ||
|
89bdc8ec75 | ||
|
ae25ade318 | ||
|
dd5e46a8a7 | ||
|
f7218aaa9e | ||
|
ee79bcfc44 | ||
|
2c2c174874 | ||
|
f57c49ce3a | ||
|
06121acfac | ||
|
2078ce7446 | ||
|
49f41abfdb | ||
|
70fcfe6d6c | ||
|
b060b4b9bf | ||
|
ee2135bfb3 | ||
|
7daa322441 | ||
|
74252cb66b | ||
|
5fe2be031f | ||
|
2ba3aaba47 | ||
|
ad8903991c | ||
|
3e5624c570 | ||
|
88d3bba300 | ||
|
f5ee6f3537 | ||
|
afc77e7adc | ||
|
024f42fce6 | ||
|
8a5f12b97c | ||
|
954b21cf39 | ||
|
74d095774d | ||
|
17a2722e6d | ||
|
c843bddbfe | ||
|
3f22a49755 | ||
|
7af2ffcebf | ||
|
de90c959e0 | ||
|
9987dc1eb4 | ||
|
3efd575dd2 | ||
|
f4c7b17a87 | ||
|
16d80718cb | ||
|
ad228d53b7 | ||
|
15ee1e531b | ||
|
1c8fb3392a | ||
|
8647866a32 | ||
|
23351c4f1c | ||
|
1367c304cf | ||
|
26d6bcb074 | ||
|
b0d651ece1 | ||
|
b6d50ba6a4 | ||
|
b3ab6a9166 | ||
|
f39a5ac9c2 | ||
|
38a9a9240d | ||
|
241b22a465 | ||
|
741abc0366 | ||
|
7854775de5 | ||
|
e62eaa6b4b | ||
|
b4cce23ef4 | ||
|
2bcaf90fc8 | ||
|
96ffbe2f84 | ||
|
6f5acee1c3 | ||
|
54e491d8bf | ||
|
ab6374e278 | ||
|
2fda4c9f67 | ||
|
5138a45b0f | ||
|
b224d4fa8a | ||
|
a552e44483 | ||
|
0cf3bba118 | ||
|
2c48ea3508 | ||
|
b9b6212b75 | ||
|
b978aaec21 | ||
|
af704681d9 | ||
|
1443ddfe6c | ||
|
54457a3e1b | ||
|
bf180e6a2c | ||
|
864a5820c9 | ||
|
4d3ca49c3f | ||
|
c49c3cf7f0 | ||
|
5d5ab57469 | ||
|
31978d8de0 | ||
|
e8eb68bf24 | ||
|
9ea08f4fed | ||
|
fe078a5c5b | ||
|
61933954f3 | ||
|
4c243638cb | ||
|
02ba04b5d8 | ||
|
4f158a4829 | ||
|
177a22df59 | ||
|
6b0ca2966e | ||
|
aadfaf7150 | ||
|
b307b9a66b | ||
|
6c1ab6002d | ||
|
9638eefc91 | ||
|
9e9c4ad587 | ||
|
ce231431b9 | ||
|
06e1e14e02 | ||
|
416e7884f5 | ||
|
d579222007 | ||
|
30243c84cd | ||
|
3557a77645 | ||
|
97be28638b | ||
|
aba0826c38 | ||
|
f032228d0e | ||
|
6cf174c5ed | ||
|
c2109d245f | ||
|
6a9745171e | ||
|
f9a68e8b23 | ||
|
6e391df5ee | ||
|
f5edca94d3 | ||
|
60046abec3 | ||
|
cafc2060b8 | ||
|
b1f45752cf | ||
|
ed17551170 | ||
|
ef5adab638 | ||
|
fb653ff99d | ||
|
78fc47a9c4 | ||
|
2a124cea61 | ||
|
4c7cc563dc | ||
|
6114af4f93 | ||
|
8c31629655 | ||
|
03c8a8edb2 | ||
|
3eeaee154f | ||
|
8cf8fa7c80 | ||
|
6b4f6fc71e | ||
|
30c2680b6f | ||
|
fb7b1800cc | ||
|
ff573bf377 | ||
|
0a33bb861e | ||
|
728756289b | ||
|
56ccd3a0ac | ||
|
66f3f0ba07 | ||
|
af5e0d589e | ||
|
533dc99e7d | ||
|
fc5ca965ba | ||
|
9c4a46bcdb | ||
|
52658886e7 | ||
|
8174ab7616 | ||
|
2b6acedae1 | ||
|
d00fe9c5f4 | ||
|
88aa270728 | ||
|
4ae409c7f4 | ||
|
9a29c9abdd | ||
|
66d93ea037 | ||
|
6c0066dbfb | ||
|
4fde644733 | ||
|
e7841c60df | ||
|
94f647b24a | ||
|
630249d22a | ||
|
db99b4cb54 | ||
|
c77db23586 | ||
|
daf66bcec4 | ||
|
8caf36349f | ||
|
6934de58e5 | ||
|
54a5007c01 | ||
|
e25a455698 | ||
|
ab429dfeb6 | ||
|
c5289dc0e8 | ||
|
d191877002 | ||
|
4d979160c2 | ||
|
d00e8f6e19 | ||
|
91b77e5237 | ||
|
5b8c246d53 | ||
|
b374b9b91c | ||
|
403717117e | ||
|
027295d995 | ||
|
c9b7eccbc1 | ||
|
2b6d9348cd | ||
|
692f8c8454 | ||
|
6783355c4d | ||
|
fb9cca1e99 | ||
|
eb770ede1a | ||
|
2643e853af | ||
|
b79456e91b | ||
|
ac66c086f8 | ||
|
ebccf401dd | ||
|
66494845b7 | ||
|
e6c36ad602 | ||
|
26379182db | ||
|
bba03d14d4 | ||
|
23b728a762 | ||
|
819c5207d2 | ||
|
311358544e | ||
|
4480ecc96d | ||
|
6c5f70a205 | ||
|
99adfb4a9e | ||
|
7909cf4067 | ||
|
780ab20aeb | ||
|
73119bb7c5 | ||
|
f20f0f064e | ||
|
1b44ae098c | ||
|
453c230716 | ||
|
439d885ee1 | ||
|
43dee3ef76 | ||
|
c71ba6ff8d | ||
|
fb7a36eb73 | ||
|
e7f294a065 | ||
|
d5037f180e | ||
|
e90158809a | ||
|
0cb802ed9a | ||
|
d0b47204f4 | ||
|
351cb391e5 | ||
|
051be927cd | ||
|
8bad2c2113 | ||
|
2bcf6fb3eb | ||
|
d1ba0ed2b2 | ||
|
6e421e60c5 | ||
|
8385050804 | ||
|
bfe4f08232 | ||
|
132f914b0d | ||
|
97d82b03ab | ||
|
f06fa3f9b7 | ||
|
6337788a22 | ||
|
024db4358b | ||
|
173f35487e | ||
|
74cbe82dd1 | ||
|
a8c30d30a9 | ||
|
1b7a52d5af | ||
|
e5109789bf | ||
|
4d2b35e09d | ||
|
5c5177ec57 | ||
|
d93b00cd15 | ||
|
39ade59174 | ||
|
0309c815b9 | ||
|
ce613098db | ||
|
ab34044196 | ||
|
17f724748f | ||
|
f3a917b5e7 | ||
|
376011ea08 | ||
|
2ce7624c14 | ||
|
c62dddd5b9 | ||
|
490ba9f140 | ||
|
f76890cc56 | ||
|
e1c8f1fed9 | ||
|
6e19a4ab8b | ||
|
b156523a7f | ||
|
d20b745ae5 | ||
|
c51e9e6b2c | ||
|
1a31fa9067 | ||
|
558b8499af | ||
|
986c162988 | ||
|
770d5cd11c | ||
|
446d2ab3af | ||
|
2f9bf7f063 | ||
|
72ff9c0964 | ||
|
33b1a11d85 | ||
|
06dabf1e4e | ||
|
28d3691e0b | ||
|
ffa21fbfd2 | ||
|
db028665fd | ||
|
6bc83d9f27 | ||
|
790d832155 | ||
|
f477cecdeb | ||
|
e031389021 | ||
|
e00f826eb8 | ||
|
24f4e8ab99 | ||
|
1550d528bd | ||
|
40c041031e | ||
|
3e555ec9f1 | ||
|
5098f14aab | ||
|
a355a9c85e | ||
|
e7db2a8573 | ||
|
9b18bd0b48 | ||
|
f95ddd594e | ||
|
fe8168784f | ||
|
4046f143f6 | ||
|
e4e16999c8 | ||
|
10f3ba4ff4 | ||
|
3cd2be5081 | ||
|
c9359978f9 | ||
|
781c67b31a | ||
|
020bd129fb | ||
|
8086b6d78c | ||
|
48e14b36b8 | ||
|
b3c1c56579 | ||
|
bd0e932910 | ||
|
525e5f88ae | ||
|
005eeb0e0b | ||
|
d21bb015e8 | ||
|
7338838b0e | ||
|
8bb4803ff9 | ||
|
892b919cf3 | ||
|
572d81ecef | ||
|
a4ce7a4037 | ||
|
6eafb15cf9 | ||
|
e19fe5ce1c | ||
|
9d2017e598 | ||
|
f33c419ed9 | ||
|
f425fbaf9d | ||
|
bcc5ec897a | ||
|
f5f2fe3472 | ||
|
b7c3863882 | ||
|
d759f48ee8 | ||
|
3a37075e71 | ||
|
58366c0b87 | ||
|
2667ed13f1 | ||
|
34daffbc96 | ||
|
be81cd72af | ||
|
4ae00714d2 | ||
|
f24cbb6692 | ||
|
5a35c5b928 | ||
|
1880da6351 | ||
|
df93cb103c | ||
|
63b474a32c | ||
|
abdbf158d1 | ||
|
ee68d80d0a | ||
|
c8e6f3a627 | ||
|
dc941575fe | ||
|
e64103e5f2 | ||
|
f0ab03a9fb | ||
|
09965f1cc6 | ||
|
d2852bb34a | ||
|
b03c52a501 | ||
|
fd6633f536 | ||
|
320ac81f48 | ||
|
70bcd2428f | ||
|
71f5ada0a3 | ||
|
aab5141404 | ||
|
ff6b119f27 | ||
|
7ef4b7eeb8 | ||
|
4668be6e24 | ||
|
a211fc7c97 | ||
|
54af0b675d | ||
|
8c8171e774 | ||
|
0bb1790206 | ||
|
45fc84d8be | ||
|
f7500f4cad | ||
|
0cfd718d8a | ||
|
5f486d0f51 | ||
|
6e5a2f85a1 | ||
|
e66a89208d | ||
|
7f65c501c6 | ||
|
22c2382765 | ||
|
38e1a4febf | ||
|
8db554b377 | ||
|
5f5cc55331 | ||
|
2f8b39186f | ||
|
a3a724e2e6 | ||
|
73235c8699 | ||
|
7e5999e862 | ||
|
afa244dcb0 | ||
|
4ea5bb2390 | ||
|
e545d552f6 | ||
|
56fe7b3596 | ||
|
eb28ec866a | ||
|
4649454282 | ||
|
a45dc6efda | ||
|
9e56b9fbb5 | ||
|
5504994cb9 | ||
|
acff6d0432 | ||
|
eea9cb7c5b | ||
|
c0ddddfb1f | ||
|
f7dab3ca56 | ||
|
e11b4038a3 | ||
|
b635e64803 | ||
|
20a194b49a | ||
|
33a5fb8837 | ||
|
59dae640db | ||
|
a6ac75e97b | ||
|
cc5b33a8ec | ||
|
6e1a627b84 | ||
|
62d4c65e1c | ||
|
600134a3ac | ||
|
df14af7337 | ||
|
2f740b570d | ||
|
294d980b52 | ||
|
c9d9909d74 | ||
|
90eeabae7b | ||
|
a32c77c5f1 | ||
|
910846f2ce | ||
|
35d0438261 | ||
|
515af472ce | ||
|
f062f7f9fe | ||
|
f68378041f | ||
|
30ef3057ac | ||
|
48f3a934c9 | ||
|
38d1fde84f | ||
|
dd78670a4b | ||
|
b8a8190a43 | ||
|
2d0989342f | ||
|
15ff9b06a1 | ||
|
41b19f69de | ||
|
f2bd6f1fce | ||
|
ec94218a4b | ||
|
c2e5c7cf38 | ||
|
bac5734527 | ||
|
bef4033d94 | ||
|
64216cba67 | ||
|
a9d6167a9f | ||
|
6c6b40548f | ||
|
a916d88e85 | ||
|
ab2f0548a3 | ||
|
e30ba07285 | ||
|
1b336d973d | ||
|
7b1866737f | ||
|
eedc4ab648 | ||
|
5e429c7a94 | ||
|
57d23eb043 | ||
|
5ebd9be89a | ||
|
5a743779e2 | ||
|
0495062110 | ||
|
d522191f69 | ||
|
bbbdc8b7a6 | ||
|
c00ed8aa5a | ||
|
0beaa94068 | ||
|
96e76665d6 | ||
|
6423a29600 | ||
|
8bddf30dcf | ||
|
a6b2db182d | ||
|
b9f3f9d859 | ||
|
6331447dcd | ||
|
4213ee660f | ||
|
3bd9287c5d | ||
|
b23d3f7c8b | ||
|
f8d9e5e027 | ||
|
8fa7fb3b1f | ||
|
63d92a0872 | ||
|
509d83365e | ||
|
d6ab73c905 | ||
|
5a93ba05d5 | ||
|
2d2fdf0b1e | ||
|
cf96622261 | ||
|
47c8ccb0e5 | ||
|
02fdb8778b | ||
|
6bed7f0e66 | ||
|
7597d335b9 | ||
|
f32c5ba244 | ||
|
ab53ab21e2 | ||
|
acfe5dbb49 | ||
|
f9b91fa189 | ||
|
2462e68ba1 | ||
|
019b2a1681 | ||
|
e94a07b677 | ||
|
c058c29755 | ||
|
9e7c7ac163 | ||
|
fcee41f00d | ||
|
ff2c1bf8ed | ||
|
a2cf26e7ed | ||
|
71e6900375 | ||
|
3bdc98bd12 | ||
|
68d2a6e951 | ||
|
acf47ac947 | ||
|
fda24e5f5a | ||
|
05eca8e4d8 | ||
|
b915f3b1a9 | ||
|
ab58968f4d | ||
|
820d1e7570 | ||
|
a120c8cf98 | ||
|
5a35ea116f | ||
|
e72ad1f030 | ||
|
a17362437a | ||
|
8643dc43b1 | ||
|
e1c869a358 | ||
|
2f5b8d9abe | ||
|
db324f54eb | ||
|
3242efb1a2 | ||
|
30f9f1a021 | ||
|
3f58d05aa7 | ||
|
c611a51575 | ||
|
73458dcd28 | ||
|
23ebeb1cc0 | ||
|
a7ba9f1886 | ||
|
d5192e2244 | ||
|
7eb595170f | ||
|
bd576ca808 | ||
|
2f8146b11f | ||
|
7f321c89cb | ||
|
fa65f606b8 | ||
|
7a3285adaf | ||
|
8bf78fef10 | ||
|
78f97ce4df | ||
|
66ccbf70f8 | ||
|
cb48600b40 | ||
|
33ce235713 | ||
|
f1a68ece01 | ||
|
dd563360af | ||
|
7f6dac4271 | ||
|
178fd8e828 | ||
|
1641e02a7d | ||
|
cfd04e9bb4 | ||
|
38effaf740 | ||
|
a68eb4a73e | ||
|
c02990ef98 | ||
|
5aa7d5ffe9 | ||
|
4942b0fca5 | ||
|
aed1bdff5a | ||
|
929c840006 | ||
|
57aef1d3c2 | ||
|
c1734a094c | ||
|
99c46685ac | ||
|
066b08040a | ||
|
35de2334fb | ||
|
5564c966a5 | ||
|
0891b5e7b7 | ||
|
f3341f201f | ||
|
bf0dafabe2 | ||
|
60f6587169 | ||
|
a9bf32919e | ||
|
7633f9f88b | ||
|
9b56233938 | ||
|
65074264b8 | ||
|
4f6fceb87f | ||
|
659fdd1d37 | ||
|
8bce2ce040 | ||
|
55cc83a5d4 | ||
|
6a51e93ded | ||
|
fcf570e96e | ||
|
cbc184e953 | ||
|
98f9063352 | ||
|
c278f1af00 | ||
|
fbb17a0ba5 | ||
|
8637a9823e | ||
|
d717fe7e8c | ||
|
5e8dfe7267 | ||
|
aaa4fbcdbd | ||
|
150f0cf486 | ||
|
711b220a05 | ||
|
b615c485f7 | ||
|
2f3b6b29ae | ||
|
7aecd83c4a | ||
|
3b460ab91f | ||
|
661c0eb970 | ||
|
f4a1f1fdc8 | ||
|
b5432c3728 | ||
|
6d0625c409 | ||
|
2f1ea08b8a | ||
|
a092da1943 | ||
|
43ef3cc562 | ||
|
047774475c | ||
|
91b8504df5 | ||
|
a4fb2dfcf8 | ||
|
ba16ec02e5 | ||
|
55b7af2623 | ||
|
b428eff10e | ||
|
35aee15b6d | ||
|
6f53c1bfde | ||
|
db0230ed75 | ||
|
7fa3c1e12a | ||
|
3b5993652f | ||
|
be4ad8947f | ||
|
2ead15e78e | ||
|
aa0740ff94 | ||
|
6d3f837820 | ||
|
359b82c29c | ||
|
b54171bc2c | ||
|
936b2131e0 | ||
|
aaef6a9e9c | ||
|
9327006e61 | ||
|
5225c841ae | ||
|
8464e319fd | ||
|
98b8bd90ea | ||
|
ae94993b09 | ||
|
72f10aaed1 | ||
|
4a74bae8c7 | ||
|
5164aec37b | ||
|
1581e9b1cd | ||
|
536e3ffb11 | ||
|
d6103191ba | ||
|
49e37a19a5 | ||
|
d02431e260 | ||
|
f24f6ead92 | ||
|
b7bdb7ae50 | ||
|
2dcf8ac96f | ||
|
5421c94853 | ||
|
10f6bc092a | ||
|
be9ea4ea8e | ||
|
f5c6fa842a | ||
|
e0943ce905 | ||
|
61e7df77a7 | ||
|
a5434360bc | ||
|
ba29c66e3b | ||
|
b3a72d55ae | ||
|
c382ad1cc8 | ||
|
363e28f323 | ||
|
d695656b8c | ||
|
31c6cbc0a2 | ||
|
b93284bc2f | ||
|
99c855b01b | ||
|
c2eaedc959 | ||
|
a631cd67ec | ||
|
66fccd3c68 | ||
|
9a4ebf4daa | ||
|
91c14211c6 | ||
|
1ea07c458b | ||
|
591add6e0c | ||
|
b99fff66df | ||
|
f54ba05b00 | ||
|
6596fb00c7 | ||
|
df0b5a80dc | ||
|
8cdbb31dbe | ||
|
0be4b1222d | ||
|
18a6bfd73a | ||
|
5e2271b237 | ||
|
798999d490 | ||
|
0e68c5e8bc | ||
|
9694af82f4 | ||
|
28a4386975 | ||
|
a238f7beba | ||
|
75ba16281b | ||
|
ad65a278d4 | ||
|
a2320b3f8d | ||
|
ae12853ad0 | ||
|
68adf6dc2f | ||
|
f88989bd4b | ||
|
77ef3847ce | ||
|
b8291837fc | ||
|
423a7f951a | ||
|
557f5fe364 | ||
|
5308970ad8 | ||
|
6b368953f4 | ||
|
2d0b63c29d | ||
|
34e941c8cb | ||
|
76cf007fff | ||
|
321963be83 | ||
|
c733bda6c3 | ||
|
e67b50b976 | ||
|
9e3c080909 | ||
|
cb661aaebd | ||
|
573571978c | ||
|
dc492a2a0a | ||
|
e47747dd0e | ||
|
4de944b41e | ||
|
5f7a1fa5cd | ||
|
b8ad328cde | ||
|
3076fd19c1 | ||
|
fac437b8c1 | ||
|
697c25015e | ||
|
d83b404fc3 | ||
|
ab7313cbc4 | ||
|
1b8d70322b | ||
|
844ffab4ed | ||
|
f5d0513d1f | ||
|
557abd4285 | ||
|
f4f1a0fbc6 | ||
|
74e10dc012 | ||
|
359812b7ed | ||
|
c2449e53c4 | ||
|
1a91249da2 | ||
|
b9162f9576 | ||
|
f726e8a7b3 | ||
|
2f284cfdc9 | ||
|
b74696adba | ||
|
c8ccba0192 | ||
|
41b0e05f62 | ||
|
847d5f1b3b | ||
|
0445f9dfc2 | ||
|
3001c780bd | ||
|
68ad5e2320 | ||
|
b5e229b19c | ||
|
453d798fcf | ||
|
451afdb660 | ||
|
d298bda92c | ||
|
fd99ba6255 | ||
|
6ade0b2b1a | ||
|
9902003da9 | ||
|
0ff2fcac11 | ||
|
e80be8e7b6 | ||
|
fe0ad0f5cb | ||
|
cb8e162f4e | ||
|
ae8c30fe57 | ||
|
ab82db9e22 | ||
|
cb9d3098de | ||
|
c927419c99 | ||
|
c009026961 | ||
|
51cf442fa5 | ||
|
954e3553ee | ||
|
cde17385b4 | ||
|
5b9e078061 | ||
|
0290d9ddfc | ||
|
eab671d102 | ||
|
f9a96ff914 | ||
|
3ec2b46d28 | ||
|
aec30b89e0 | ||
|
309c33e190 | ||
|
9c0276f97b | ||
|
0a3402ff43 | ||
|
bd82308024 | ||
|
db3b634e62 | ||
|
40bcb22977 | ||
|
4bd94092f1 | ||
|
cac1ce6895 | ||
|
5c3696123a | ||
|
4140fc86ee | ||
|
2ad771e7fd | ||
|
5a38468144 | ||
|
0a9c81772f | ||
|
c9649ac501 | ||
|
6ea567742b | ||
|
f31349eaa0 | ||
|
9d44668d5f | ||
|
a12f58c1c7 | ||
|
b5283eaaed | ||
|
4b6189af5f | ||
|
5d0ffbe453 | ||
|
502706931e | ||
|
57c411288f | ||
|
b09b7b11a1 | ||
|
1a7b3c7294 | ||
|
4678fc7dde | ||
|
9f492fad49 | ||
|
bd0a959e18 | ||
|
dd605e2610 | ||
|
9910f6b817 | ||
|
991897aff4 | ||
|
3cde019208 | ||
|
2f6efbed63 | ||
|
5cff4e299b | ||
|
bce33639da | ||
|
366c465cad | ||
|
acc6e05bdc | ||
|
4ce15577cd | ||
|
98fa4d5e65 | ||
|
7fe94076c7 | ||
|
b8b0c8fa63 | ||
|
e585e87bec | ||
|
6a4924bb16 | ||
|
ab0d516d91 | ||
|
b756ae39d0 | ||
|
c15c38ea8f | ||
|
743963318f | ||
|
ed3fc50858 | ||
|
5b886fe6be | ||
|
7074bbc405 | ||
|
ef314c1707 | ||
|
a3e7626dd9 | ||
|
5cb5d74eed | ||
|
6eee10d46d | ||
|
2d95f2b0d6 | ||
|
ec41bb9c70 | ||
|
2d1780b1cf | ||
|
d9ce99887c | ||
|
9b9cc90414 | ||
|
c1eb539a5c | ||
|
e1793f57ef | ||
|
dbd09a8743 | ||
|
0d189ca617 | ||
|
dc9bcc40ee | ||
|
4991f7ff39 | ||
|
a393b8b122 | ||
|
c73b354386 | ||
|
392ecee3ff | ||
|
bae721c49e | ||
|
4e806e21a6 | ||
|
ec0fdf83b2 | ||
|
cb94d8414f | ||
|
8890051c17 | ||
|
cf00c9476f | ||
|
b2a24c7abd | ||
|
732a1f4694 | ||
|
4c5aa7084e | ||
|
fe1fea671c | ||
|
04c754c0ac | ||
|
754c22c84e | ||
|
629331870b | ||
|
78774315cb | ||
|
36b9c07928 | ||
|
40a818630d | ||
|
568511a4cf | ||
|
109fb39e09 | ||
|
68450d2042 | ||
|
8a052bbed6 | ||
|
3afbb56640 | ||
|
c0ad84a491 | ||
|
c72f17605c | ||
|
42fbee0cdb | ||
|
e9b7ec735f | ||
|
743788135f | ||
|
8ea3e6fa26 | ||
|
f23c83e681 | ||
|
b615bda17e | ||
|
f7c7cd1d3c | ||
|
c7e7be4379 | ||
|
d63d49f246 | ||
|
dad94edb20 | ||
|
7108d5f3ab | ||
|
ef47a74920 | ||
|
a43dab8057 | ||
|
f44039b628 | ||
|
08fa5205b0 | ||
|
b91daebd92 | ||
|
9cd6c5c624 | ||
|
650e017b72 | ||
|
18f9d6dec5 | ||
|
4df6571ad9 | ||
|
0f5923a10a | ||
|
bcdae1169e | ||
|
f260d5df49 | ||
|
808b861dd1 | ||
|
17f1c487a8 | ||
|
8dc2c1a38f | ||
|
220a494692 | ||
|
db6bc10196 | ||
|
1880363aeb | ||
|
19c7b59883 | ||
|
749df89229 | ||
|
444f2172fa | ||
|
77a77c0ea7 | ||
|
dbf380a0d1 | ||
|
ade34f5217 | ||
|
e89607799a | ||
|
d05d8d6a9e | ||
|
b6aa50d3dc | ||
|
9df361935f | ||
|
98b8a122b6 | ||
|
c7232522ee | ||
|
5280f1d745 | ||
|
b52a081e7b | ||
|
f981a44861 | ||
|
d7c5ce0750 | ||
|
9ccc66ca1e | ||
|
8606af3616 | ||
|
f6e821ba6b | ||
|
e8dbcf819b | ||
|
bbe2ef4e8e | ||
|
dd15455031 | ||
|
12ac7bb338 | ||
|
46ef348f0d | ||
|
1a55cca8af | ||
|
81ee989f1f | ||
|
c9c06f8a3d | ||
|
72127979c3 | ||
|
1ad3ddef94 | ||
|
97ec5eabf7 | ||
|
e12e3a3f2d | ||
|
4ff625f23b | ||
|
e38dcd85ac | ||
|
0245baf1b6 | ||
|
10b55c043c | ||
|
457655b416 | ||
|
7e4506c860 | ||
|
794d376348 | ||
|
c60578f5b5 | ||
|
3a9a392a77 | ||
|
a13d4698be | ||
|
c046a1993e | ||
|
f709117cc4 | ||
|
30dd298fca | ||
|
0ff8bb8090 | ||
|
74bbd3c3a2 | ||
|
2e82eaf59a | ||
|
7f1df1f1bd | ||
|
3c79238a44 | ||
|
0aa2565df3 | ||
|
22b11db16e | ||
|
d0e678b5e9 | ||
|
e7bba968b3 | ||
|
4934a24293 | ||
|
ccb68bcda9 | ||
|
66bf4ba3ad | ||
|
78a0cfd052 | ||
|
8548373742 | ||
|
2dfd725ee0 | ||
|
5b779b4f14 | ||
|
fc48aa7155 | ||
|
6193a842f4 | ||
|
eb86b471fe | ||
|
2b52584547 | ||
|
6e3cc57f48 | ||
|
f2c04621a5 | ||
|
3ed6938d4a | ||
|
99fec25ed5 | ||
|
73758ad1fd | ||
|
c53fe0ed1f | ||
|
6082c2bcac | ||
|
069abed784 | ||
|
7d8fa4d78a | ||
|
76081f8d89 | ||
|
5ef4285558 | ||
|
aa7df4282e | ||
|
0ef1d5d0de | ||
|
cceb4bb81f | ||
|
f0f45e007d | ||
|
6a8ffe1642 | ||
|
3a73868c10 | ||
|
ab1b5897a6 | ||
|
f94734a5c8 | ||
|
61b86c9584 | ||
|
1ac1d6e903 | ||
|
b6c58f74c0 | ||
|
c88e99d87c | ||
|
8d7ab9d05e | ||
|
47155a4a29 | ||
|
d0b87fd7c3 | ||
|
d49fd37656 | ||
|
0bd29d71be | ||
|
4b5b62c8ae | ||
|
65fb2ca2d5 | ||
|
0d5bfc0997 | ||
|
4f02c373c2 | ||
|
209a5b1207 | ||
|
95349eacab | ||
|
82ba604b99 | ||
|
46a8dec655 | ||
|
b5af234524 | ||
|
b5c41750f7 | ||
|
6083824eec | ||
|
40977785c3 | ||
|
5eddf4f1aa | ||
|
99a8e25411 | ||
|
08587d8f2f | ||
|
3480d50f61 | ||
|
43af55252d | ||
|
9c43b31fc0 | ||
|
9ec7184aa1 | ||
|
4e2cb30db7 | ||
|
9ca83d3291 | ||
|
6b2172d873 | ||
|
1a5d9f7dad | ||
|
a8425862f0 | ||
|
a3a3db6abb | ||
|
d6c3bc57c0 | ||
|
59c09f90f9 | ||
|
d982b83e14 | ||
|
cc0e930d34 | ||
|
3c3d77fbea | ||
|
da7453fdbf | ||
|
5fcd25506e | ||
|
4979a472de | ||
|
4e8d4f4591 | ||
|
0f5d2b15e0 | ||
|
7fc9631434 | ||
|
df5953dd7b | ||
|
8f5b2b4a0e | ||
|
43c63ffa70 | ||
|
6779bc7459 | ||
|
664be2d0ba | ||
|
6113898b69 | ||
|
79aad6b5c2 | ||
|
6da7757d36 | ||
|
dbb3cb8cc8 | ||
|
579f36a1dd | ||
|
72c2b306cf | ||
|
c2673cd396 | ||
|
8eb152816a | ||
|
a0bc8a21a5 | ||
|
08e4d72758 | ||
|
66340a27fa | ||
|
83fe9835b6 | ||
|
4c1a50a3ca | ||
|
fe44a7b3bc | ||
|
e86d192db7 | ||
|
ea8f1c52f9 | ||
|
a4c1573c45 | ||
|
182bf49ad0 | ||
|
13e1ddbccd | ||
|
327b9a1757 | ||
|
18c48db7f7 | ||
|
b6543bd87f | ||
|
9ad8f71d7c | ||
|
e369311fc2 | ||
|
72ff261fe3 | ||
|
774c6f7e05 | ||
|
771af6ae08 | ||
|
b3cd207444 | ||
|
03f9fa4bc2 | ||
|
e32bfd9aab | ||
|
7e47f8f893 | ||
|
cb816e9653 | ||
|
6b3e7cbc08 | ||
|
db4993ae9b | ||
|
4dc3cf6c6b | ||
|
2b84bbf3a8 | ||
|
26ef4c9961 | ||
|
4f56c38599 | ||
|
240f4dcfb1 | ||
|
ac6abd81c9 | ||
|
14bda4f3a5 | ||
|
61b9670b45 | ||
|
01e8db317e | ||
|
92fc09493e | ||
|
d927209db7 | ||
|
e94007b21f | ||
|
18750f275a | ||
|
d686a853f4 |
@ -1,5 +0,0 @@
|
|||||||
[target.aarch64-unknown-linux-gnu]
|
|
||||||
linker = "aarch64-linux-gnu-gcc"
|
|
||||||
|
|
||||||
[target.armv7-unknown-linux-gnueabihf]
|
|
||||||
linker = "arm-linux-gnueabihf-gcc"
|
|
62
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,50 +1,23 @@
|
|||||||
name: 问题反馈 / Bug report
|
name: Bug report
|
||||||
title: "[BUG] "
|
description: Create a report to help us improve
|
||||||
description: 反馈你遇到的问题 / Report the issue you are experiencing
|
title: "[BUG]"
|
||||||
labels: ["bug"]
|
labels: ["bug"]
|
||||||
type: "Bug"
|
|
||||||
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
## 在提交问题之前,请确认以下事项:
|
|
||||||
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide/term.html) 以及 [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
|
|
||||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
|
|
||||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
|
||||||
4. 请 **务必** 查看 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本更新日志
|
|
||||||
5. 请 **务必** 尝试 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本,确定问题是否仍然存在
|
|
||||||
6. 请 **务必** 按照模板规范详细描述问题以及尝试更新 Alpha 版本,否则issue将会被直接关闭
|
|
||||||
## Before submitting the issue, please make sure of the following checklist:
|
|
||||||
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide/term.html) and [FAQ](https://clash-verge-rev.github.io/faq/windows.html)
|
|
||||||
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
|
|
||||||
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
|
|
||||||
4. Please be sure to check out [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version update log
|
|
||||||
5. Please be sure to try the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version to ensure that the problem still exists
|
|
||||||
6. Please describe the problem in detail according to the template specification and try to update the Alpha version, otherwise the issue will be closed
|
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
|
||||||
attributes:
|
attributes:
|
||||||
label: 问题描述 / Describe the bug
|
label: Describe the bug
|
||||||
description: 详细清晰地描述你遇到的问题,并配合截图 / Describe the problem you encountered in detail and clearly, and provide screenshots
|
description: A clear and concise description of what the bug is.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: 软件版本 / Verge Version
|
label: To Reproduce
|
||||||
description: 请提供Verge的具体版本,如果是alpha版本,请注明下载时间(精确到小时分钟) / Please provide the specific version of Verge. If it is an alpha version, please indicate the download time (accurate to hours and minutes)
|
description: Steps to reproduce the behavior.
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 复现步骤 / To Reproduce
|
|
||||||
description: 请提供复现问题的步骤 / Steps to reproduce the behavior
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: 操作系统 / OS
|
label: Platform
|
||||||
options:
|
options:
|
||||||
- Windows
|
- Windows
|
||||||
- Linux
|
- Linux
|
||||||
@ -53,13 +26,20 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: 操作系统版本 / OS Version
|
label: System Version
|
||||||
description: 请提供你的操作系统版本,Linux请额外提供桌面环境及窗口系统 / Please provide your OS version, for Linux, please also provide the desktop environment and window system
|
placeholder: "e.g. macOS 10.15.7"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Software Version
|
||||||
|
placeholder: "e.g. 1.4.3"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: 日志(勿上传日志文件,请粘贴日志内容) / Log (Do not upload the log file, paste the log content directly)
|
label: Log
|
||||||
description: 请提供完整或相关部分的Debug日志(请在“软件左侧菜单”->“设置”->“日志等级”调整到debug,Verge错误请把“杂项设置”->“app日志等级”调整到debug,并重启Verge生效。日志文件在“软件左侧菜单”->“设置”->“日志目录”下) / Please provide a complete or relevant part of the Debug log (please adjust the "Log level" to debug in "Software left menu" -> "Settings" -> "Log level". If there is a Verge error, please adjust "Miscellaneous settings" -> "app log level" to debug, and restart Verge to take effect. The log file is under "Software left menu" -> "Settings" -> "Log directory")
|
description: "Log file content or screenshot"
|
||||||
validations:
|
- type: textarea
|
||||||
required: true
|
attributes:
|
||||||
|
label: Additional Information
|
||||||
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,4 +0,0 @@
|
|||||||
contact_links:
|
|
||||||
- name: 讨论交流 / Communication
|
|
||||||
url: https://t.me/clash_verge_rev
|
|
||||||
about: 在 Telegram 群组中与其他用户讨论交流 / Communicate with other users in the Telegram group
|
|
48
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,47 +1,27 @@
|
|||||||
name: 功能请求 / Feature request
|
name: Feature request
|
||||||
title: "[Feature] "
|
description: Suggest an idea for this project
|
||||||
description: 提出你的功能请求 / Propose your feature request
|
title: "[Feature]"
|
||||||
labels: ["enhancement"]
|
labels: ["enhancement"]
|
||||||
type: "Feature"
|
|
||||||
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
## 在提交问题之前,请确认以下事项:
|
|
||||||
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide/term.html) 确认软件不存在类似的功能
|
|
||||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
|
|
||||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
|
||||||
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保该功能还未实现
|
|
||||||
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
|
|
||||||
## Before submitting the issue, please make sure of the following checklist:
|
|
||||||
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide/term.html) to confirm that the software does not have similar functions
|
|
||||||
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
|
|
||||||
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
|
|
||||||
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the function has not been implemented
|
|
||||||
5. Please describe the problem in detail according to the template specification, otherwise the issue will be closed
|
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
|
||||||
attributes:
|
attributes:
|
||||||
label: 功能描述 / Feature description
|
label: Is your feature request related to a problem? Please describe.
|
||||||
description: 详细清晰地描述你的功能请求 / A clear and concise description of what the feature is
|
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: 使用场景 / Use case
|
label: Describe the solution you'd like
|
||||||
description: 请描述你的功能请求的使用场景 / Please describe the use case of your feature request
|
description: A clear and concise description of what you want to happen.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: checkboxes
|
- type: textarea
|
||||||
id: os-labels
|
|
||||||
attributes:
|
attributes:
|
||||||
label: 适用系统 / Target OS
|
label: Describe alternatives you've considered
|
||||||
description: 请选择该功能适用的操作系统(至少选择一个) / Please select the operating system(s) for this feature request (select at least one)
|
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||||
options:
|
|
||||||
- label: windows
|
|
||||||
- label: macos
|
|
||||||
- label: linux
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context or screenshots about the feature request here.
|
||||||
|
4
.github/build-for-linux/Dockerfile
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
FROM rust:buster
|
||||||
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
RUN chmod a+x /entrypoint.sh
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
14
.github/build-for-linux/action.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
name: "Build for Linux"
|
||||||
|
branding:
|
||||||
|
icon: user-check
|
||||||
|
color: gray-dark
|
||||||
|
inputs:
|
||||||
|
target:
|
||||||
|
required: true
|
||||||
|
description: "Rust Target"
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "docker"
|
||||||
|
image: "Dockerfile"
|
||||||
|
args:
|
||||||
|
- ${{ inputs.target }}
|
8
.github/build-for-linux/build.sh
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
pnpm install
|
||||||
|
pnpm check $INPUT_TARGET
|
||||||
|
sed -i "s/#openssl/openssl={version=\"0.10\",features=[\"vendored\"]}/g" src-tauri/Cargo.toml
|
||||||
|
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
|
||||||
|
pnpm build --target $INPUT_TARGET
|
||||||
|
else
|
||||||
|
pnpm build --target $INPUT_TARGET -b deb
|
||||||
|
fi
|
37
.github/build-for-linux/entrypoint.sh
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
wget https://nodejs.org/dist/v20.10.0/node-v20.10.0-linux-x64.tar.xz
|
||||||
|
tar -Jxvf ./node-v20.10.0-linux-x64.tar.xz
|
||||||
|
export PATH=$(pwd)/node-v20.10.0-linux-x64/bin:$PATH
|
||||||
|
npm install pnpm -g
|
||||||
|
|
||||||
|
rustup target add "$INPUT_TARGET"
|
||||||
|
|
||||||
|
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||||
|
elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
|
||||||
|
dpkg --add-architecture arm64
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y libncurses6:arm64 libtinfo6:arm64 linux-libc-dev:arm64 libncursesw6:arm64 libssl3:arm64 libcups2:arm64
|
||||||
|
apt-get install -y --no-install-recommends g++-aarch64-linux-gnu libc6-dev-arm64-cross libssl-dev:arm64 libwebkit2gtk-4.0-dev:arm64 libgtk-3-dev:arm64 patchelf:arm64 librsvg2-dev:arm64 libayatana-appindicator3-dev:arm64
|
||||||
|
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
|
||||||
|
export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc
|
||||||
|
export CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++
|
||||||
|
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
|
||||||
|
export PKG_CONFIG_ALLOW_CROSS=1
|
||||||
|
elif [ "$INPUT_TARGET" = "armv7-unknown-linux-gnueabihf" ]; then
|
||||||
|
dpkg --add-architecture armhf
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y libncurses6:armhf libtinfo6:armhf linux-libc-dev:armhf libncursesw6:armhf libssl3:armhf libcups2:armhf
|
||||||
|
apt-get install -y --no-install-recommends g++-arm-linux-gnueabihf libc6-dev-armhf-cross libssl-dev:armhf libwebkit2gtk-4.0-dev:armhf libgtk-3-dev:armhf patchelf:armhf librsvg2-dev:armhf libayatana-appindicator3-dev:armhf
|
||||||
|
export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc
|
||||||
|
export CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc
|
||||||
|
export CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++
|
||||||
|
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig
|
||||||
|
export PKG_CONFIG_ALLOW_CROSS=1
|
||||||
|
else
|
||||||
|
echo "Unknown target: $INPUT_TARGET" && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
bash .github/build-for-linux/build.sh
|
482
.github/workflows/alpha.yml
vendored
@ -2,214 +2,16 @@ name: Alpha Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
push:
|
||||||
# UTC+8 0,6,12,18
|
branches: [main]
|
||||||
- cron: "0 16,22,4,10 * * *"
|
tags-ignore: [updater, alpha]
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
env:
|
env:
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
RUST_BACKTRACE: short
|
RUST_BACKTRACE: short
|
||||||
concurrency:
|
|
||||||
# only allow per workflow per commit (and not pr) to run at a time
|
|
||||||
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
|
|
||||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_commit:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Check if version changed or src changed
|
|
||||||
id: check
|
|
||||||
run: |
|
|
||||||
# For manual workflow_dispatch, always run
|
|
||||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
|
||||||
echo "should_run=true" >> $GITHUB_OUTPUT
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Store current version from package.json
|
|
||||||
CURRENT_VERSION=$(cat package.json | jq -r '.version')
|
|
||||||
echo "Current version: $CURRENT_VERSION"
|
|
||||||
|
|
||||||
# Get the previous commit's package.json version
|
|
||||||
git checkout HEAD~1 package.json
|
|
||||||
PREVIOUS_VERSION=$(cat package.json | jq -r '.version')
|
|
||||||
echo "Previous version: $PREVIOUS_VERSION"
|
|
||||||
|
|
||||||
# Reset back to current commit
|
|
||||||
git checkout HEAD package.json
|
|
||||||
|
|
||||||
# Check if version changed
|
|
||||||
if [ "$CURRENT_VERSION" != "$PREVIOUS_VERSION" ]; then
|
|
||||||
echo "Version changed from $PREVIOUS_VERSION to $CURRENT_VERSION"
|
|
||||||
echo "should_run=true" >> $GITHUB_OUTPUT
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if src or src-tauri directories changed
|
|
||||||
CURRENT_SRC_HASH=$(git rev-parse HEAD:src)
|
|
||||||
PREVIOUS_SRC_HASH=$(git rev-parse HEAD~1:src 2>/dev/null || echo "")
|
|
||||||
CURRENT_TAURI_HASH=$(git rev-parse HEAD:src-tauri 2>/dev/null || echo "")
|
|
||||||
PREVIOUS_TAURI_HASH=$(git rev-parse HEAD~1:src-tauri 2>/dev/null || echo "")
|
|
||||||
|
|
||||||
echo "Current src hash: $CURRENT_SRC_HASH"
|
|
||||||
echo "Previous src hash: $PREVIOUS_SRC_HASH"
|
|
||||||
echo "Current tauri hash: $CURRENT_TAURI_HASH"
|
|
||||||
echo "Previous tauri hash: $PREVIOUS_TAURI_HASH"
|
|
||||||
|
|
||||||
if [ "$CURRENT_SRC_HASH" != "$PREVIOUS_SRC_HASH" ] || [ "$CURRENT_TAURI_HASH" != "$PREVIOUS_TAURI_HASH" ]; then
|
|
||||||
echo "Source directories changed"
|
|
||||||
echo "should_run=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "Version and source directories unchanged"
|
|
||||||
echo "should_run=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
delete_old_assets:
|
|
||||||
needs: check_commit
|
|
||||||
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Delete Old Alpha Release Assets
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
script: |
|
|
||||||
const releaseTag = 'alpha';
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get the release by tag name
|
|
||||||
const { data: release } = await github.rest.repos.getReleaseByTag({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
tag: releaseTag
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Found release with ID: ${release.id}`);
|
|
||||||
|
|
||||||
// Delete each asset
|
|
||||||
if (release.assets && release.assets.length > 0) {
|
|
||||||
console.log(`Deleting ${release.assets.length} assets`);
|
|
||||||
|
|
||||||
for (const asset of release.assets) {
|
|
||||||
console.log(`Deleting asset: ${asset.name} (${asset.id})`);
|
|
||||||
await github.rest.repos.deleteReleaseAsset({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
asset_id: asset.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('All assets deleted successfully');
|
|
||||||
} else {
|
|
||||||
console.log('No assets found to delete');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.status === 404) {
|
|
||||||
console.log('Release not found, nothing to delete');
|
|
||||||
} else {
|
|
||||||
console.error('Error:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update_tag:
|
|
||||||
name: Update tag
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: delete_old_assets
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Fetch Alpha update logs
|
|
||||||
id: fetch_alpha_logs
|
|
||||||
run: |
|
|
||||||
# Check if UPDATELOG.md exists
|
|
||||||
if [ -f "UPDATELOG.md" ]; then
|
|
||||||
# Extract the section starting with ## and containing -alpha until the next ## or end of file
|
|
||||||
# ALPHA_LOGS=$(awk '/^## .*-alpha/{flag=1; print; next} /^## /{flag=0} flag' UPDATELOG.md)
|
|
||||||
ALPHA_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
|
|
||||||
|
|
||||||
if [ -n "$ALPHA_LOGS" ]; then
|
|
||||||
echo "Found alpha update logs"
|
|
||||||
echo "ALPHA_LOGS<<EOF" >> $GITHUB_ENV
|
|
||||||
echo "$ALPHA_LOGS" >> $GITHUB_ENV
|
|
||||||
echo "EOF" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "No alpha sections found in UPDATELOG.md"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "UPDATELOG.md file not found"
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set Env
|
|
||||||
run: |
|
|
||||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- run: |
|
|
||||||
# 检查 ALPHA_LOGS 是否存在,如果不存在则使用默认消息
|
|
||||||
if [ -z "$ALPHA_LOGS" ]; then
|
|
||||||
echo "No alpha logs found, using default message"
|
|
||||||
ALPHA_LOGS="More new features are now supported. Check for detailed changelog soon."
|
|
||||||
else
|
|
||||||
echo "Using found alpha logs"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 生成 release.txt 文件
|
|
||||||
cat > release.txt << EOF
|
|
||||||
$ALPHA_LOGS
|
|
||||||
|
|
||||||
## 我应该下载哪个版本?
|
|
||||||
|
|
||||||
### MacOS
|
|
||||||
- MacOS intel芯片: x64.dmg
|
|
||||||
- MacOS apple M芯片: aarch64.dmg
|
|
||||||
|
|
||||||
### Linux
|
|
||||||
- Linux 64位: amd64.deb/amd64.rpm
|
|
||||||
- Linux arm64 architecture: arm64.deb/aarch64.rpm
|
|
||||||
- Linux armv7架构: armhf.deb/armhfp.rpm
|
|
||||||
|
|
||||||
### Windows (不再支持Win7)
|
|
||||||
#### 正常版本(推荐)
|
|
||||||
- 64位: x64-setup.exe
|
|
||||||
- arm64架构: arm64-setup.exe
|
|
||||||
#### 便携版问题很多不再提供
|
|
||||||
#### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用)
|
|
||||||
- 64位: x64_fixed_webview2-setup.exe
|
|
||||||
- arm64架构: arm64_fixed_webview2-setup.exe
|
|
||||||
|
|
||||||
### FAQ
|
|
||||||
|
|
||||||
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
|
|
||||||
|
|
||||||
### 稳定机场VPN推荐
|
|
||||||
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
|
||||||
|
|
||||||
Created at ${{ env.BUILDTIME }}.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Upload Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
tag_name: alpha
|
|
||||||
name: "Clash Verge Rev Alpha"
|
|
||||||
body_path: release.txt
|
|
||||||
prerelease: true
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
generate_release_notes: true
|
|
||||||
|
|
||||||
alpha:
|
alpha:
|
||||||
needs: update_tag
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -222,14 +24,23 @@ jobs:
|
|||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
- os: ubuntu-22.04
|
|
||||||
target: x86_64-unknown-linux-gnu
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Apply Patch
|
||||||
|
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||||
|
run: |
|
||||||
|
git config --global user.email "clash-verge-rev@github.io"
|
||||||
|
git config --global user.name "clash-verge-rev"
|
||||||
|
git am patches/support-windows-aarch64.patch
|
||||||
|
|
||||||
|
- name: Init Submodule
|
||||||
|
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||||
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
@ -240,23 +51,16 @@ jobs:
|
|||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: src-tauri
|
workspaces: src-tauri
|
||||||
cache-all-crates: true
|
|
||||||
cache-on-failure: true
|
|
||||||
|
|
||||||
- name: Install dependencies (ubuntu only)
|
|
||||||
if: matrix.os == 'ubuntu-22.04'
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "20"
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v2
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
|
version: 8
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Pnpm install and check
|
- name: Pnpm install and check
|
||||||
@ -264,22 +68,12 @@ jobs:
|
|||||||
pnpm i
|
pnpm i
|
||||||
pnpm check ${{ matrix.target }}
|
pnpm check ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Release Alpha Version
|
|
||||||
run: pnpm release-alpha-version
|
|
||||||
|
|
||||||
- name: Tauri build
|
- name: Tauri build
|
||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
||||||
with:
|
with:
|
||||||
tagName: alpha
|
tagName: alpha
|
||||||
releaseName: "Clash Verge Rev Alpha"
|
releaseName: "Clash Verge Rev Alpha"
|
||||||
@ -289,225 +83,87 @@ jobs:
|
|||||||
tauriScript: pnpm
|
tauriScript: pnpm
|
||||||
args: --target ${{ matrix.target }}
|
args: --target ${{ matrix.target }}
|
||||||
|
|
||||||
alpha-for-linux-arm:
|
- name: Portable Bundle
|
||||||
needs: update_tag
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: pnpm portable ${{ matrix.target }} --alpha
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
|
||||||
|
alpha-for-linux:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-22.04
|
- os: ubuntu-latest
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
- os: ubuntu-latest
|
||||||
target: aarch64-unknown-linux-gnu
|
target: aarch64-unknown-linux-gnu
|
||||||
arch: arm64
|
|
||||||
- os: ubuntu-22.04
|
|
||||||
target: armv7-unknown-linux-gnueabihf
|
|
||||||
arch: armhf
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust Stable
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
|
|
||||||
- name: Add Rust Target
|
|
||||||
run: rustup target add ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Rust Cache
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: src-tauri
|
|
||||||
cache-all-crates: true
|
|
||||||
|
|
||||||
- name: Install Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22"
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Pnpm install and check
|
|
||||||
run: |
|
|
||||||
pnpm i
|
|
||||||
pnpm check ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Release Alpha Version
|
|
||||||
run: pnpm release-alpha-version
|
|
||||||
|
|
||||||
- name: "Setup for linux"
|
|
||||||
run: |-
|
|
||||||
sudo ls -lR /etc/apt/
|
|
||||||
|
|
||||||
cat > /tmp/sources.list << EOF
|
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
|
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
|
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-updates main multiverse universe restricted
|
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-backports main multiverse universe restricted
|
|
||||||
|
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy main multiverse universe restricted
|
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main multiverse universe restricted
|
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main multiverse universe restricted
|
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main multiverse universe restricted
|
|
||||||
EOF
|
|
||||||
|
|
||||||
sudo mv /etc/apt/sources.list /etc/apt/sources.list.default
|
|
||||||
sudo mv /tmp/sources.list /etc/apt/sources.list
|
|
||||||
|
|
||||||
sudo dpkg --add-architecture ${{ matrix.arch }}
|
|
||||||
sudo apt update
|
|
||||||
|
|
||||||
sudo apt install -y \
|
|
||||||
libxslt1.1:${{ matrix.arch }} \
|
|
||||||
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
|
|
||||||
libayatana-appindicator3-dev:${{ matrix.arch }} \
|
|
||||||
libssl-dev:${{ matrix.arch }} \
|
|
||||||
patchelf:${{ matrix.arch }} \
|
|
||||||
librsvg2-dev:${{ matrix.arch }}
|
|
||||||
|
|
||||||
- name: "Install aarch64 tools"
|
|
||||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
|
||||||
run: |
|
|
||||||
sudo apt install -y \
|
|
||||||
gcc-aarch64-linux-gnu \
|
|
||||||
g++-aarch64-linux-gnu
|
|
||||||
|
|
||||||
- name: "Install armv7 tools"
|
|
||||||
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
|
|
||||||
run: |
|
|
||||||
sudo apt install -y \
|
|
||||||
gcc-arm-linux-gnueabihf \
|
|
||||||
g++-arm-linux-gnueabihf
|
|
||||||
|
|
||||||
- name: Build for Linux
|
- name: Build for Linux
|
||||||
run: |
|
uses: ./.github/build-for-linux
|
||||||
export PKG_CONFIG_ALLOW_CROSS=1
|
|
||||||
if [ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]; then
|
|
||||||
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH
|
|
||||||
export PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu/
|
|
||||||
elif [ "${{ matrix.target }}" == "armv7-unknown-linux-gnueabihf" ]; then
|
|
||||||
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig/:$PKG_CONFIG_PATH
|
|
||||||
export PKG_CONFIG_SYSROOT_DIR=/usr/arm-linux-gnueabihf/
|
|
||||||
fi
|
|
||||||
pnpm build --target ${{ matrix.target }}
|
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
with:
|
||||||
|
target: ${{ matrix.target }}
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install jq
|
sudo apt-get install jq
|
||||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Upload Release
|
- name: Upload Release
|
||||||
uses: softprops/action-gh-release@v2
|
if: startsWith(matrix.target, 'x86_64')
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: alpha
|
tag_name: alpha
|
||||||
name: "Clash Verge Rev Alpha"
|
name: "Clash Verge Rev Alpha"
|
||||||
|
body: "More new features are now supported."
|
||||||
prerelease: true
|
prerelease: true
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
files: |
|
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
|
||||||
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
- name: Upload Release
|
||||||
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
alpha-for-fixed-webview2:
|
tag_name: alpha
|
||||||
needs: update_tag
|
name: "Clash Verge Rev Alpha"
|
||||||
strategy:
|
body: "More new features are now supported."
|
||||||
fail-fast: false
|
prerelease: true
|
||||||
matrix:
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
include:
|
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||||
- os: windows-latest
|
update_tag:
|
||||||
target: x86_64-pc-windows-msvc
|
name: Update tag
|
||||||
arch: x64
|
runs-on: ubuntu-latest
|
||||||
- os: windows-latest
|
needs: [alpha, alpha-for-linux]
|
||||||
target: aarch64-pc-windows-msvc
|
|
||||||
arch: arm64
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
- name: Set Env
|
||||||
- name: Add Rust Target
|
|
||||||
run: rustup target add ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Rust Cache
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: src-tauri
|
|
||||||
cache-all-crates: true
|
|
||||||
cache-on-failure: true
|
|
||||||
|
|
||||||
- name: Install Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22"
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Pnpm install and check
|
|
||||||
run: |
|
run: |
|
||||||
pnpm i
|
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
||||||
pnpm check ${{ matrix.target }}
|
shell: bash
|
||||||
|
- name: Update Tag
|
||||||
- name: Release Alpha Version
|
uses: richardsimko/update-tag@v1
|
||||||
run: pnpm release-alpha-version
|
with:
|
||||||
|
tag_name: alpha
|
||||||
- name: Download WebView2 Runtime
|
|
||||||
run: |
|
|
||||||
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
|
|
||||||
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
|
|
||||||
Remove-Item .\src-tauri\tauri.windows.conf.json
|
|
||||||
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
|
|
||||||
|
|
||||||
- name: Tauri build
|
|
||||||
id: build
|
|
||||||
uses: tauri-apps/tauri-action@v0
|
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
- run: |
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
cat > release.txt << 'EOF'
|
||||||
with:
|
## Clash Verge Rev Alpha
|
||||||
tauriScript: pnpm
|
Created at ${{ env.BUILDTIME }}.
|
||||||
args: --target ${{ matrix.target }}
|
EOF
|
||||||
|
|
||||||
- name: Rename
|
|
||||||
run: |
|
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
|
|
||||||
foreach ($file in $files) {
|
|
||||||
$newName = $file.Name -replace "-setup\.exe$", "_fixed_webview2-setup.exe"
|
|
||||||
Rename-Item $file.FullName $newName
|
|
||||||
}
|
|
||||||
|
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
|
|
||||||
foreach ($file in $files) {
|
|
||||||
$newName = $file.Name -replace "-setup\.nsis\.zip$", "_fixed_webview2-setup.nsis.zip"
|
|
||||||
Rename-Item $file.FullName $newName
|
|
||||||
}
|
|
||||||
|
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
|
|
||||||
foreach ($file in $files) {
|
|
||||||
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
|
|
||||||
Rename-Item $file.FullName $newName
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Upload Release
|
- name: Upload Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: alpha
|
tag_name: alpha
|
||||||
name: "Clash Verge Rev Alpha"
|
name: "Clash Verge Rev Alpha"
|
||||||
|
body_path: release.txt
|
||||||
prerelease: true
|
prerelease: true
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
generate_release_notes: true
|
||||||
|
|
||||||
- name: Portable Bundle
|
|
||||||
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --alpha
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
94
.github/workflows/dev.yml
vendored
@ -1,94 +0,0 @@
|
|||||||
name: Development Test
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
permissions: write-all
|
|
||||||
env:
|
|
||||||
CARGO_INCREMENTAL: 0
|
|
||||||
RUST_BACKTRACE: short
|
|
||||||
concurrency:
|
|
||||||
# only allow per workflow per commit (and not pr) to run at a time
|
|
||||||
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
|
|
||||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
dev:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: windows-latest
|
|
||||||
target: x86_64-pc-windows-msvc
|
|
||||||
bundle: nsis
|
|
||||||
- os: macos-latest
|
|
||||||
target: aarch64-apple-darwin
|
|
||||||
bundle: dmg
|
|
||||||
- os: macos-latest
|
|
||||||
target: x86_64-apple-darwin
|
|
||||||
bundle: dmg
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Rust Stable
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
|
|
||||||
- name: Add Rust Target
|
|
||||||
run: rustup target add ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Rust Cache
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: src-tauri
|
|
||||||
cache-all-crates: true
|
|
||||||
cache-on-failure: true
|
|
||||||
|
|
||||||
- name: Install Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "20"
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Pnpm install and check
|
|
||||||
run: |
|
|
||||||
pnpm i
|
|
||||||
pnpm check ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Tauri build
|
|
||||||
uses: tauri-apps/tauri-action@v0
|
|
||||||
env:
|
|
||||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
||||||
with:
|
|
||||||
tauriScript: pnpm
|
|
||||||
args: --target ${{ matrix.target }} -b ${{ matrix.bundle }}
|
|
||||||
|
|
||||||
- name: Upload Artifacts
|
|
||||||
if: matrix.os == 'macos-latest'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.target }}
|
|
||||||
path: src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Upload Artifacts
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.target }}
|
|
||||||
path: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*.exe
|
|
||||||
if-no-files-found: error
|
|
312
.github/workflows/release.yml
vendored
@ -6,10 +6,6 @@ permissions: write-all
|
|||||||
env:
|
env:
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
RUST_BACKTRACE: short
|
RUST_BACKTRACE: short
|
||||||
concurrency:
|
|
||||||
# only allow per workflow per commit (and not pr) to run at a time
|
|
||||||
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
|
|
||||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@ -25,14 +21,23 @@ jobs:
|
|||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
- os: ubuntu-22.04
|
|
||||||
target: x86_64-unknown-linux-gnu
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Apply Patch
|
||||||
|
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||||
|
run: |
|
||||||
|
git config --global user.email "clash-verge-rev@github.io"
|
||||||
|
git config --global user.name "clash-verge-rev"
|
||||||
|
git am patches/support-windows-aarch64.patch
|
||||||
|
|
||||||
|
- name: Init Submodule
|
||||||
|
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||||
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
@ -43,23 +48,16 @@ jobs:
|
|||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: src-tauri
|
workspaces: src-tauri
|
||||||
cache-all-crates: true
|
|
||||||
cache-on-failure: true
|
|
||||||
|
|
||||||
- name: Install dependencies (ubuntu only)
|
|
||||||
if: matrix.os == 'ubuntu-22.04'
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "20"
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v2
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
|
version: 8
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Pnpm install and check
|
- name: Pnpm install and check
|
||||||
@ -70,253 +68,86 @@ jobs:
|
|||||||
- name: Tauri build
|
- name: Tauri build
|
||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
||||||
with:
|
with:
|
||||||
tagName: v__VERSION__
|
tagName: v__VERSION__
|
||||||
releaseName: "Clash Verge Rev v__VERSION__"
|
releaseName: "Clash Verge Rev v__VERSION__"
|
||||||
releaseBody: "More new features are now supported."
|
releaseBody: "More new features are now supported."
|
||||||
|
releaseDraft: false
|
||||||
|
prerelease: false
|
||||||
tauriScript: pnpm
|
tauriScript: pnpm
|
||||||
args: --target ${{ matrix.target }}
|
args: --target ${{ matrix.target }}
|
||||||
|
|
||||||
release-for-linux-arm:
|
- name: Portable Bundle
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: pnpm portable ${{ matrix.target }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
|
||||||
|
release-for-linux:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-22.04
|
- os: ubuntu-latest
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
- os: ubuntu-latest
|
||||||
target: aarch64-unknown-linux-gnu
|
target: aarch64-unknown-linux-gnu
|
||||||
arch: arm64
|
|
||||||
- os: ubuntu-22.04
|
|
||||||
target: armv7-unknown-linux-gnueabihf
|
|
||||||
arch: armhf
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust Stable
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
|
|
||||||
- name: Add Rust Target
|
|
||||||
run: rustup target add ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Rust Cache
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: src-tauri
|
|
||||||
cache-all-crates: true
|
|
||||||
|
|
||||||
- name: Install Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22"
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Pnpm install and check
|
|
||||||
run: |
|
|
||||||
pnpm i
|
|
||||||
pnpm check ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: "Setup for linux"
|
|
||||||
run: |-
|
|
||||||
sudo ls -lR /etc/apt/
|
|
||||||
|
|
||||||
cat > /tmp/sources.list << EOF
|
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
|
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
|
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-updates main multiverse universe restricted
|
|
||||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-backports main multiverse universe restricted
|
|
||||||
|
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy main multiverse universe restricted
|
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main multiverse universe restricted
|
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main multiverse universe restricted
|
|
||||||
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main multiverse universe restricted
|
|
||||||
EOF
|
|
||||||
|
|
||||||
sudo mv /etc/apt/sources.list /etc/apt/sources.list.default
|
|
||||||
sudo mv /tmp/sources.list /etc/apt/sources.list
|
|
||||||
|
|
||||||
sudo dpkg --add-architecture ${{ matrix.arch }}
|
|
||||||
sudo apt update
|
|
||||||
|
|
||||||
sudo apt install -y \
|
|
||||||
libxslt1.1:${{ matrix.arch }} \
|
|
||||||
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
|
|
||||||
libayatana-appindicator3-dev:${{ matrix.arch }} \
|
|
||||||
libssl-dev:${{ matrix.arch }} \
|
|
||||||
patchelf:${{ matrix.arch }} \
|
|
||||||
librsvg2-dev:${{ matrix.arch }}
|
|
||||||
|
|
||||||
- name: "Install aarch64 tools"
|
|
||||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
|
||||||
run: |
|
|
||||||
sudo apt install -y \
|
|
||||||
gcc-aarch64-linux-gnu \
|
|
||||||
g++-aarch64-linux-gnu
|
|
||||||
|
|
||||||
- name: "Install armv7 tools"
|
|
||||||
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
|
|
||||||
run: |
|
|
||||||
sudo apt install -y \
|
|
||||||
gcc-arm-linux-gnueabihf \
|
|
||||||
g++-arm-linux-gnueabihf
|
|
||||||
|
|
||||||
- name: Build for Linux
|
- name: Build for Linux
|
||||||
run: |
|
uses: ./.github/build-for-linux
|
||||||
export PKG_CONFIG_ALLOW_CROSS=1
|
|
||||||
if [ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]; then
|
|
||||||
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH
|
|
||||||
export PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu/
|
|
||||||
elif [ "${{ matrix.target }}" == "armv7-unknown-linux-gnueabihf" ]; then
|
|
||||||
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig/:$PKG_CONFIG_PATH
|
|
||||||
export PKG_CONFIG_SYSROOT_DIR=/usr/arm-linux-gnueabihf/
|
|
||||||
fi
|
|
||||||
pnpm build --target ${{ matrix.target }}
|
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
with:
|
||||||
|
target: ${{ matrix.target }}
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install jq
|
sudo apt-get install jq
|
||||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Upload Release
|
- name: Upload Release
|
||||||
uses: softprops/action-gh-release@v2
|
if: startsWith(matrix.target, 'x86_64')
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: v${{env.VERSION}}
|
tag_name: v${{env.VERSION}}
|
||||||
name: "Clash Verge Rev v${{env.VERSION}}"
|
name: "Clash Verge Rev v${{env.VERSION}}"
|
||||||
body: "More new features are now supported."
|
body: "More new features are now supported."
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
files: |
|
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
|
||||||
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
|
||||||
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
|
|
||||||
|
|
||||||
release-for-fixed-webview2:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: windows-latest
|
|
||||||
target: x86_64-pc-windows-msvc
|
|
||||||
arch: x64
|
|
||||||
- os: windows-latest
|
|
||||||
target: aarch64-pc-windows-msvc
|
|
||||||
arch: arm64
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Add Rust Target
|
|
||||||
run: rustup target add ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Rust Cache
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: src-tauri
|
|
||||||
cache-all-crates: true
|
|
||||||
cache-on-failure: true
|
|
||||||
|
|
||||||
- name: Install Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22"
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Pnpm install and check
|
|
||||||
run: |
|
|
||||||
pnpm i
|
|
||||||
pnpm check ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Download WebView2 Runtime
|
|
||||||
run: |
|
|
||||||
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
|
|
||||||
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
|
|
||||||
Remove-Item .\src-tauri\tauri.windows.conf.json
|
|
||||||
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
|
|
||||||
|
|
||||||
- name: Tauri build
|
|
||||||
id: build
|
|
||||||
uses: tauri-apps/tauri-action@v0
|
|
||||||
env:
|
|
||||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
|
||||||
with:
|
|
||||||
tauriScript: pnpm
|
|
||||||
args: --target ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Rename
|
|
||||||
run: |
|
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
|
|
||||||
foreach ($file in $files) {
|
|
||||||
$newName = $file.Name -replace "-setup\.exe$", "_fixed_webview2-setup.exe"
|
|
||||||
Rename-Item $file.FullName $newName
|
|
||||||
}
|
|
||||||
|
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
|
|
||||||
foreach ($file in $files) {
|
|
||||||
$newName = $file.Name -replace "-setup\.nsis\.zip$", "_fixed_webview2-setup.nsis.zip"
|
|
||||||
Rename-Item $file.FullName $newName
|
|
||||||
}
|
|
||||||
|
|
||||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
|
|
||||||
foreach ($file in $files) {
|
|
||||||
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
|
|
||||||
Rename-Item $file.FullName $newName
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Upload Release
|
- name: Upload Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: v${{steps.build.outputs.appVersion}}
|
tag_name: v${{env.VERSION}}
|
||||||
name: "Clash Verge Rev v${{steps.build.outputs.appVersion}}"
|
name: "Clash Verge Rev v${{env.VERSION}}"
|
||||||
body: "More new features are now supported."
|
body: "More new features are now supported."
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||||
|
|
||||||
- name: Portable Bundle
|
|
||||||
run: pnpm portable-fixed-webview2 ${{ matrix.target }}
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
release-update:
|
release-update:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [release, release-for-linux-arm]
|
needs: [release, release-for-linux]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "20"
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v2
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
|
version: 8
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Pnpm install
|
- name: Pnpm install
|
||||||
@ -326,50 +157,3 @@ jobs:
|
|||||||
run: pnpm updater
|
run: pnpm updater
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
release-update-for-fixed-webview2:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [release-for-fixed-webview2]
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22"
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Pnpm install
|
|
||||||
run: pnpm i
|
|
||||||
|
|
||||||
- name: Release updater file
|
|
||||||
run: pnpm updater-fixed-webview2
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
submit-to-winget:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [release-update]
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Get Version
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install jq
|
|
||||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
|
||||||
- name: Submit to Winget
|
|
||||||
uses: vedantmgoyal9/winget-releaser@main
|
|
||||||
with:
|
|
||||||
identifier: ClashVergeRev.ClashVergeRev
|
|
||||||
version: ${{env.VERSION}}
|
|
||||||
release-tag: v${{env.VERSION}}
|
|
||||||
installers-regex: '_(arm64|x64|x86)-setup\.exe$'
|
|
||||||
token: ${{ secrets.WINGET_TOKEN }}
|
|
||||||
|
33
.github/workflows/updater.yml
vendored
@ -7,16 +7,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "20"
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v2
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
with:
|
with:
|
||||||
|
version: 8
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Pnpm install
|
- name: Pnpm install
|
||||||
@ -26,27 +27,3 @@ jobs:
|
|||||||
run: pnpm updater
|
run: pnpm updater
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
release-update-for-fixed-webview2:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22"
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Pnpm install
|
|
||||||
run: pnpm i
|
|
||||||
|
|
||||||
- name: Release updater file
|
|
||||||
run: pnpm updater-fixed-webview2
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
3
.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.pnpm-store
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
@ -7,5 +6,3 @@ dist-ssr
|
|||||||
update.json
|
update.json
|
||||||
scripts/_env.sh
|
scripts/_env.sh
|
||||||
.vscode
|
.vscode
|
||||||
.tool-versions
|
|
||||||
.idea
|
|
||||||
|
@ -1,16 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
#pnpm pretty-quick --staged
|
pnpm pretty-quick --staged
|
||||||
|
|
||||||
# 运行 clippy fmt
|
|
||||||
cargo fmt --manifest-path ./src-tauri/Cargo.toml
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "rustfmt failed to format the code. Please fix the issues and try again."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#git add .
|
|
||||||
|
|
||||||
# 允许提交
|
|
||||||
exit 0
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 运行 clippy
|
|
||||||
# cargo clippy --manifest-path ./src-tauri/Cargo.toml --fix
|
|
||||||
|
|
||||||
# 如果 clippy 失败,阻止 push
|
|
||||||
# if [ $? -ne 0 ]; then
|
|
||||||
# echo "Clippy found issues in sub_crate. Please fix them before pushing."
|
|
||||||
# exit 1
|
|
||||||
# fi
|
|
||||||
|
|
||||||
# 允许 push
|
|
||||||
exit 0
|
|
@ -1 +0,0 @@
|
|||||||
nodejs 21.7.1
|
|
@ -17,28 +17,15 @@ If you're a Windows user, you may need to perform some additional steps:
|
|||||||
- Make sure to add Rust and Node.js to your system's PATH. This is usually done during the installation process, but you can verify and manually add them if necessary.
|
- Make sure to add Rust and Node.js to your system's PATH. This is usually done during the installation process, but you can verify and manually add them if necessary.
|
||||||
- The gnu `patch` tool should be installed
|
- The gnu `patch` tool should be installed
|
||||||
|
|
||||||
When you setup `Rust` environment, Only use toolchain with `Windows MSVC` , to change settings follow command:
|
### Install Node.js Packages
|
||||||
|
|
||||||
|
After installing Rust and Node.js, install the necessary Node.js packages:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
rustup target add x86_64-pc-windows-msvc
|
pnpm i
|
||||||
rustup set default-host x86_64-pc-windows-msvc
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install Node.js Package
|
### Download the Clash Binary
|
||||||
|
|
||||||
After installing Rust and Node.js, install the necessary Node.js and Node Package Manager:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
npm install pnpm -g
|
|
||||||
```
|
|
||||||
|
|
||||||
### Install Dependencies
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pnpm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Download the Mihomo Core Binary
|
|
||||||
|
|
||||||
You have two options for downloading the clash binary:
|
You have two options for downloading the clash binary:
|
||||||
|
|
||||||
@ -48,7 +35,7 @@ You have two options for downloading the clash binary:
|
|||||||
# Use '--force' to force update to the latest version
|
# Use '--force' to force update to the latest version
|
||||||
# pnpm run check --force
|
# pnpm run check --force
|
||||||
```
|
```
|
||||||
- Manually download it from the [Mihomo release](https://github.com/MetaCubeX/mihomo/releases). After downloading, rename the binary according to the [Tauri configuration](https://tauri.app/v1/api/config#bundleconfig.externalbin).
|
- Manually download it from the [Clash Meta release](https://github.com/MetaCubeX/Clash.Meta/releases). After downloading, rename the binary according to the [Tauri configuration](https://tauri.app/v1/api/config#bundleconfig.externalbin).
|
||||||
|
|
||||||
### Run the Development Server
|
### Run the Development Server
|
||||||
|
|
||||||
@ -62,38 +49,12 @@ pnpm dev:diff
|
|||||||
|
|
||||||
### Build the Project
|
### Build the Project
|
||||||
|
|
||||||
To build this project:
|
If you want to build the project, use:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
For a faster build, use the following command
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pnpm build:fast
|
|
||||||
```
|
|
||||||
|
|
||||||
This uses Rust's fast-release profile which significantly reduces compilation time by disabling optimization and LTO. The resulting binary will be larger and less performant than the standard build, but it's useful for testing changes quickly.
|
|
||||||
|
|
||||||
The `Artifacts` will display in the `log` in the Terminal.
|
|
||||||
|
|
||||||
### Build clean
|
|
||||||
|
|
||||||
To clean rust build:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pnpm clean
|
|
||||||
```
|
|
||||||
|
|
||||||
### Portable Version (Windows Only)
|
|
||||||
|
|
||||||
To package portable version after the build:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
pnpm portable
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing Your Changes
|
## Contributing Your Changes
|
||||||
|
|
||||||
Once you have made your changes:
|
Once you have made your changes:
|
||||||
|
66
README.md
@ -1,5 +1,5 @@
|
|||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
<img src="./src-tauri/icons/icon.png" alt="Clash" width="128" />
|
<img src="./src/assets/image/logo.png" alt="Clash" width="128" />
|
||||||
<br>
|
<br>
|
||||||
Continuation of <a href="https://github.com/zzzgydi/clash-verge">Clash Verge</a>
|
Continuation of <a href="https://github.com/zzzgydi/clash-verge">Clash Verge</a>
|
||||||
<br>
|
<br>
|
||||||
@ -9,55 +9,57 @@
|
|||||||
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
|
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
## Preview
|
|
||||||
|
|
||||||
| Dark | Light |
|
|
||||||
| -------------------------------- | --------------------------------- |
|
|
||||||
|  |  |
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
请到发布页面下载对应的安装包:[Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases)<br>
|
Click on the corresponding link below to download the installation package. Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||||
Go to the [release page](https://github.com/clash-verge-rev/clash-verge-rev/releases) to download the corresponding installation package<br>
|
|
||||||
Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
|
||||||
|
|
||||||
### 安装说明和常见问题,请到[文档页](https://clash-verge-rev.github.io/)查看:[Doc](https://clash-verge-rev.github.io/)
|
[[Windows x64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/Clash.Verge_1.5.2_x64-setup.exe)]
|
||||||
|
[[Windows arm64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/Clash.Verge_1.5.2_arm64-setup.exe)]
|
||||||
|
|
||||||
---
|
[[macOS intel](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/Clash.Verge_1.5.2_x64.dmg)]
|
||||||
|
[[macOS apple](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/Clash.Verge_1.5.2_aarch64.dmg)]
|
||||||
|
|
||||||
### TG 频道: [@clash_verge_rev](https://t.me/clash_verge_re)
|
[[Linux x64 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/clash-verge_1.5.2_amd64.AppImage)]
|
||||||
|
[[Linux x64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/clash-verge_1.5.2_amd64.deb)]
|
||||||
|
[[Linux arm64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/clash-verge_1.5.2_arm64.deb)]
|
||||||
|
|
||||||
|
Or you can build it yourself. Supports Windows, Linux and macOS 10.15+
|
||||||
|
|
||||||
|
Notes: If you could not start the app on Windows, please check that you have [Webview2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) installed.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
|
||||||
|
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://clash-verge-rev.github.io)
|
||||||
|
- Simple UI and supports custom theme color.
|
||||||
|
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
|
||||||
|
- System proxy setting and guard.
|
||||||
|
|
||||||
|
#### TG Group: [@clash_verge_rev](https://t.me/clash_verge_rev)
|
||||||
|
|
||||||
## Promotion
|
## Promotion
|
||||||
|
|
||||||
[狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
[狗狗加速 —— 技术流机场 Doggygo VPN](https://狗狗加速.com)
|
||||||
|
|
||||||
- 高性能海外机场,免费试用,优惠套餐,解锁流媒体,全球首家支持 Hysteria 协议。
|
- 高性能海外机场,免费试用,优惠套餐,解锁流媒体,全球首家支持 Hysteria 协议。
|
||||||
- 使用 Clash Verge 专属邀请链接注册送 3 天,每天 1G 流量免费试用:[点此注册](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
- 使用 Clash Verge 专属邀请链接注册送 3 天,每天 1G 流量免费试用:https://verge.狗狗加速.com/#/register?code=oaxsAGo6
|
||||||
- Clash Verge 专属 8 折优惠码: verge20 (仅有 500 份)
|
- Clash Verge 专属 8 折优惠码: verge20 (仅有 500 份)
|
||||||
- 优惠套餐每月仅需 15.8 元,160G 流量,年付 8 折
|
- 优惠套餐每月仅需 15.8 元,160G 流量,年付 8 折
|
||||||
- 海外团队,无跑路风险,高达 50% 返佣
|
- 海外团队,无跑路风险,高达 50% 返佣
|
||||||
- 集群负载均衡设计,高速专线(兼容老客户端),极低延迟,无视晚高峰,4K 秒开
|
- 集群负载均衡设计,高速专线(兼容老客户端),极低延迟,无视晚高峰,4K 秒开
|
||||||
- 全球首家 Hysteria 协议机场,现已上线更快的 `Hysteria2` 协议(Clash Verge 客户端最佳搭配)
|
- 全球首家 Hysteria 协议机场,现已上线更快的 `Hysteria2` 协议(Clash Verge 客户端最佳搭配)
|
||||||
- 解锁流媒体及 ChatGPT
|
- 解锁流媒体及 ChatGPT
|
||||||
- 官网:[https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
- 官网:https://狗狗加速.com
|
||||||
|
|
||||||
## Features
|
## Preview
|
||||||
|
|
||||||
- 基于性能强劲的 Rust 和 Tauri 2 框架
|

|
||||||
- 内置[Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo)内核,并支持切换 `Alpha` 版本内核。
|
|
||||||
- 简洁美观的用户界面,支持自定义主题颜色、代理组/托盘图标以及 `CSS Injection`。
|
|
||||||
- 配置文件管理和增强(Merge 和 Script),配置文件语法提示。
|
|
||||||
- 系统代理和守卫、`TUN(虚拟网卡)` 模式。
|
|
||||||
- 可视化节点和规则编辑
|
|
||||||
- WebDav 配置备份和同步
|
|
||||||
|
|
||||||
### FAQ
|
### FAQ
|
||||||
|
|
||||||
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq/windows.html)
|
#### 1. **macOS** "Clash Verge" is damaged and can't be opened
|
||||||
|
|
||||||
### Donation
|
open the terminal and run `sudo xattr -r -d com.apple.quarantine /Applications/Clash\ Verge.app`
|
||||||
|
|
||||||
[捐助Clash Verge Rev的开发](https://github.com/sponsors/clash-verge-rev)
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
@ -71,6 +73,14 @@ pnpm run check
|
|||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Todos
|
||||||
|
|
||||||
|
> This keng is a little big...
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
This is a learning project for Rust practice.
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
Issue and PR welcome!
|
Issue and PR welcome!
|
||||||
|
856
UPDATELOG.md
@ -1,859 +1,3 @@
|
|||||||
## v2.2.3
|
|
||||||
|
|
||||||
#### 已知问题
|
|
||||||
- 仅在Ubuntu 22.04/24.04,Fedora 41 **Gnome桌面环境** 做过简单测试,不保证其他其他Linux发行版可用,将在未来做进一步适配和调优
|
|
||||||
- MacOS 自定义图标与速率显示推荐图标尺寸为 256x256。其他尺寸(可能)会导致不正常图标和速率间隙
|
|
||||||
- MacOS 下 墙贴主要为浅色,Tray 图标深色时图标闪烁;彩色 Tray 速率颜色淡
|
|
||||||
- Linux 下 Clash Verge Rev 内存占用显著高于 Windows / MacOS
|
|
||||||
|
|
||||||
### 2.2.3 相对于 2.2.2
|
|
||||||
#### 修复了:
|
|
||||||
- 首页“当前代理”因为重复刷新导致的CPU占用过高的问题
|
|
||||||
- “开机自启”和“DNS覆写”开关跳动问题
|
|
||||||
- 自定义托盘图标未能应用更改
|
|
||||||
- MacOS 自定义托盘图标显示速率时图标和文本间隙过大
|
|
||||||
- MacOS 托盘速率显示不全
|
|
||||||
- Linux 在系统服务模式下无法拉起 Mihomo 内核
|
|
||||||
- 使用异步操作,避免获取系统信息和切换代理模式可能带来的崩溃
|
|
||||||
- 相同节点名称可能导致的页面渲染出错
|
|
||||||
- URL Schemes被截断的问题
|
|
||||||
- 首页流量统计卡更好的时间戳范围
|
|
||||||
- 静默启动无法触发自动轻量化计时器
|
|
||||||
|
|
||||||
#### 新增了:
|
|
||||||
- Mihomo(Meta)内核升级至 1.19.4
|
|
||||||
- Clash Verge Rev 从现在开始不再强依赖系统服务和管理权限
|
|
||||||
- 支持根据用户偏好选择Sidecar(用户空间)模式或安装服务
|
|
||||||
- 增加载入初始配置文件的错误提示,防止切换到错误的订阅配置
|
|
||||||
- 检测是否以管理员模式运行软件,如果是提示无法使用开机自启
|
|
||||||
- 代理组显示节点数量
|
|
||||||
- 统一运行模式检测,支持管理员模式下开启TUN模式
|
|
||||||
- 托盘切换代理模式会根据设置自动断开之前连接
|
|
||||||
- 如订阅获取失败回退使用Clash内核代理再次尝试
|
|
||||||
|
|
||||||
#### 移除了:
|
|
||||||
- 实时保存窗口位置和大小。这个功能可能会导致窗口异常大小和位置,还需观察。
|
|
||||||
|
|
||||||
#### 优化了:
|
|
||||||
- 重构了后端内核管理逻辑,更轻量化和有效的管理内核,提高了性能和稳定性
|
|
||||||
- 前端统一刷新应用数据,优化数据获取和刷新逻辑
|
|
||||||
- 优化首页流量图表代码,调整图表文字边距
|
|
||||||
- MacOS 托盘速率更好的显示样式和更新逻辑
|
|
||||||
- 首页仅在有流量图表时显示流量图表区域
|
|
||||||
- 更新DNS默认覆写配置
|
|
||||||
- 移除测试目录,简化资源初始化逻辑
|
|
||||||
|
|
||||||
## v2.2.2
|
|
||||||
|
|
||||||
**发行代号:拓**
|
|
||||||
|
|
||||||
感谢 Tunglies 对 Verge 后端重构,性能优化做出的重大贡献!
|
|
||||||
|
|
||||||
代号释义: 本次发布在功能上的大幅扩展。新首页设计为用户带来全新交互体验,DNS 覆写功能增强网络控制能力,解锁测试页面助力内容访问自由度提升,轻量模式提供灵活使用选择。此外,macOS 应用菜单集成、sidecar 模式、诊断信息导出等新特性进一步丰富了软件的适用场景。这些新增功能显著拓宽了 Clash Verge 的功能边界,为用户提供了更强大的工具和可能性。
|
|
||||||
|
|
||||||
#### 已知问题
|
|
||||||
- 仅在Ubuntu 22.04/24.04,Fedora 41 **Gnome桌面环境** 做过简单测试,不保证其他其他Linux发行版可用,将在未来做进一步适配和调优
|
|
||||||
|
|
||||||
### 2.2.2 相对于 2.2.1(已下架不再提供)
|
|
||||||
#### 修复了:
|
|
||||||
- 弹黑框的问题(原因是服务崩溃触发重装机制)
|
|
||||||
- MacOS进入轻量模式以后隐藏Dock图标
|
|
||||||
- 增加轻量模式缺失的tray翻译
|
|
||||||
- Linux下的窗口边框被削掉的问题
|
|
||||||
|
|
||||||
#### 新增了:
|
|
||||||
- 加强服务检测和重装逻辑
|
|
||||||
- 增强内核与服务保活机制
|
|
||||||
- 增加服务模式下的僵尸进程清理机制
|
|
||||||
- 新增当服务模式多次尝试失败后自动回退至用户空间模式
|
|
||||||
|
|
||||||
### 2.2.1 相对于 2.2.0(已下架不再提供)
|
|
||||||
#### 修复了:
|
|
||||||
1. **首页**
|
|
||||||
- 修复 Direct 模式首页无法渲染
|
|
||||||
- 修复 首页启用轻量模式导致 ClashVergeRev 从托盘退出
|
|
||||||
- 修复 系统代理标识判断不准的问题
|
|
||||||
- 修复 系统代理地址错误的问题
|
|
||||||
- 代理模式“多余的切换动画”
|
|
||||||
2. **系统**
|
|
||||||
- 修复 MacOS 无法使用快捷键粘贴/选择/复制订阅地址。
|
|
||||||
- 修复 代理端口设置同步问题。
|
|
||||||
- 修复 Linux 无法与 Mihomo 核心 和 ClashVergeRev 服务通信
|
|
||||||
3. **界面**
|
|
||||||
- 修复 连接详情卡没有跟随主题色
|
|
||||||
4. **轻量模式**
|
|
||||||
- 修复 MacOS 轻量模式下 Dock 栏图标无法隐藏。
|
|
||||||
|
|
||||||
#### 新增了:
|
|
||||||
1. **首页**
|
|
||||||
- 首页文本过长自动截断
|
|
||||||
2. **轻量模式**
|
|
||||||
- 新增托盘进入轻量模式支持
|
|
||||||
- 新增进入轻量模式快捷键支持
|
|
||||||
3. **系统**
|
|
||||||
- 在 ClashVergeRev 对 Mihomo 进行操作时,总是尝试确保两者运行
|
|
||||||
- 服务器模式下启动mihomo内核的时候查找并停止其他已经存在的内核进程,防止内核假死等问题带来的通信失败
|
|
||||||
4. **托盘**
|
|
||||||
- 新增 MacOS 启用托盘速率显示时,可选隐藏托盘图标显示
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2.2.0(已下架不再提供)
|
|
||||||
|
|
||||||
#### 新增功能
|
|
||||||
1. **首页**
|
|
||||||
- 新增首页功能,默认启动页面改为首页。
|
|
||||||
- 首页流量图卡片显示上传/下载名称。
|
|
||||||
- 首页支持轻量模式切换。
|
|
||||||
- 流量统计数据持久保存。
|
|
||||||
- 限制首页配置文件卡片URL长度。
|
|
||||||
|
|
||||||
2. **DNS 设置与覆写**
|
|
||||||
- 新增 DNS 覆写功能。
|
|
||||||
- 默认启用 DNS 覆写。
|
|
||||||
|
|
||||||
3. **解锁测试**
|
|
||||||
- 新增解锁测试页面。
|
|
||||||
|
|
||||||
4. **轻量模式**
|
|
||||||
- 新增轻量模式及设置。
|
|
||||||
- 添加自动轻量模式定时器。
|
|
||||||
|
|
||||||
5. **系统支持**
|
|
||||||
- Mihomo(meta)内核升级 1.19.3
|
|
||||||
- macOS 支持 CMD+W 关闭窗口。
|
|
||||||
- 新增 macOS 应用菜单。
|
|
||||||
- 添加 macOS 安装服务时候的管理员权限提示。
|
|
||||||
- 新增 sidecar(用户空间启动内核) 模式。
|
|
||||||
|
|
||||||
6. **其他**
|
|
||||||
- 增强延迟测试日志和错误处理。
|
|
||||||
- 添加诊断信息导出。
|
|
||||||
- 新增代理命令。
|
|
||||||
|
|
||||||
#### 修复
|
|
||||||
1. **系统**
|
|
||||||
- 修复 Windows 热键崩溃。
|
|
||||||
- 修复 macOS 无框标题。
|
|
||||||
- 修复 macOS 静默启动崩溃。
|
|
||||||
- 修复 macOS tray图标错位到左上角的问题。
|
|
||||||
- 修复 Windows/Linux 运行时崩溃。
|
|
||||||
- 修复 Win10 阴影和边框问题。
|
|
||||||
- 修复 升级或重装后开机自启状态检测和同步问题。
|
|
||||||
|
|
||||||
2. **构建**
|
|
||||||
- 修复构建失败问题。
|
|
||||||
|
|
||||||
#### 优化
|
|
||||||
1. **性能**
|
|
||||||
- 重构后端,巨幅性能优化。
|
|
||||||
- 优化首页组件性能。
|
|
||||||
- 优化流量图表资源使用。
|
|
||||||
- 提升代理组列表滚动性能。
|
|
||||||
- 加快应用退出速度。
|
|
||||||
- 加快进入轻量模式速度。
|
|
||||||
- 优化小数值速度更新。
|
|
||||||
- 增加请求超时至 60 秒。
|
|
||||||
- 修复代理节点选择同步。
|
|
||||||
- 优化修改verge配置性能。
|
|
||||||
|
|
||||||
2. **重构**
|
|
||||||
- 重构后端,巨幅性能优化。
|
|
||||||
- 优化定时器管理。
|
|
||||||
- 重构 MihomoManager 处理流量。
|
|
||||||
- 优化 WebSocket 连接。
|
|
||||||
|
|
||||||
3. **其他**
|
|
||||||
- 更新依赖。
|
|
||||||
- 默认 TUN 堆栈改为 gvisor。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v2.1.2
|
|
||||||
|
|
||||||
**发行代号:臻**
|
|
||||||
|
|
||||||
代号释义: 千锤百炼臻至善,集性能跃升、功能拓展、交互焕新于一体,彰显持续打磨、全方位优化的迭代精神。
|
|
||||||
|
|
||||||
感谢 Tychristine 对社区群组管理做出的重大贡献!
|
|
||||||
|
|
||||||
##### 2.1.2相对2.1.1(已下架不再提供)更新了:
|
|
||||||
|
|
||||||
- 无法更新和签名验证失败的问题(该死的CDN缓存)
|
|
||||||
- 设置菜单区分Verge基本设置和高级设置
|
|
||||||
- 增加v2 Updater的更多功能和权限
|
|
||||||
- 退出Verge后Tun代理状态仍保留的问题
|
|
||||||
|
|
||||||
##### 2.1.1相对2.1.0(已下架不再提供)更新了:
|
|
||||||
|
|
||||||
- 检测所需的Clash Verge Service版本(杀毒软件误报可能与此有关,因为检测和安装新版本Service需管理员权限)
|
|
||||||
- MacOS下支持彩色托盘图标和更好速率显示(感谢Tunglies)
|
|
||||||
- 文件类型判断不准导致脚本检测报错的问题
|
|
||||||
- 打开Win下的阴影(Win10因底层兼容性问题,可能圆角和边框显示不太完美)
|
|
||||||
- 边框去白边
|
|
||||||
- 修复Linux下编译问题
|
|
||||||
- 修复热键无法关闭面板的问题
|
|
||||||
|
|
||||||
##### 2.1.0 - 发行代号:臻
|
|
||||||
|
|
||||||
### 功能新增
|
|
||||||
|
|
||||||
- 新增窗口状态实时监控与自动保存功能
|
|
||||||
- 增强核心配置变更时的验证与错误处理机制
|
|
||||||
- 支持通过环境变量`CLASH_VERGE_REV_IP`自定义复制IP地址
|
|
||||||
- 添加连接表列宽持久化设置与进程过滤功能
|
|
||||||
- 新增代理组首字母导航与动态滚动定位功能
|
|
||||||
- 实现连接追踪暂停/恢复功能
|
|
||||||
- 支持从托盘菜单快速切换代理配置
|
|
||||||
- 添加轻量级模式开关选项
|
|
||||||
- 允许用户自定义TUN模式增强类型和FakeIP范围
|
|
||||||
- 新增系统代理状态指示器
|
|
||||||
- 增加Alpha版本自动重命名逻辑
|
|
||||||
- 优化字母导航工具提示与防抖交互机制
|
|
||||||
|
|
||||||
### 性能优化
|
|
||||||
|
|
||||||
- 重构代理列表渲染逻辑,提升布局计算效率
|
|
||||||
- 优化代理数据更新机制,采用乐观UI策略
|
|
||||||
- 改进虚拟列表渲染性能(Virtuoso)
|
|
||||||
- 提升主窗口Clash模式切换速度(感谢Tunglies)
|
|
||||||
- 加速内核关闭流程并优化管理逻辑
|
|
||||||
- 优化节点延迟刷新速率
|
|
||||||
- 改进托盘网速显示更新逻辑
|
|
||||||
- 提升配置验证错误信息的可读性
|
|
||||||
- 重构服务架构,优化代码组织结构(感谢Tunglies)
|
|
||||||
- 优化内核启动时的配置验证流程
|
|
||||||
|
|
||||||
### 问题修复
|
|
||||||
|
|
||||||
- 修复删除节点时关联组信息残留问题
|
|
||||||
- 解决菜单切换异常与重复勾选问题
|
|
||||||
- 修正连接页流量计算错误
|
|
||||||
- 修复Windows圆角显示异常问题
|
|
||||||
- 解决控制台废弃API警告
|
|
||||||
- 修复全局热键空值导致的崩溃
|
|
||||||
- 修复Alpha版本Windows打包重命名问题
|
|
||||||
- 修复MacOS端口切换崩溃问题
|
|
||||||
- 解决Linux持续集成更新器问题
|
|
||||||
- 修复静默启动后热键失效问题
|
|
||||||
- 修正TypeScript代理组类型定义
|
|
||||||
- 修复Windows托盘图标空白问题
|
|
||||||
- 优化远程目标地址显示(替换旧版IP展示)
|
|
||||||
|
|
||||||
### 交互体验
|
|
||||||
|
|
||||||
- 统一多平台托盘图标点击行为
|
|
||||||
- 优化代理列表滚动流畅度
|
|
||||||
- 改进日志搜索功能与数据管理
|
|
||||||
- 重构热键管理逻辑,修复托盘冻结问题
|
|
||||||
- 优化托盘网速显示样式
|
|
||||||
- 增强字母导航工具提示的动态响应
|
|
||||||
|
|
||||||
### 国际化
|
|
||||||
|
|
||||||
- 新增配置检查多语言支持
|
|
||||||
- 添加轻量级模式多语言文本
|
|
||||||
- 完善多语言翻译内容
|
|
||||||
|
|
||||||
### 维护更新
|
|
||||||
|
|
||||||
- 将默认TUN协议栈改为gVisor
|
|
||||||
- 更新Node.js运行版本
|
|
||||||
- 移除自动生成更新器文件
|
|
||||||
- 清理废弃代码与未使用组件
|
|
||||||
- 禁用工作流自动Alpha标签更新
|
|
||||||
- 更新依赖库版本
|
|
||||||
- 添加MacOS格式转换函数专项测试
|
|
||||||
- 优化开发模式日志输出
|
|
||||||
|
|
||||||
### 安全增强
|
|
||||||
|
|
||||||
- 强化应用启动时的配置验证机制
|
|
||||||
- 改进脚本验证与异常处理流程
|
|
||||||
- 修复编译警告(移除无用导入)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v2.0.3
|
|
||||||
|
|
||||||
### Notice
|
|
||||||
|
|
||||||
- !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!!
|
|
||||||
- 历时3个月的紧密开发与严格测试稳定版2.0.0终于发布了:巨量改进与性能、稳定性提升,目前Clash Verge Rev已经有了比肩cfw的健壮性;而且更强大易用!
|
|
||||||
- 由于更改了服务安装逻辑,每次更新安装需要输入系统密码卸载老版本服务和安装新版本服务,以后可以丝滑使用tun(虚拟网卡)模式
|
|
||||||
|
|
||||||
### 2.0.3相对于2.0.2改进修复了:
|
|
||||||
|
|
||||||
1. 修复VLess-URL识别网络类型错误 f400f90 #2126
|
|
||||||
2. 新增系统代理绕过文本校验 c71e18e
|
|
||||||
3. 修复脚本编辑器UI显示不正确 6197249 #2267
|
|
||||||
4. 修复Shift热键无效 589324b #2278
|
|
||||||
5. 新增nushell环境变量复制 d233a84
|
|
||||||
6. 修复全局扩展脚本无法覆写DNS d22b37c #2235
|
|
||||||
7. 切换到系统代理相对于稳定的版本 38745d4
|
|
||||||
8. 修改fake-ip-range网段 0e3b631
|
|
||||||
9. 修复窗口隐藏后WebSocket未断开连接,减小内存风险 b42d13f
|
|
||||||
10. 改进系统代理绕过设置 c5c840d
|
|
||||||
11. 修复i18n翻译文本缺失 b149084
|
|
||||||
12. 修复双击托盘图标打开面板 f839d3b #2346
|
|
||||||
13. 修复Windows10窗口白色边框 4f6ca40 #2425
|
|
||||||
14. 修复Windows窗口状态恢复 4f6ca40
|
|
||||||
15. 改进保存配置文件自动重启Mihomo内核 0669f7a
|
|
||||||
16. 改进更新托盘图标性能 d9291d4
|
|
||||||
17. 修复保存配置后代理列表未更新 542baf9 #2460
|
|
||||||
18. 新增MacOS托盘显示实时速率,可在"界面设置"中关闭 1b2f1b6
|
|
||||||
19. 新增托盘菜单显示已设置的快捷键 eeff4d4
|
|
||||||
20. 新增重载配置文件错误响应"400"时显示更多错误信息 c5989d2 #2492
|
|
||||||
21. 修复GUI代理状态与菜单显示不一致 13b63b5 #2502
|
|
||||||
22. 新增默认语言跟随系统语言(无语言支持即为英语),添加了阿拉伯语、印尼语、鞑靼语支持 9655f77 #2940
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Meta(mihomo)内核升级 1.19.1
|
|
||||||
- 增加更多语言和托盘语言跟随
|
|
||||||
- MacOS增加状态栏速率显示
|
|
||||||
- 托盘显示快捷键
|
|
||||||
- 重载配置文件错误响应"400"时显示更多错误信息
|
|
||||||
- 改进保存配置文件自动重启Mihomo内核
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
- 改进更新托盘图标性能
|
|
||||||
- 窗口隐藏后WebSocket断开连接
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v2.0.2
|
|
||||||
|
|
||||||
### Notice
|
|
||||||
|
|
||||||
- !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!!
|
|
||||||
- 历时3个月的紧密开发与严格测试稳定版2.0.0终于发布了:巨量改进与性能、稳定性提升,目前Clash Verge Rev已经有了比肩cfw的健壮性;而且更强大易用!
|
|
||||||
- 由于更改了服务安装逻辑,Mac/Linux 首次安装需要输入系统密码卸载和安装服务,以后可以丝滑使用 tun(虚拟网卡)模式
|
|
||||||
- 因 Tauri 2.0 底层 bug,关闭窗口后保留webview进程,优点是再次打开面板更快,缺点是内存使用略有增加
|
|
||||||
|
|
||||||
### 2.0.2相对于2.0.1改进了:
|
|
||||||
|
|
||||||
- MacOS 下自定义图标可以支持彩色、单色切换
|
|
||||||
- 修正了 Linux 下多个内核僵尸进程的问题
|
|
||||||
- 修正了 DNS ipv6 强制覆盖的逻辑
|
|
||||||
- 修改了 MacOS tun 模式下覆盖设置 dns 字段的问题
|
|
||||||
- 修正了 MacOS tray 图标不会随代理模式更改的问题
|
|
||||||
- 静默启动下重复运行会出现多个实例的bug
|
|
||||||
- 安装的时候自动删除历史残留启动项
|
|
||||||
- Tun模式默认是还用内核推荐的 mixed 堆栈
|
|
||||||
- 改进了默认窗口大小(启动软件窗口不会那么小了)
|
|
||||||
- 改进了 WebDAV 备份超时时间机制
|
|
||||||
- 测试菜单添加滚动条
|
|
||||||
- 改进和修正了 Tun 模式下对设置的覆盖逻辑
|
|
||||||
- 修复了打开配置出错的问题
|
|
||||||
- 修复了配置文件无法拖拽添加的问题
|
|
||||||
- 改善了浅色模式的对比度
|
|
||||||
|
|
||||||
### 2.0.1相对于2.0.0改进了:
|
|
||||||
|
|
||||||
- 无法从 2.0rc和2.0.0 升级的问题(已经安装了2.0版本的需手动下载安装)
|
|
||||||
- MacOS 系统下少有的无法安装服务,无法启动的问题,目前更健壮了
|
|
||||||
- 当系统中没有 yaml 编辑器的情况下,打开文件程序崩溃的问题
|
|
||||||
- Windows 应用内升级和覆盖安装不会删除老执行文件的问题
|
|
||||||
- 修改优化了 mac 下 fakeip 段和 dns
|
|
||||||
- 测试菜单 svg 图标格式检查
|
|
||||||
- 应用内升级重复安装 vs runtime 的问题
|
|
||||||
- 修复外部控制下密码有特殊字符认证出错的问题
|
|
||||||
- 修复恢复 Webdav 备份设置后, Webdav 设置丢失的问题
|
|
||||||
- 代理页面增加快速回到顶部的按钮
|
|
||||||
|
|
||||||
### Breaking changes
|
|
||||||
|
|
||||||
- 重大框架升级:使用 Tauri 2.0(巨量改进与性能提升)
|
|
||||||
- 出现 bug 到 issues 中提出;以后不再接受1.x版本的bug反馈。
|
|
||||||
- 强烈建议完全删除 1.x 老版本再安装此版本 !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!!
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Meta(mihomo)内核升级 1.18.10
|
|
||||||
- Win 下的系统代理替换为 Shadowsocks/CFW/v2rayN 等成熟的 sysproxy.exe 方案,解决拨号/VPN 环境下无法设置系统代理的问题
|
|
||||||
- 服务模式改进为启动软件时自动安装,TUN 模式可自由开启不再限制于服务模式
|
|
||||||
- Mac 下可用 URL Scheme 导入订阅
|
|
||||||
- 可使用 Ctrl(cmd)+Q 快捷键退出程序
|
|
||||||
- 成功导入订阅的提示消息
|
|
||||||
- 能自动选中新导入的订阅
|
|
||||||
- 日志加入颜色区分
|
|
||||||
- 改进多处文本表述
|
|
||||||
- 加入图标 svg 格式检测
|
|
||||||
- 增加更多 app 调试日志
|
|
||||||
- 添加 MacOS 下白色桌面的 tray 黑色配色(但会代理系统代理、tun 模式图标失效的问题)
|
|
||||||
- 增加 Webdav 备份功能
|
|
||||||
- 添加统一延迟的设置开关
|
|
||||||
- 添加 Windows 下自动检测并下载 vc runtime 的功能
|
|
||||||
- 支持显示 mux 和 mptcp 的节点标识
|
|
||||||
- 延迟测试连接更换 http 的 cp.cloudflare.com/generate_204 (关闭统一延迟的情况下延迟测试结果会有所增加)
|
|
||||||
- 重构日志记录逻辑,可以收集和筛选所有日志类型了(之前无法记录debug的日志类型)
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
- 优化及重构内核启动管理逻辑
|
|
||||||
- 优化 TUN 启动逻辑
|
|
||||||
- 重构和优化 app_handle
|
|
||||||
- 重构系统代理绕过逻辑
|
|
||||||
- 移除无用的 PID 创建逻辑
|
|
||||||
- 优化系统 DNS 设置逻辑
|
|
||||||
- 后端实现窗口控制
|
|
||||||
- 重构 MacOS 下的 DNS 设置逻辑
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 修复已有多个订阅导入新订阅会跳选订阅的问题
|
|
||||||
- 修复多个 Linux 下的 bug, Tun 模式在 Linux 下目前工作正常
|
|
||||||
- 修复 Linux wayland 下任务栏图标缺失的问题
|
|
||||||
- 修复 Linux KDE 桌面环境无法启动的问题
|
|
||||||
- 移除多余退出变量和钩子
|
|
||||||
- 修复 MacOS 下 tray 菜单重启 app 失效的问题
|
|
||||||
- 修复某些特定配置文件载入失败的问题
|
|
||||||
- 修复 MacOS 下 tun 模式 fakeip 不生效的问题
|
|
||||||
- 修复 Linux 下 关闭 tun 模式文件报错的问题
|
|
||||||
- 修复快捷键设置的相关 bug
|
|
||||||
- 修复 Win 下点左键菜单闪现的问题(Mac 下的操作逻辑相反,默认情况下不管点左/右键均会打开菜单,闪现不属于 bug)
|
|
||||||
|
|
||||||
### Known issues
|
|
||||||
|
|
||||||
- Windows 下窗口大小无法记忆(等待上游修复)
|
|
||||||
- Webdav 备份因为安全性和兼容性问题,暂不支持跨平台配置同步
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.7.7
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 修复导入订阅没有自动重载(不显示节点)的问题
|
|
||||||
- 英语状态下修复 Windows 工具栏提示文本超过限制的问题
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.7.6
|
|
||||||
|
|
||||||
### Notice
|
|
||||||
|
|
||||||
- Clash Verge Rev 目前已进入稳定周期,日后更新将着重于 bug 修复与内核常规升级
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Meta(mihomo)内核升级 1.18.7
|
|
||||||
- 界面细节调整
|
|
||||||
- 优化服务模式安装逻辑
|
|
||||||
- 移除无用的 console log
|
|
||||||
- 能自动选择第一个订阅
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 修复服务模式安装问题
|
|
||||||
- 修复 Mac 下的代理绕过 CIDR 写法过滤
|
|
||||||
- 修复 32 位升级 URL
|
|
||||||
- 修复不同分组 URL 测试地址配置无效的问题
|
|
||||||
- 修复 Web UI 下的一处 hostname 参数
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.7.5
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 展示局域网 IP 地址信息
|
|
||||||
- 在设置页面直接复制环境变量
|
|
||||||
- 优化服务模式安装逻辑
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
- 优化切换订阅速度
|
|
||||||
- 优化更改端口速度
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 调整 MacOS 托盘图标大小
|
|
||||||
- Trojan URI 解析错误
|
|
||||||
- 卡片拖动显示层级错误
|
|
||||||
- 代理绕过格式检查错误
|
|
||||||
- MacOS 下编辑器最大化失败
|
|
||||||
- MacOS 服务安装失败
|
|
||||||
- 更改窗口大小导致闪退的问题
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.7.3
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 支持可视化编辑订阅代理组
|
|
||||||
- 支持可视化编辑订阅节点
|
|
||||||
- 支持可视化编辑订阅规则
|
|
||||||
- 扩展脚本支持订阅名称参数 `function main(config, profileName)`
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 代理绕过格式检查错误
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.7.2
|
|
||||||
|
|
||||||
### Break Changes
|
|
||||||
|
|
||||||
- 更新后请务必重新导入所有订阅,包括 Remote 和 Local
|
|
||||||
- 此版本重构了 Merge/Script,更新前请先备份好自定义 Merge 和 Script(更新并不会删除配置文件,但是旧版 Merge 和 Script 在更新后无法从前端访问,备份以防万一)
|
|
||||||
- Merge 改名为 `扩展配置`,分为 `全局扩展配置` 和 `订阅扩展配置`,全局扩展配置对所有订阅生效,订阅扩展配置只对关联的订阅生效
|
|
||||||
- Script 改名为 `扩展脚本`,同样分为 `全局扩展脚本` 和 `订阅扩展脚本`
|
|
||||||
- 订阅扩展配置在订阅右键菜单里进入
|
|
||||||
- 执行优先级为: 全局扩展配置 -> 全局扩展脚本 -> 订阅扩展配置 ->订阅扩展脚本
|
|
||||||
- 扩展配置删除了 `prepend/append` 能力,请使用 右键订阅 -> `编辑规则`/`编辑节点`/`编辑代理组` 来代替
|
|
||||||
- MacOS 用户更新后请重新安装服务模式
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 升级内核到 1.18.6
|
|
||||||
- 移除内核授权,改为服务模式实现
|
|
||||||
- 自动填充本地订阅名称
|
|
||||||
- 添加重大更新处理逻辑
|
|
||||||
- 订阅单独指定扩展配置/脚本(需要重新导入订阅)
|
|
||||||
- 添加可视化规则编辑器(需要重新导入订阅)
|
|
||||||
- 编辑器新增工具栏按钮(格式化、最大化/最小化)
|
|
||||||
- WEBUI 使用最新版 metacubex,并解决无法自动登陆问问题
|
|
||||||
- 禁用部分 Webview2 快捷键
|
|
||||||
- 热键配置新增连接符 + 号
|
|
||||||
- 新增部分悬浮提示按钮,用于解释说明
|
|
||||||
- 当日志等级为`Debug`时(更改需重启软件生效),支持点击内存主动内存回收(绿色文字)
|
|
||||||
- 设置页面右上角新增 TG 频道链接
|
|
||||||
- 各种细节优化和界面性能优化
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 修复代理绕过格式检查
|
|
||||||
- 通过进程名称关闭进程
|
|
||||||
- 退出软件时恢复 DNS 设置
|
|
||||||
- 修复创建本地订阅时更新间隔无法保存
|
|
||||||
- 连接页面列宽无法调整
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.7.1
|
|
||||||
|
|
||||||
### Break Changes
|
|
||||||
|
|
||||||
- 更新后请务必重新导入所有订阅,包括 Remote 和 Local
|
|
||||||
- 此版本重构了 Merge/Script,更新前请先备份好自定义 Merge 和 Script(更新并不会删除配置文件,但是旧版 Merge 和 Script 在更新后无法从前端访问,备份以防万一)
|
|
||||||
- Merge 改名为 `扩展配置`,分为 `全局扩展配置` 和 `订阅扩展配置`,全局扩展配置对所有订阅生效,订阅扩展配置只对关联的订阅生效
|
|
||||||
- Script 改名为 `扩展脚本`,同样分为 `全局扩展脚本` 和 `订阅扩展脚本`
|
|
||||||
- 订阅扩展配置在订阅右键菜单里进入
|
|
||||||
- 执行优先级为: 全局扩展配置 -> 全局扩展脚本 -> 订阅扩展配置 ->订阅扩展脚本
|
|
||||||
- 扩展配置删除了 `prepend/append` 能力,请使用 右键订阅 -> `编辑规则`/`编辑节点`/`编辑代理组` 来代替
|
|
||||||
- MacOS 用户更新后请重新安装服务模式
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 升级内核到 1.18.6
|
|
||||||
- 移除内核授权,改为服务模式实现
|
|
||||||
- 自动填充本地订阅名称
|
|
||||||
- 添加重大更新处理逻辑
|
|
||||||
- 订阅单独指定扩展配置/脚本(需要重新导入订阅)
|
|
||||||
- 添加可视化规则编辑器(需要重新导入订阅)
|
|
||||||
- 编辑器新增工具栏按钮(格式化、最大化/最小化)
|
|
||||||
- WEBUI 使用最新版 metacubex,并解决无法自动登陆问问题
|
|
||||||
- 禁用部分 Webview2 快捷键
|
|
||||||
- 热键配置新增连接符 + 号
|
|
||||||
- 新增部分悬浮提示按钮,用于解释说明
|
|
||||||
- 当日志等级为`Debug`时(更改需重启软件生效),支持点击内存主动内存回收(绿色文字)
|
|
||||||
- 设置页面右上角新增 TG 频道链接
|
|
||||||
- 各种细节优化和界面性能优化
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 修复代理绕过格式检查
|
|
||||||
- 通过进程名称关闭进程
|
|
||||||
- 退出软件时恢复 DNS 设置
|
|
||||||
- 修复创建本地订阅时更新间隔无法保存
|
|
||||||
- 连接页面列宽无法调整
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.7.0
|
|
||||||
|
|
||||||
### Break Changes
|
|
||||||
|
|
||||||
- 此版本重构了 Merge/Script,更新前请先备份好自定义 Merge 和 Script(更新并不会删除配置文件,但是旧版 Merge 和 Script 在更新后无法从前端访问,备份以防万一)
|
|
||||||
- Merge 改名为 `扩展配置`,分为 `全局扩展配置` 和 `订阅扩展配置`,全局扩展配置对所有订阅生效,订阅扩展配置只对关联的订阅生效
|
|
||||||
- Script 改名为 `扩展脚本`,同样分为 `全局扩展脚本` 和 `订阅扩展脚本`
|
|
||||||
- 执行优先级为: 全局扩展配置 -> 全局扩展脚本 -> 订阅扩展配置 ->订阅扩展脚本
|
|
||||||
- MacOS 用户更新后请重新安装服务模式
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 移除内核授权,改为服务模式实现
|
|
||||||
- 自动填充本地订阅名称
|
|
||||||
- 添加重大更新处理逻辑
|
|
||||||
- 订阅单独指定扩展配置/脚本(需要重新导入订阅)
|
|
||||||
- 添加可视化规则编辑器(需要重新导入订阅)
|
|
||||||
- 编辑器新增工具栏按钮(格式化、最大化/最小化)
|
|
||||||
- WEBUI 使用最新版 metacubex,并解决无法自动登陆问问题
|
|
||||||
- 禁用部分 Webview2 快捷键
|
|
||||||
- 热键配置新增连接符 + 号
|
|
||||||
- 新增部分悬浮提示按钮,用于解释说明
|
|
||||||
- 当日志等级为`Debug`时(更改需重启软件生效),支持点击内存主动内存回收(绿色文字)
|
|
||||||
- 设置页面右上角新增 TG 频道链接
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 修复代理绕过格式检查
|
|
||||||
- 通过进程名称关闭进程
|
|
||||||
- 退出软件时恢复 DNS 设置
|
|
||||||
- 修复创建本地订阅时更新间隔无法保存
|
|
||||||
- 连接页面列宽无法调整
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.6.6
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- MacOS 应用签名
|
|
||||||
- 删除 AppImage
|
|
||||||
- 应用更新对话框添加下载按钮
|
|
||||||
- 设置系统代理绕过时保留默认值
|
|
||||||
- 系统代理绕过设置输入格式检查
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- MacOS 代理组图标无法显示
|
|
||||||
- RPM 包依赖缺失
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.6.5
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 添加 RPM 包支持
|
|
||||||
- 优化细节
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- MacOS 10.15 编辑器空白的问题
|
|
||||||
- MacOS 低版本启动白屏的问题
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.6.4
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 系统代理支持 PAC 模式
|
|
||||||
- 允许关闭不使用的端口
|
|
||||||
- 使用新的应用图标
|
|
||||||
- MacOS 支持切换托盘图标单色/彩色模式
|
|
||||||
- CSS 注入支持通过编辑器编辑
|
|
||||||
- 优化代理组列表性能
|
|
||||||
- 优化流量图显性能
|
|
||||||
- 支持波斯语
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- Kill 内核后 Tun 开启缓慢的问题
|
|
||||||
- 代理绕过为空时使用默认值
|
|
||||||
- 无法读取剪切板内容
|
|
||||||
- Windows 下覆盖安装无法内核占用问题
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.6.2
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 支持本地文件拖拽导入
|
|
||||||
- 重新支持 32 位 CPU
|
|
||||||
- 新增内置 Webview2 版本
|
|
||||||
- 优化 Merge 逻辑,支持深度合并
|
|
||||||
- 删除 Merge 配置中的 append/prepend-provider 字段
|
|
||||||
- 支持更新稳定版内核
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- MacOS DNS 还原失败
|
|
||||||
- CMD 环境变量格式错误
|
|
||||||
- Linux 下与 N 卡的兼容性问题
|
|
||||||
- 修改 Tun 设置不立即生效
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.6.1
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 鼠标悬浮显示当前订阅的名称 [#938](https://github.com/clash-verge-rev/clash-verge-rev/pull/938)
|
|
||||||
- 日志过滤支持正则表达式 [#959](https://github.com/clash-verge-rev/clash-verge-rev/pull/959)
|
|
||||||
- 更新 Clash 内核到 1.18.4
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 修复 Linux KDE 环境下系统代理无法开启的问题
|
|
||||||
- 窗口最大化图标调整 [#924](https://github.com/clash-verge-rev/clash-verge-rev/pull/924)
|
|
||||||
- 修改 MacOS 托盘点击行为(左键菜单,右键点击事件)
|
|
||||||
- 修复 MacOS 服务模式安装失败的问题
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.6.0
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Meta(mihomo)内核回退 1.18.1(当前新版内核 hy2 协议有 bug,等修复后更新)
|
|
||||||
- 多处界面细节调整 [#724](https://github.com/clash-verge-rev/clash-verge-rev/pull/724) [#799](https://github.com/clash-verge-rev/clash-verge-rev/pull/799) [#900](https://github.com/clash-verge-rev/clash-verge-rev/pull/900) [#901](https://github.com/clash-verge-rev/clash-verge-rev/pull/901)
|
|
||||||
- Linux 下新增服务模式
|
|
||||||
- 新增订阅卡片右键可以打开机场首页
|
|
||||||
- url-test 支持手动选择、节点组 fixed 节点使用角标展示 [#840](https://github.com/clash-verge-rev/clash-verge-rev/pull/840)
|
|
||||||
- Clash 配置、Merge 配置提供 JSON Schema 语法支持、连接界面调整 [#887](https://github.com/clash-verge-rev/clash-verge-rev/pull/887)
|
|
||||||
- 修改 Merge 配置文件默认内容 [#889](https://github.com/clash-verge-rev/clash-verge-rev/pull/889)
|
|
||||||
- 修改 tun 模式默认 mtu 为 1500,老版本升级,需在 tun 模式设置下“重置为默认值”。
|
|
||||||
- 使用 npm 安装 meta-json-schema [#895](https://github.com/clash-verge-rev/clash-verge-rev/pull/895)
|
|
||||||
- 更新部分翻译 [#904](https://github.com/clash-verge-rev/clash-verge-rev/pull/904)
|
|
||||||
- 支持 ico 格式的任务栏图标
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 修复 Linux KDE 环境下系统代理无法开启的问题
|
|
||||||
- 修复延迟检测动画问题
|
|
||||||
- 窗口最大化图标调整 [#816](https://github.com/clash-verge-rev/clash-verge-rev/pull/816)
|
|
||||||
- 修复 Windows 某些情况下无法安装服务模式 [#822](https://github.com/clash-verge-rev/clash-verge-rev/pull/822)
|
|
||||||
- UI 细节修复 [#821](https://github.com/clash-verge-rev/clash-verge-rev/pull/821)
|
|
||||||
- 修复使用默认编辑器打开配置文件
|
|
||||||
- 修复内核文件在特定目录也可以更新的问题 [#857](https://github.com/clash-verge-rev/clash-verge-rev/pull/857)
|
|
||||||
- 修复服务模式的安装目录问题
|
|
||||||
- 修复删除配置文件的“更新间隔”出现的问题 [#907](https://github.com/clash-verge-rev/clash-verge-rev/issues/907)
|
|
||||||
|
|
||||||
### 已知问题(历史遗留问题,暂未找到有效解决方案)
|
|
||||||
|
|
||||||
- MacOS M 芯片下服务模式无法安装;临时解决方案:在内核 ⚙️ 下,手动授权,再打开 tun 模式。
|
|
||||||
- MacOS 下如果删除过网络配置,会导致无法正常打开系统代理;临时解决方案:使用浏览器代理插件或手动配置系统代理。
|
|
||||||
- Window 拨号连接下无法正确识别并打开系统代理;临时解决方案:使用浏览器代理插件或使用 tun 模式。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.5.11
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Meta(mihomo)内核更新 1.18.2
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 升级图标无法点击的问题
|
|
||||||
- 卸载时检查安装目录是否为空
|
|
||||||
- 代理界面图标重合的问题
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.5.10
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 优化 Linux 托盘菜单显示
|
|
||||||
- 添加透明代理端口设置
|
|
||||||
- 删除订阅前确认
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 删除 MacOS 程序坞图标
|
|
||||||
- Windows 下 service 日志没有清理
|
|
||||||
- MacOS 无法开启系统代理
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.5.9
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 缓存代理组图标
|
|
||||||
- 使用`boa_engine` 代替 `rquickjs`
|
|
||||||
- 支持 Linux armv7
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- Windows 首次安装无法点击
|
|
||||||
- Windows 触摸屏无法拖动
|
|
||||||
- 规则列表 `REJECT-DROP` 颜色
|
|
||||||
- MacOS Dock 栏不显示图标
|
|
||||||
- MacOS 自定义字体无效
|
|
||||||
- 避免使用空 UA 拉取订阅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.5.8
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 优化 UI 细节
|
|
||||||
- Linux 绘制窗口圆角
|
|
||||||
- 开放 DevTools
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- 修复 MacOS 下开启 Tun 内核崩溃的问题
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.5.7
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 优化 UI 各种细节
|
|
||||||
- 提供菜单栏图标样式切换选项(单色/彩色/禁用)
|
|
||||||
- 添加自动检查更新开关
|
|
||||||
- MacOS 开启 Tun 模式自动修改 DNS
|
|
||||||
- 调整可拖动区域(尝试修复触摸屏无法拖动的问题)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.5.6
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 全新专属 Verge rev UI 界面 (by @Amnesiash) 及细节调整
|
|
||||||
- 提供允许无效证书的开关
|
|
||||||
- 删除不必要的快捷键
|
|
||||||
- Provider 更新添加动画
|
|
||||||
- Merge 支持 Provider
|
|
||||||
- 更换订阅框的粘贴按钮,删除默认的"Remote File" Profile 名称
|
|
||||||
- 链接菜单添加节点显示
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- Linux 下图片显示错误
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.5.4
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 支持自定义托盘图标
|
|
||||||
- 支持禁用代理组图标
|
|
||||||
- 代理组显示当前代理
|
|
||||||
- 修改 `打开面板` 快捷键为`打开/关闭面板`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.5.3
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Tun 设置添加重置按钮
|
|
||||||
|
|
||||||
### Bugs Fixes
|
|
||||||
|
|
||||||
- Tun 设置项显示错误的问题
|
|
||||||
- 修改一些默认值
|
|
||||||
- 启动时不更改启动项设置
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.5.2
|
## v1.5.2
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
BIN
docs/preview.gif
Normal file
After Width: | Height: | Size: 6.7 MiB |
Before Width: | Height: | Size: 314 KiB |
Before Width: | Height: | Size: 274 KiB |
131
package.json
@ -1,113 +1,80 @@
|
|||||||
{
|
{
|
||||||
"name": "clash-verge",
|
"name": "clash-verge",
|
||||||
"version": "2.2.3",
|
"version": "1.5.2",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev -- --profile fast-dev",
|
"dev": "tauri dev",
|
||||||
"dev:diff": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev -- --profile fast-dev",
|
"dev:diff": "tauri dev -f verge-dev",
|
||||||
"build": "cross-env NODE_OPTIONS='--max-old-space-size=4096' tauri build",
|
"build": "tauri build",
|
||||||
"build:fast": "cross-env NODE_OPTIONS='--max-old-space-size=4096' tauri build -- --profile fast-release",
|
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"web:dev": "vite",
|
"web:dev": "vite",
|
||||||
"web:build": "tsc --noEmit && vite build",
|
"web:build": "tsc && vite build",
|
||||||
"web:serve": "vite preview",
|
"web:serve": "vite preview",
|
||||||
"check": "node scripts/check.mjs",
|
"check": "node scripts/check.mjs",
|
||||||
"updater": "node scripts/updater.mjs",
|
"updater": "node scripts/updater.mjs",
|
||||||
"updater-fixed-webview2": "node scripts/updater-fixed-webview2.mjs",
|
|
||||||
"portable": "node scripts/portable.mjs",
|
"portable": "node scripts/portable.mjs",
|
||||||
"portable-fixed-webview2": "node scripts/portable-fixed-webview2.mjs",
|
"prepare": "husky install"
|
||||||
"fix-alpha-version": "node scripts/fix-alpha_version.mjs",
|
|
||||||
"release-version": "node scripts/release_version.mjs",
|
|
||||||
"release-alpha-version": "node scripts/release-alpha_version.mjs",
|
|
||||||
"prepare": "husky",
|
|
||||||
"clippy": "cargo clippy --manifest-path ./src-tauri/Cargo.toml"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.1.0",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.11.3",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@juggle/resize-observer": "^3.4.0",
|
"@juggle/resize-observer": "^3.4.0",
|
||||||
"@mui/icons-material": "^6.4.8",
|
"@mui/icons-material": "^5.15.5",
|
||||||
"@mui/lab": "6.0.0-beta.25",
|
"@mui/lab": "5.0.0-alpha.149",
|
||||||
"@mui/material": "^6.4.8",
|
"@mui/material": "^5.15.5",
|
||||||
"@mui/x-data-grid": "^7.28.0",
|
"@mui/x-data-grid": "^6.18.7",
|
||||||
"@tauri-apps/api": "2.2.0",
|
"@tauri-apps/api": "^1.5.3",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "^2.2.2",
|
"ahooks": "^3.7.8",
|
||||||
"@tauri-apps/plugin-dialog": "^2.2.0",
|
"axios": "^1.6.5",
|
||||||
"@tauri-apps/plugin-fs": "^2.2.0",
|
"dayjs": "1.11.5",
|
||||||
"@tauri-apps/plugin-global-shortcut": "^2.2.0",
|
"i18next": "^23.7.16",
|
||||||
"@tauri-apps/plugin-notification": "^2.2.2",
|
|
||||||
"@tauri-apps/plugin-process": "^2.2.0",
|
|
||||||
"@tauri-apps/plugin-shell": "2.2.0",
|
|
||||||
"@tauri-apps/plugin-updater": "2.3.0",
|
|
||||||
"@types/d3-shape": "^3.1.7",
|
|
||||||
"@types/json-schema": "^7.0.15",
|
|
||||||
"ahooks": "^3.8.4",
|
|
||||||
"axios": "^1.8.3",
|
|
||||||
"cli-color": "^2.0.4",
|
|
||||||
"d3-shape": "^3.2.0",
|
|
||||||
"dayjs": "1.11.13",
|
|
||||||
"foxact": "^0.2.44",
|
|
||||||
"glob": "^11.0.1",
|
|
||||||
"i18next": "^24.2.3",
|
|
||||||
"js-base64": "^3.7.7",
|
|
||||||
"js-yaml": "^4.1.0",
|
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"monaco-editor": "^0.52.2",
|
"monaco-editor": "^0.34.1",
|
||||||
"monaco-yaml": "^5.3.1",
|
"nanoid": "^5.0.4",
|
||||||
"nanoid": "^5.1.5",
|
"react": "^18.2.0",
|
||||||
"peggy": "^4.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react": "^18.3.1",
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-dom": "^18.3.1",
|
"react-hook-form": "^7.49.3",
|
||||||
"react-error-boundary": "^4.1.2",
|
|
||||||
"react-hook-form": "^7.54.2",
|
|
||||||
"react-i18next": "^13.5.0",
|
"react-i18next": "^13.5.0",
|
||||||
"react-markdown": "^9.1.0",
|
"react-router-dom": "^6.21.2",
|
||||||
"react-monaco-editor": "^0.56.2",
|
|
||||||
"react-router-dom": "^6.30.0",
|
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"react-virtuoso": "^4.12.5",
|
"react-virtuoso": "^4.6.2",
|
||||||
"recharts": "^2.15.1",
|
"recoil": "^0.7.7",
|
||||||
"sockette": "^2.0.6",
|
"snarkdown": "^2.0.0",
|
||||||
"swr": "^2.3.3",
|
"swr": "^1.3.0",
|
||||||
"tar": "^7.4.3",
|
"tar": "^6.2.0"
|
||||||
"types-pac": "^1.0.3",
|
|
||||||
"zustand": "^5.0.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/github": "^6.0.0",
|
"@actions/github": "^5.1.1",
|
||||||
"@tauri-apps/cli": "2.2.7",
|
"@tauri-apps/cli": "^1.5.9",
|
||||||
|
"@types/fs-extra": "^9.0.13",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/js-yaml": "^4.0.9",
|
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/react": "^18.3.18",
|
"@types/react": "^18.2.48",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.2.18",
|
||||||
"@types/react-transition-group": "^4.4.12",
|
"@types/react-transition-group": "^4.4.10",
|
||||||
"@vitejs/plugin-legacy": "^6.0.2",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"adm-zip": "^0.5.10",
|
||||||
"adm-zip": "^0.5.16",
|
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"fs-extra": "^11.2.0",
|
||||||
"husky": "^9.1.7",
|
"https-proxy-agent": "^5.0.1",
|
||||||
"meta-json-schema": "^1.19.3",
|
"husky": "^7.0.4",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^2.8.8",
|
||||||
"pretty-quick": "^4.1.1",
|
"pretty-quick": "^3.3.1",
|
||||||
"sass": "^1.86.0",
|
"sass": "^1.70.0",
|
||||||
"terser": "^5.39.0",
|
"typescript": "^5.3.3",
|
||||||
"typescript": "^5.8.2",
|
"vite": "^5.0.11",
|
||||||
"vite": "^6.2.2",
|
|
||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vite-plugin-svgr": "^4.3.0"
|
"vite-plugin-svgr": "^4.2.0"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"endOfLine": "lf"
|
"endOfLine": "lf"
|
||||||
},
|
}
|
||||||
"type": "module",
|
|
||||||
"packageManager": "pnpm@9.13.2"
|
|
||||||
}
|
}
|
||||||
|
195
patches/support-windows-aarch64.patch
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
From 871c9a6d1ed014c93da2436a437df03734e9f76c Mon Sep 17 00:00:00 2001
|
||||||
|
From: MystiPanda <mystipanda@proton.me>
|
||||||
|
Date: Sun, 10 Dec 2023 19:47:45 +0800
|
||||||
|
Subject: [PATCH] feat: Support windows aarch64
|
||||||
|
|
||||||
|
---
|
||||||
|
.gitmodules | 3 +
|
||||||
|
src-tauri/Cargo.toml | 2 +-
|
||||||
|
src-tauri/quick-rs | 1 +
|
||||||
|
src-tauri/src/enhance/script.rs | 130 +++++++++++++++++++-------------
|
||||||
|
4 files changed, 81 insertions(+), 55 deletions(-)
|
||||||
|
create mode 100644 .gitmodules
|
||||||
|
create mode 160000 src-tauri/quick-rs
|
||||||
|
|
||||||
|
diff --git a/.gitmodules b/.gitmodules
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..2eda7e4
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/.gitmodules
|
||||||
|
@@ -0,0 +1,3 @@
|
||||||
|
+[submodule "src-tauri/quick-rs"]
|
||||||
|
+ path = src-tauri/quick-rs
|
||||||
|
+ url = https://github.com/clash-verge-rev/quick-rs.git
|
||||||
|
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
|
||||||
|
index 2f1a3be..d67f6ed 100644
|
||||||
|
--- a/src-tauri/Cargo.toml
|
||||||
|
+++ b/src-tauri/Cargo.toml
|
||||||
|
@@ -25,7 +25,6 @@ log4rs = "1"
|
||||||
|
nanoid = "0.4"
|
||||||
|
chrono = "0.4"
|
||||||
|
sysinfo = "0.30"
|
||||||
|
-rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde_yaml = "0.9"
|
||||||
|
once_cell = "1.18"
|
||||||
|
@@ -33,6 +32,7 @@ port_scanner = "0.1.5"
|
||||||
|
delay_timer = "0.11.5"
|
||||||
|
parking_lot = "0.12"
|
||||||
|
percent-encoding = "2.3.1"
|
||||||
|
+quick-rs = { path = "quick-rs" }
|
||||||
|
window-shadows = { version = "0.2" }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
diff --git a/src-tauri/quick-rs b/src-tauri/quick-rs
|
||||||
|
new file mode 160000
|
||||||
|
index 0000000..78277c4
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src-tauri/quick-rs
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+Subproject commit 78277c4509c64f18c0fc5c9f2b84671de7c83343
|
||||||
|
diff --git a/src-tauri/src/enhance/script.rs b/src-tauri/src/enhance/script.rs
|
||||||
|
index 30a922f..d47dc33 100644
|
||||||
|
--- a/src-tauri/src/enhance/script.rs
|
||||||
|
+++ b/src-tauri/src/enhance/script.rs
|
||||||
|
@@ -3,61 +3,83 @@ use anyhow::Result;
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
|
||||||
|
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
|
||||||
|
- use rquickjs::{function::Func, Context, Runtime};
|
||||||
|
- use std::sync::{Arc, Mutex};
|
||||||
|
-
|
||||||
|
- let runtime = Runtime::new().unwrap();
|
||||||
|
- let context = Context::full(&runtime).unwrap();
|
||||||
|
- let outputs = Arc::new(Mutex::new(vec![]));
|
||||||
|
-
|
||||||
|
- let copy_outputs = outputs.clone();
|
||||||
|
- let result = context.with(|ctx| -> Result<Mapping> {
|
||||||
|
- ctx.globals().set(
|
||||||
|
- "__verge_log__",
|
||||||
|
- Func::from(move |level: String, data: String| {
|
||||||
|
- let mut out = copy_outputs.lock().unwrap();
|
||||||
|
- out.push((level, data));
|
||||||
|
- }),
|
||||||
|
- )?;
|
||||||
|
-
|
||||||
|
- ctx.eval(
|
||||||
|
- r#"var console = Object.freeze({
|
||||||
|
- log(data){__verge_log__("log",JSON.stringify(data))},
|
||||||
|
- info(data){__verge_log__("info",JSON.stringify(data))},
|
||||||
|
- error(data){__verge_log__("error",JSON.stringify(data))},
|
||||||
|
- debug(data){__verge_log__("debug",JSON.stringify(data))},
|
||||||
|
- });"#,
|
||||||
|
- )?;
|
||||||
|
-
|
||||||
|
- let config = use_lowercase(config.clone());
|
||||||
|
- let config_str = serde_json::to_string(&config)?;
|
||||||
|
-
|
||||||
|
- let code = format!(
|
||||||
|
- r#"try{{
|
||||||
|
+ use quick_rs::{context::Context, function::Function, module::Module, runtime::Runtime};
|
||||||
|
+
|
||||||
|
+ let config = use_lowercase(config.clone());
|
||||||
|
+ let config_str = serde_json::to_string(&config)?;
|
||||||
|
+
|
||||||
|
+ let runtime = Runtime::new();
|
||||||
|
+ let context = Context::from(&runtime);
|
||||||
|
+
|
||||||
|
+ let code = format!(
|
||||||
|
+ r#"
|
||||||
|
+ let output = [];
|
||||||
|
+
|
||||||
|
+ function __verge_log__(type, data) {{
|
||||||
|
+ output.push([type, data]);
|
||||||
|
+ }}
|
||||||
|
+
|
||||||
|
+ var console = Object.freeze({{
|
||||||
|
+ log(data) {{ __verge_log__("log", JSON.stringify(data)) }},
|
||||||
|
+ info(data) {{ __verge_log__("info", JSON.stringify(data)) }},
|
||||||
|
+ error(data) {{ __verge_log__("error", JSON.stringify(data)) }},
|
||||||
|
+ debug(data) {{ __verge_log__("debug", JSON.stringify(data)) }},
|
||||||
|
+ }});
|
||||||
|
+
|
||||||
|
{script};
|
||||||
|
- JSON.stringify(main({config_str})||'')
|
||||||
|
- }} catch(err) {{
|
||||||
|
- `__error_flag__ ${{err.toString()}}`
|
||||||
|
- }}"#
|
||||||
|
- );
|
||||||
|
- let result: String = ctx.eval(code.as_str())?;
|
||||||
|
- if result.starts_with("__error_flag__") {
|
||||||
|
- anyhow::bail!(result[15..].to_owned());
|
||||||
|
- }
|
||||||
|
- if result == "\"\"" {
|
||||||
|
- anyhow::bail!("main function should return object");
|
||||||
|
- }
|
||||||
|
- Ok(serde_json::from_str::<Mapping>(result.as_str())?)
|
||||||
|
- });
|
||||||
|
-
|
||||||
|
- let mut out = outputs.lock().unwrap();
|
||||||
|
- match result {
|
||||||
|
- Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||||
|
- Err(err) => {
|
||||||
|
- out.push(("exception".into(), err.to_string()));
|
||||||
|
- Ok((config, out.to_vec()))
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
+
|
||||||
|
+ export function _main(){{
|
||||||
|
+ try{{
|
||||||
|
+ let result = JSON.stringify(main({config_str})||"");
|
||||||
|
+ return JSON.stringify({{result, output}});
|
||||||
|
+ }} catch(err) {{
|
||||||
|
+ output.push(["exception", err.toString()]);
|
||||||
|
+ return JSON.stringify({{result: "__error__", output}});
|
||||||
|
+ }}
|
||||||
|
+ }}
|
||||||
|
+ "#
|
||||||
|
+ );
|
||||||
|
+ let value = context.eval_module(&code, "_main")?;
|
||||||
|
+ let module = Module::new(value)?;
|
||||||
|
+ let value = module.get("_main")?;
|
||||||
|
+ let function = Function::new(value)?;
|
||||||
|
+ let value = function.call(vec![])?;
|
||||||
|
+ let result = serde_json::from_str::<serde_json::Value>(&value.to_string()?)?;
|
||||||
|
+ result
|
||||||
|
+ .as_object()
|
||||||
|
+ .map(|obj| {
|
||||||
|
+ let result = obj.get("result").unwrap().as_str().unwrap();
|
||||||
|
+ let output = obj.get("output").unwrap();
|
||||||
|
+
|
||||||
|
+ let mut out = output
|
||||||
|
+ .as_array()
|
||||||
|
+ .unwrap()
|
||||||
|
+ .iter()
|
||||||
|
+ .map(|item| {
|
||||||
|
+ let item = item.as_array().unwrap();
|
||||||
|
+ (
|
||||||
|
+ item[0].as_str().unwrap().into(),
|
||||||
|
+ item[1].as_str().unwrap().into(),
|
||||||
|
+ )
|
||||||
|
+ })
|
||||||
|
+ .collect::<Vec<_>>();
|
||||||
|
+ if result.is_empty() {
|
||||||
|
+ anyhow::bail!("main function should return object");
|
||||||
|
+ }
|
||||||
|
+ if result == "__error__" {
|
||||||
|
+ return Ok((config, out.to_vec()));
|
||||||
|
+ }
|
||||||
|
+ let result = serde_json::from_str::<Mapping>(result);
|
||||||
|
+
|
||||||
|
+ match result {
|
||||||
|
+ Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||||
|
+ Err(err) => {
|
||||||
|
+ out.push(("exception".into(), err.to_string()));
|
||||||
|
+ Ok((config, out.to_vec()))
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ })
|
||||||
|
+ .unwrap_or_else(|| anyhow::bail!("Unknown result"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
--
|
||||||
|
2.43.0.windows.1
|
||||||
|
|
9542
pnpm-lock.yaml
generated
@ -1,14 +1,11 @@
|
|||||||
import fs from "fs";
|
import fs from "fs-extra";
|
||||||
import fsp from "fs/promises";
|
|
||||||
import zlib from "zlib";
|
import zlib from "zlib";
|
||||||
import { extract } from "tar";
|
import tar from "tar";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import AdmZip from "adm-zip";
|
import AdmZip from "adm-zip";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
import proxyAgent from "https-proxy-agent";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import { log_info, log_debug, log_error, log_success } from "./utils.mjs";
|
|
||||||
import { glob } from "glob";
|
|
||||||
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
const TEMP_DIR = path.join(cwd, "node_modules/.verge");
|
const TEMP_DIR = path.join(cwd, "node_modules/.verge");
|
||||||
@ -16,28 +13,22 @@ const FORCE = process.argv.includes("--force");
|
|||||||
|
|
||||||
const PLATFORM_MAP = {
|
const PLATFORM_MAP = {
|
||||||
"x86_64-pc-windows-msvc": "win32",
|
"x86_64-pc-windows-msvc": "win32",
|
||||||
"i686-pc-windows-msvc": "win32",
|
|
||||||
"aarch64-pc-windows-msvc": "win32",
|
"aarch64-pc-windows-msvc": "win32",
|
||||||
"x86_64-apple-darwin": "darwin",
|
"x86_64-apple-darwin": "darwin",
|
||||||
"aarch64-apple-darwin": "darwin",
|
"aarch64-apple-darwin": "darwin",
|
||||||
"x86_64-unknown-linux-gnu": "linux",
|
"x86_64-unknown-linux-gnu": "linux",
|
||||||
"i686-unknown-linux-gnu": "linux",
|
|
||||||
"aarch64-unknown-linux-gnu": "linux",
|
"aarch64-unknown-linux-gnu": "linux",
|
||||||
"armv7-unknown-linux-gnueabihf": "linux",
|
"armv7-unknown-linux-gnueabihf": "linux",
|
||||||
"riscv64gc-unknown-linux-gnu": "linux",
|
|
||||||
"loongarch64-unknown-linux-gnu": "linux",
|
"loongarch64-unknown-linux-gnu": "linux",
|
||||||
};
|
};
|
||||||
const ARCH_MAP = {
|
const ARCH_MAP = {
|
||||||
"x86_64-pc-windows-msvc": "x64",
|
"x86_64-pc-windows-msvc": "x64",
|
||||||
"i686-pc-windows-msvc": "ia32",
|
|
||||||
"aarch64-pc-windows-msvc": "arm64",
|
"aarch64-pc-windows-msvc": "arm64",
|
||||||
"x86_64-apple-darwin": "x64",
|
"x86_64-apple-darwin": "x64",
|
||||||
"aarch64-apple-darwin": "arm64",
|
"aarch64-apple-darwin": "arm64",
|
||||||
"x86_64-unknown-linux-gnu": "x64",
|
"x86_64-unknown-linux-gnu": "x64",
|
||||||
"i686-unknown-linux-gnu": "ia32",
|
|
||||||
"aarch64-unknown-linux-gnu": "arm64",
|
"aarch64-unknown-linux-gnu": "arm64",
|
||||||
"armv7-unknown-linux-gnueabihf": "arm",
|
"armv7-unknown-linux-gnueabihf": "arm",
|
||||||
"riscv64gc-unknown-linux-gnu": "riscv64",
|
|
||||||
"loongarch64-unknown-linux-gnu": "loong64",
|
"loongarch64-unknown-linux-gnu": "loong64",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -62,15 +53,12 @@ let META_ALPHA_VERSION;
|
|||||||
|
|
||||||
const META_ALPHA_MAP = {
|
const META_ALPHA_MAP = {
|
||||||
"win32-x64": "mihomo-windows-amd64-compatible",
|
"win32-x64": "mihomo-windows-amd64-compatible",
|
||||||
"win32-ia32": "mihomo-windows-386",
|
|
||||||
"win32-arm64": "mihomo-windows-arm64",
|
"win32-arm64": "mihomo-windows-arm64",
|
||||||
"darwin-x64": "mihomo-darwin-amd64-compatible",
|
"darwin-x64": "mihomo-darwin-amd64",
|
||||||
"darwin-arm64": "mihomo-darwin-arm64",
|
"darwin-arm64": "mihomo-darwin-arm64",
|
||||||
"linux-x64": "mihomo-linux-amd64-compatible",
|
"linux-x64": "mihomo-linux-amd64-compatible",
|
||||||
"linux-ia32": "mihomo-linux-386",
|
|
||||||
"linux-arm64": "mihomo-linux-arm64",
|
"linux-arm64": "mihomo-linux-arm64",
|
||||||
"linux-arm": "mihomo-linux-armv7",
|
"linux-arm": "mihomo-linux-armv7",
|
||||||
"linux-riscv64": "mihomo-linux-riscv64",
|
|
||||||
"linux-loong64": "mihomo-linux-loong64",
|
"linux-loong64": "mihomo-linux-loong64",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,7 +73,7 @@ async function getLatestAlphaVersion() {
|
|||||||
process.env.https_proxy;
|
process.env.https_proxy;
|
||||||
|
|
||||||
if (httpProxy) {
|
if (httpProxy) {
|
||||||
options.agent = new HttpsProxyAgent(httpProxy);
|
options.agent = proxyAgent(httpProxy);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch(META_ALPHA_VERSION_URL, {
|
const response = await fetch(META_ALPHA_VERSION_URL, {
|
||||||
@ -94,9 +82,9 @@ async function getLatestAlphaVersion() {
|
|||||||
});
|
});
|
||||||
let v = await response.text();
|
let v = await response.text();
|
||||||
META_ALPHA_VERSION = v.trim(); // Trim to remove extra whitespaces
|
META_ALPHA_VERSION = v.trim(); // Trim to remove extra whitespaces
|
||||||
log_info(`Latest alpha version: ${META_ALPHA_VERSION}`);
|
console.log(`Latest alpha version: ${META_ALPHA_VERSION}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log_error("Error fetching latest alpha version:", error.message);
|
console.error("Error fetching latest alpha version:", error.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,15 +97,12 @@ let META_VERSION;
|
|||||||
|
|
||||||
const META_MAP = {
|
const META_MAP = {
|
||||||
"win32-x64": "mihomo-windows-amd64-compatible",
|
"win32-x64": "mihomo-windows-amd64-compatible",
|
||||||
"win32-ia32": "mihomo-windows-386",
|
|
||||||
"win32-arm64": "mihomo-windows-arm64",
|
"win32-arm64": "mihomo-windows-arm64",
|
||||||
"darwin-x64": "mihomo-darwin-amd64-compatible",
|
"darwin-x64": "mihomo-darwin-amd64",
|
||||||
"darwin-arm64": "mihomo-darwin-arm64",
|
"darwin-arm64": "mihomo-darwin-arm64",
|
||||||
"linux-x64": "mihomo-linux-amd64-compatible",
|
"linux-x64": "mihomo-linux-amd64-compatible",
|
||||||
"linux-ia32": "mihomo-linux-386",
|
|
||||||
"linux-arm64": "mihomo-linux-arm64",
|
"linux-arm64": "mihomo-linux-arm64",
|
||||||
"linux-arm": "mihomo-linux-armv7",
|
"linux-arm": "mihomo-linux-armv7",
|
||||||
"linux-riscv64": "mihomo-linux-riscv64",
|
|
||||||
"linux-loong64": "mihomo-linux-loong64",
|
"linux-loong64": "mihomo-linux-loong64",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,7 +117,7 @@ async function getLatestReleaseVersion() {
|
|||||||
process.env.https_proxy;
|
process.env.https_proxy;
|
||||||
|
|
||||||
if (httpProxy) {
|
if (httpProxy) {
|
||||||
options.agent = new HttpsProxyAgent(httpProxy);
|
options.agent = proxyAgent(httpProxy);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch(META_VERSION_URL, {
|
const response = await fetch(META_VERSION_URL, {
|
||||||
@ -141,9 +126,9 @@ async function getLatestReleaseVersion() {
|
|||||||
});
|
});
|
||||||
let v = await response.text();
|
let v = await response.text();
|
||||||
META_VERSION = v.trim(); // Trim to remove extra whitespaces
|
META_VERSION = v.trim(); // Trim to remove extra whitespaces
|
||||||
log_info(`Latest release version: ${META_VERSION}`);
|
console.log(`Latest release version: ${META_VERSION}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log_error("Error fetching latest release version:", error.message);
|
console.error("Error fetching latest release version:", error.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,13 +138,13 @@ async function getLatestReleaseVersion() {
|
|||||||
*/
|
*/
|
||||||
if (!META_MAP[`${platform}-${arch}`]) {
|
if (!META_MAP[`${platform}-${arch}`]) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`clash meta alpha unsupported platform "${platform}-${arch}"`,
|
`clash meta alpha unsupported platform "${platform}-${arch}"`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!META_ALPHA_MAP[`${platform}-${arch}`]) {
|
if (!META_ALPHA_MAP[`${platform}-${arch}`]) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`clash meta alpha unsupported platform "${platform}-${arch}"`,
|
`clash meta alpha unsupported platform "${platform}-${arch}"`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,8 +160,8 @@ function clashMetaAlpha() {
|
|||||||
const zipFile = `${name}-${META_ALPHA_VERSION}.${urlExt}`;
|
const zipFile = `${name}-${META_ALPHA_VERSION}.${urlExt}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "verge-mihomo-alpha",
|
name: "clash-meta-alpha",
|
||||||
targetFile: `verge-mihomo-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
targetFile: `clash-meta-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
||||||
exeFile,
|
exeFile,
|
||||||
zipFile,
|
zipFile,
|
||||||
downloadURL,
|
downloadURL,
|
||||||
@ -192,8 +177,8 @@ function clashMeta() {
|
|||||||
const zipFile = `${name}-${META_VERSION}.${urlExt}`;
|
const zipFile = `${name}-${META_VERSION}.${urlExt}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "verge-mihomo",
|
name: "clash-meta",
|
||||||
targetFile: `verge-mihomo-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
targetFile: `clash-meta-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
||||||
exeFile,
|
exeFile,
|
||||||
zipFile,
|
zipFile,
|
||||||
downloadURL,
|
downloadURL,
|
||||||
@ -208,44 +193,44 @@ async function resolveSidecar(binInfo) {
|
|||||||
const sidecarDir = path.join(cwd, "src-tauri", "sidecar");
|
const sidecarDir = path.join(cwd, "src-tauri", "sidecar");
|
||||||
const sidecarPath = path.join(sidecarDir, targetFile);
|
const sidecarPath = path.join(sidecarDir, targetFile);
|
||||||
|
|
||||||
await fsp.mkdir(sidecarDir, { recursive: true });
|
await fs.mkdirp(sidecarDir);
|
||||||
if (!FORCE && fs.existsSync(sidecarPath)) return;
|
if (!FORCE && (await fs.pathExists(sidecarPath))) return;
|
||||||
|
|
||||||
const tempDir = path.join(TEMP_DIR, name);
|
const tempDir = path.join(TEMP_DIR, name);
|
||||||
const tempZip = path.join(tempDir, zipFile);
|
const tempZip = path.join(tempDir, zipFile);
|
||||||
const tempExe = path.join(tempDir, exeFile);
|
const tempExe = path.join(tempDir, exeFile);
|
||||||
|
|
||||||
await fsp.mkdir(tempDir, { recursive: true });
|
await fs.mkdirp(tempDir);
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(tempZip)) {
|
if (!(await fs.pathExists(tempZip))) {
|
||||||
await downloadFile(downloadURL, tempZip);
|
await downloadFile(downloadURL, tempZip);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zipFile.endsWith(".zip")) {
|
if (zipFile.endsWith(".zip")) {
|
||||||
const zip = new AdmZip(tempZip);
|
const zip = new AdmZip(tempZip);
|
||||||
zip.getEntries().forEach((entry) => {
|
zip.getEntries().forEach((entry) => {
|
||||||
log_debug(`"${name}" entry name`, entry.entryName);
|
console.log(`[DEBUG]: "${name}" entry name`, entry.entryName);
|
||||||
});
|
});
|
||||||
zip.extractAllTo(tempDir, true);
|
zip.extractAllTo(tempDir, true);
|
||||||
await fsp.rename(tempExe, sidecarPath);
|
await fs.rename(tempExe, sidecarPath);
|
||||||
log_success(`unzip finished: "${name}"`);
|
console.log(`[INFO]: "${name}" unzip finished`);
|
||||||
} else if (zipFile.endsWith(".tgz")) {
|
} else if (zipFile.endsWith(".tgz")) {
|
||||||
// tgz
|
// tgz
|
||||||
await fsp.mkdir(tempDir, { recursive: true });
|
await fs.mkdirp(tempDir);
|
||||||
await extract({
|
await tar.extract({
|
||||||
cwd: tempDir,
|
cwd: tempDir,
|
||||||
file: tempZip,
|
file: tempZip,
|
||||||
//strip: 1, // 可能需要根据实际的 .tgz 文件结构调整
|
//strip: 1, // 可能需要根据实际的 .tgz 文件结构调整
|
||||||
});
|
});
|
||||||
const files = await fsp.readdir(tempDir);
|
const files = await fs.readdir(tempDir);
|
||||||
log_debug(`"${name}" files in tempDir:`, files);
|
console.log(`[DEBUG]: "${name}" files in tempDir:`, files);
|
||||||
const extractedFile = files.find((file) => file.startsWith("虚空终端-"));
|
const extractedFile = files.find((file) => file.startsWith("虚空终端-"));
|
||||||
if (extractedFile) {
|
if (extractedFile) {
|
||||||
const extractedFilePath = path.join(tempDir, extractedFile);
|
const extractedFilePath = path.join(tempDir, extractedFile);
|
||||||
await fsp.rename(extractedFilePath, sidecarPath);
|
await fs.rename(extractedFilePath, sidecarPath);
|
||||||
log_success(`"${name}" file renamed to "${sidecarPath}"`);
|
console.log(`[INFO]: "${name}" file renamed to "${sidecarPath}"`);
|
||||||
execSync(`chmod 755 ${sidecarPath}`);
|
execSync(`chmod 755 ${sidecarPath}`);
|
||||||
log_success(`chmod binary finished: "${name}"`);
|
console.log(`[INFO]: "${name}" chmod binary finished`);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Expected file not found in ${tempDir}`);
|
throw new Error(`Expected file not found in ${tempDir}`);
|
||||||
}
|
}
|
||||||
@ -255,15 +240,16 @@ async function resolveSidecar(binInfo) {
|
|||||||
const writeStream = fs.createWriteStream(sidecarPath);
|
const writeStream = fs.createWriteStream(sidecarPath);
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
const onError = (error) => {
|
const onError = (error) => {
|
||||||
log_error(`"${name}" gz failed:`, error.message);
|
console.error(`[ERROR]: "${name}" gz failed:`, error.message);
|
||||||
reject(error);
|
reject(error);
|
||||||
};
|
};
|
||||||
readStream
|
readStream
|
||||||
.pipe(zlib.createGunzip().on("error", onError))
|
.pipe(zlib.createGunzip().on("error", onError))
|
||||||
.pipe(writeStream)
|
.pipe(writeStream)
|
||||||
.on("finish", () => {
|
.on("finish", () => {
|
||||||
|
console.log(`[INFO]: "${name}" gunzip finished`);
|
||||||
execSync(`chmod 755 ${sidecarPath}`);
|
execSync(`chmod 755 ${sidecarPath}`);
|
||||||
log_success(`chmod binary finished: "${name}"`);
|
console.log(`[INFO]: "${name}" chmod binary finished`);
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.on("error", onError);
|
.on("error", onError);
|
||||||
@ -271,58 +257,35 @@ async function resolveSidecar(binInfo) {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// 需要删除文件
|
// 需要删除文件
|
||||||
await fsp.rm(sidecarPath, { recursive: true, force: true });
|
await fs.remove(sidecarPath);
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
// delete temp dir
|
// delete temp dir
|
||||||
await fsp.rm(tempDir, { recursive: true, force: true });
|
await fs.remove(tempDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolveSetDnsScript = () =>
|
|
||||||
resolveResource({
|
|
||||||
file: "set_dns.sh",
|
|
||||||
localPath: path.join(cwd, "scripts/set_dns.sh"),
|
|
||||||
});
|
|
||||||
const resolveUnSetDnsScript = () =>
|
|
||||||
resolveResource({
|
|
||||||
file: "unset_dns.sh",
|
|
||||||
localPath: path.join(cwd, "scripts/unset_dns.sh"),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* download the file to the resources dir
|
* download the file to the resources dir
|
||||||
*/
|
*/
|
||||||
async function resolveResource(binInfo) {
|
async function resolveResource(binInfo) {
|
||||||
const { file, downloadURL, localPath } = binInfo;
|
const { file, downloadURL } = binInfo;
|
||||||
|
|
||||||
const resDir = path.join(cwd, "src-tauri/resources");
|
const resDir = path.join(cwd, "src-tauri/resources");
|
||||||
const targetPath = path.join(resDir, file);
|
const targetPath = path.join(resDir, file);
|
||||||
|
|
||||||
if (!FORCE && fs.existsSync(targetPath)) return;
|
if (!FORCE && (await fs.pathExists(targetPath))) return;
|
||||||
|
|
||||||
if (downloadURL) {
|
await fs.mkdirp(resDir);
|
||||||
await fsp.mkdir(resDir, { recursive: true });
|
await downloadFile(downloadURL, targetPath);
|
||||||
await downloadFile(downloadURL, targetPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localPath) {
|
console.log(`[INFO]: ${file} finished`);
|
||||||
await fs.copyFile(localPath, targetPath, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error("Error copying file:", err);
|
|
||||||
} else {
|
|
||||||
console.log("File was copied successfully");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
log_debug(`copy file finished: "${localPath}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
log_success(`${file} finished`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* download file and save to `path`
|
* download file and save to `path`
|
||||||
*/ async function downloadFile(url, path) {
|
*/
|
||||||
|
async function downloadFile(url, path) {
|
||||||
const options = {};
|
const options = {};
|
||||||
|
|
||||||
const httpProxy =
|
const httpProxy =
|
||||||
@ -332,7 +295,7 @@ async function resolveResource(binInfo) {
|
|||||||
process.env.https_proxy;
|
process.env.https_proxy;
|
||||||
|
|
||||||
if (httpProxy) {
|
if (httpProxy) {
|
||||||
options.agent = new HttpsProxyAgent(httpProxy);
|
options.agent = proxyAgent(httpProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
@ -341,9 +304,9 @@ async function resolveResource(binInfo) {
|
|||||||
headers: { "Content-Type": "application/octet-stream" },
|
headers: { "Content-Type": "application/octet-stream" },
|
||||||
});
|
});
|
||||||
const buffer = await response.arrayBuffer();
|
const buffer = await response.arrayBuffer();
|
||||||
await fsp.writeFile(path, new Uint8Array(buffer));
|
await fs.writeFile(path, new Uint8Array(buffer));
|
||||||
|
|
||||||
log_success(`download finished: ${url}`);
|
console.log(`[INFO]: download finished "${url}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SimpleSC.dll
|
// SimpleSC.dll
|
||||||
@ -354,111 +317,51 @@ const resolvePlugin = async () => {
|
|||||||
const tempDir = path.join(TEMP_DIR, "SimpleSC");
|
const tempDir = path.join(TEMP_DIR, "SimpleSC");
|
||||||
const tempZip = path.join(
|
const tempZip = path.join(
|
||||||
tempDir,
|
tempDir,
|
||||||
"NSIS_Simple_Service_Plugin_Unicode_1.30.zip",
|
"NSIS_Simple_Service_Plugin_Unicode_1.30.zip"
|
||||||
);
|
);
|
||||||
const tempDll = path.join(tempDir, "SimpleSC.dll");
|
const tempDll = path.join(tempDir, "SimpleSC.dll");
|
||||||
const pluginDir = path.join(process.env.APPDATA, "Local/NSIS");
|
const pluginDir = path.join(process.env.APPDATA, "Local/NSIS");
|
||||||
const pluginPath = path.join(pluginDir, "SimpleSC.dll");
|
const pluginPath = path.join(pluginDir, "SimpleSC.dll");
|
||||||
await fsp.mkdir(pluginDir, { recursive: true });
|
await fs.mkdirp(pluginDir);
|
||||||
await fsp.mkdir(tempDir, { recursive: true });
|
await fs.mkdirp(tempDir);
|
||||||
if (!FORCE && fs.existsSync(pluginPath)) return;
|
if (!FORCE && (await fs.pathExists(pluginPath))) return;
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(tempZip)) {
|
if (!(await fs.pathExists(tempZip))) {
|
||||||
await downloadFile(url, tempZip);
|
await downloadFile(url, tempZip);
|
||||||
}
|
}
|
||||||
const zip = new AdmZip(tempZip);
|
const zip = new AdmZip(tempZip);
|
||||||
zip.getEntries().forEach((entry) => {
|
zip.getEntries().forEach((entry) => {
|
||||||
log_debug(`"SimpleSC" entry name`, entry.entryName);
|
console.log(`[DEBUG]: "SimpleSC" entry name`, entry.entryName);
|
||||||
});
|
});
|
||||||
zip.extractAllTo(tempDir, true);
|
zip.extractAllTo(tempDir, true);
|
||||||
await fsp.cp(tempDll, pluginPath, { recursive: true, force: true });
|
await fs.copyFile(tempDll, pluginPath);
|
||||||
log_success(`unzip finished: "SimpleSC"`);
|
console.log(`[INFO]: "SimpleSC" unzip finished`);
|
||||||
} finally {
|
} finally {
|
||||||
await fsp.rm(tempDir, { recursive: true, force: true });
|
await fs.remove(tempDir);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// service chmod
|
|
||||||
const resolveServicePermission = async () => {
|
|
||||||
const serviceExecutables = [
|
|
||||||
"clash-verge-service*",
|
|
||||||
"install-service*",
|
|
||||||
"uninstall-service*",
|
|
||||||
];
|
|
||||||
const resDir = path.join(cwd, "src-tauri/resources");
|
|
||||||
for (let f of serviceExecutables) {
|
|
||||||
// 使用glob模块来处理通配符
|
|
||||||
const files = glob.sync(path.join(resDir, f));
|
|
||||||
for (let filePath of files) {
|
|
||||||
if (fs.existsSync(filePath)) {
|
|
||||||
execSync(`chmod 755 ${filePath}`);
|
|
||||||
log_success(`chmod finished: "${filePath}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 在 resolveResource 函数后添加新函数
|
|
||||||
async function resolveLocales() {
|
|
||||||
const srcLocalesDir = path.join(cwd, "src/locales");
|
|
||||||
const targetLocalesDir = path.join(cwd, "src-tauri/resources/locales");
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 确保目标目录存在
|
|
||||||
await fsp.mkdir(targetLocalesDir, { recursive: true });
|
|
||||||
|
|
||||||
// 读取所有语言文件
|
|
||||||
const files = await fsp.readdir(srcLocalesDir);
|
|
||||||
|
|
||||||
// 复制每个文件
|
|
||||||
for (const file of files) {
|
|
||||||
const srcPath = path.join(srcLocalesDir, file);
|
|
||||||
const targetPath = path.join(targetLocalesDir, file);
|
|
||||||
|
|
||||||
await fsp.copyFile(srcPath, targetPath);
|
|
||||||
log_success(`Copied locale file: ${file}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
log_success("All locale files copied successfully");
|
|
||||||
} catch (err) {
|
|
||||||
log_error("Error copying locale files:", err.message);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* main
|
* main
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`;
|
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`;
|
||||||
|
|
||||||
const resolveService = () => {
|
const resolveService = () =>
|
||||||
let ext = platform === "win32" ? ".exe" : "";
|
|
||||||
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
|
||||||
resolveResource({
|
resolveResource({
|
||||||
file: "clash-verge-service" + suffix + ext,
|
file: "clash-verge-service.exe",
|
||||||
downloadURL: `${SERVICE_URL}/clash-verge-service${ext}`,
|
downloadURL: `${SERVICE_URL}/clash-verge-service.exe`,
|
||||||
});
|
});
|
||||||
};
|
const resolveInstall = () =>
|
||||||
|
|
||||||
const resolveInstall = () => {
|
|
||||||
let ext = platform === "win32" ? ".exe" : "";
|
|
||||||
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
|
||||||
resolveResource({
|
resolveResource({
|
||||||
file: "install-service" + suffix + ext,
|
file: "install-service.exe",
|
||||||
downloadURL: `${SERVICE_URL}/install-service${ext}`,
|
downloadURL: `${SERVICE_URL}/install-service.exe`,
|
||||||
});
|
});
|
||||||
};
|
const resolveUninstall = () =>
|
||||||
|
|
||||||
const resolveUninstall = () => {
|
|
||||||
let ext = platform === "win32" ? ".exe" : "";
|
|
||||||
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
|
||||||
|
|
||||||
resolveResource({
|
resolveResource({
|
||||||
file: "uninstall-service" + suffix + ext,
|
file: "uninstall-service.exe",
|
||||||
downloadURL: `${SERVICE_URL}/uninstall-service${ext}`,
|
downloadURL: `${SERVICE_URL}/uninstall-service.exe`,
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const resolveMmdb = () =>
|
const resolveMmdb = () =>
|
||||||
resolveResource({
|
resolveResource({
|
||||||
file: "Country.mmdb",
|
file: "Country.mmdb",
|
||||||
@ -480,30 +383,24 @@ const resolveEnableLoopback = () =>
|
|||||||
downloadURL: `https://github.com/Kuingsmile/uwp-tool/releases/download/latest/enableLoopback.exe`,
|
downloadURL: `https://github.com/Kuingsmile/uwp-tool/releases/download/latest/enableLoopback.exe`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const resolveWinSysproxy = () =>
|
|
||||||
resolveResource({
|
|
||||||
file: "sysproxy.exe",
|
|
||||||
downloadURL: `https://github.com/clash-verge-rev/sysproxy/releases/download/${arch}/sysproxy.exe`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const tasks = [
|
const tasks = [
|
||||||
// { name: "clash", func: resolveClash, retry: 5 },
|
// { name: "clash", func: resolveClash, retry: 5 },
|
||||||
{
|
{
|
||||||
name: "verge-mihomo-alpha",
|
name: "clash-meta-alpha",
|
||||||
func: () =>
|
func: () =>
|
||||||
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
|
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
|
||||||
retry: 5,
|
retry: 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "verge-mihomo",
|
name: "clash-meta",
|
||||||
func: () =>
|
func: () =>
|
||||||
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
|
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
|
||||||
retry: 5,
|
retry: 5,
|
||||||
},
|
},
|
||||||
{ name: "plugin", func: resolvePlugin, retry: 5, winOnly: true },
|
{ name: "plugin", func: resolvePlugin, retry: 5, winOnly: true },
|
||||||
{ name: "service", func: resolveService, retry: 5 },
|
{ name: "service", func: resolveService, retry: 5, winOnly: true },
|
||||||
{ name: "install", func: resolveInstall, retry: 5 },
|
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
|
||||||
{ name: "uninstall", func: resolveUninstall, retry: 5 },
|
{ name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },
|
||||||
{ name: "mmdb", func: resolveMmdb, retry: 5 },
|
{ name: "mmdb", func: resolveMmdb, retry: 5 },
|
||||||
{ name: "geosite", func: resolveGeosite, retry: 5 },
|
{ name: "geosite", func: resolveGeosite, retry: 5 },
|
||||||
{ name: "geoip", func: resolveGeoIP, retry: 5 },
|
{ name: "geoip", func: resolveGeoIP, retry: 5 },
|
||||||
@ -513,51 +410,19 @@ const tasks = [
|
|||||||
retry: 5,
|
retry: 5,
|
||||||
winOnly: true,
|
winOnly: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "service_chmod",
|
|
||||||
func: resolveServicePermission,
|
|
||||||
retry: 5,
|
|
||||||
unixOnly: platform === "linux" || platform === "darwin",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "windows-sysproxy",
|
|
||||||
func: resolveWinSysproxy,
|
|
||||||
retry: 5,
|
|
||||||
winOnly: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "set_dns_script",
|
|
||||||
func: resolveSetDnsScript,
|
|
||||||
retry: 5,
|
|
||||||
macosOnly: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "unset_dns_script",
|
|
||||||
func: resolveUnSetDnsScript,
|
|
||||||
retry: 5,
|
|
||||||
macosOnly: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "locales",
|
|
||||||
func: resolveLocales,
|
|
||||||
retry: 2,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
async function runTask() {
|
async function runTask() {
|
||||||
const task = tasks.shift();
|
const task = tasks.shift();
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
if (task.unixOnly && platform === "win32") return runTask();
|
if (task.winOnly && process.platform !== "win32") return runTask();
|
||||||
if (task.winOnly && platform !== "win32") return runTask();
|
|
||||||
if (task.macosOnly && platform !== "darwin") return runTask();
|
|
||||||
if (task.linuxOnly && platform !== "linux") return runTask();
|
|
||||||
|
|
||||||
for (let i = 0; i < task.retry; i++) {
|
for (let i = 0; i < task.retry; i++) {
|
||||||
try {
|
try {
|
||||||
await task.func();
|
await task.func();
|
||||||
break;
|
break;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log_error(`task::${task.name} try ${i} ==`, err.message);
|
console.error(`[ERROR]: task::${task.name} try ${i} ==`, err.message);
|
||||||
if (i === task.retry - 1) throw err;
|
if (i === task.retry - 1) throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -565,3 +430,4 @@ async function runTask() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runTask();
|
runTask();
|
||||||
|
runTask();
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
import { exec } from "child_process";
|
|
||||||
import { promisify } from "util";
|
|
||||||
import fs from "fs/promises";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 为Alpha版本重命名版本号
|
|
||||||
*/
|
|
||||||
const execPromise = promisify(exec);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标准输出HEAD hash
|
|
||||||
*/
|
|
||||||
async function getLatestCommitHash() {
|
|
||||||
try {
|
|
||||||
const { stdout } = await execPromise("git rev-parse HEAD");
|
|
||||||
const commitHash = stdout.trim();
|
|
||||||
// 格式化,只截取前7位字符
|
|
||||||
const formathash = commitHash.substring(0, 7);
|
|
||||||
console.log(`Found the latest commit hash code: ${commitHash}`);
|
|
||||||
return formathash;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("pnpm run fix-alpha-version ERROR", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string 传入格式化后的hash
|
|
||||||
* 将新的版本号写入文件 package.json
|
|
||||||
*/
|
|
||||||
async function updatePackageVersion(newVersion) {
|
|
||||||
// 获取内容根目录
|
|
||||||
const _dirname = process.cwd();
|
|
||||||
const packageJsonPath = path.join(_dirname, "package.json");
|
|
||||||
try {
|
|
||||||
// 读取文件
|
|
||||||
const data = await fs.readFile(packageJsonPath, "utf8");
|
|
||||||
const packageJson = JSON.parse(data);
|
|
||||||
// 获取键值替换
|
|
||||||
let result = packageJson.version.replace("alpha", newVersion);
|
|
||||||
console.log("[INFO]: Current version is: ", result);
|
|
||||||
packageJson.version = result;
|
|
||||||
// 写入版本号
|
|
||||||
await fs.writeFile(
|
|
||||||
packageJsonPath,
|
|
||||||
JSON.stringify(packageJson, null, 2),
|
|
||||||
"utf8",
|
|
||||||
);
|
|
||||||
console.log(`[INFO]: Alpha version update to: ${newVersion}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("pnpm run fix-alpha-version ERROR", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newVersion = await getLatestCommitHash();
|
|
||||||
updatePackageVersion(newVersion).catch(console.error);
|
|
@ -1,103 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import fsp from "fs/promises";
|
|
||||||
import path from "path";
|
|
||||||
import AdmZip from "adm-zip";
|
|
||||||
import { createRequire } from "module";
|
|
||||||
import { getOctokit, context } from "@actions/github";
|
|
||||||
|
|
||||||
const target = process.argv.slice(2)[0];
|
|
||||||
const alpha = process.argv.slice(2)[1];
|
|
||||||
|
|
||||||
const ARCH_MAP = {
|
|
||||||
"x86_64-pc-windows-msvc": "x64",
|
|
||||||
"i686-pc-windows-msvc": "x86",
|
|
||||||
"aarch64-pc-windows-msvc": "arm64",
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROCESS_MAP = {
|
|
||||||
x64: "x64",
|
|
||||||
ia32: "x86",
|
|
||||||
arm64: "arm64",
|
|
||||||
};
|
|
||||||
const arch = target ? ARCH_MAP[target] : PROCESS_MAP[process.arch];
|
|
||||||
/// Script for ci
|
|
||||||
/// 打包绿色版/便携版 (only Windows)
|
|
||||||
async function resolvePortable() {
|
|
||||||
if (process.platform !== "win32") return;
|
|
||||||
|
|
||||||
const releaseDir = target
|
|
||||||
? `./src-tauri/target/${target}/release`
|
|
||||||
: `./src-tauri/target/release`;
|
|
||||||
|
|
||||||
const configDir = path.join(releaseDir, ".config");
|
|
||||||
|
|
||||||
if (!fs.existsSync(releaseDir)) {
|
|
||||||
throw new Error("could not found the release dir");
|
|
||||||
}
|
|
||||||
|
|
||||||
await fsp.mkdir(configDir, { recursive: true });
|
|
||||||
if (!fs.existsSync(path.join(configDir, "PORTABLE"))) {
|
|
||||||
await fsp.writeFile(path.join(configDir, "PORTABLE"), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
const zip = new AdmZip();
|
|
||||||
|
|
||||||
zip.addLocalFile(path.join(releaseDir, "Clash Verge.exe"));
|
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo.exe"));
|
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo-alpha.exe"));
|
|
||||||
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
|
||||||
zip.addLocalFolder(
|
|
||||||
path.join(
|
|
||||||
releaseDir,
|
|
||||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
|
|
||||||
),
|
|
||||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
|
|
||||||
);
|
|
||||||
zip.addLocalFolder(configDir, ".config");
|
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
|
||||||
const packageJson = require("../package.json");
|
|
||||||
const { version } = packageJson;
|
|
||||||
|
|
||||||
const zipFile = `Clash.Verge_${version}_${arch}_fixed_webview2_portable.zip`;
|
|
||||||
zip.writeZip(zipFile);
|
|
||||||
|
|
||||||
console.log("[INFO]: create portable zip successfully");
|
|
||||||
|
|
||||||
// push release assets
|
|
||||||
if (process.env.GITHUB_TOKEN === undefined) {
|
|
||||||
throw new Error("GITHUB_TOKEN is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
|
||||||
const github = getOctokit(process.env.GITHUB_TOKEN);
|
|
||||||
const tag = alpha ? "alpha" : process.env.TAG_NAME || `v${version}`;
|
|
||||||
console.log("[INFO]: upload to ", tag);
|
|
||||||
|
|
||||||
const { data: release } = await github.rest.repos.getReleaseByTag({
|
|
||||||
...options,
|
|
||||||
tag,
|
|
||||||
});
|
|
||||||
|
|
||||||
let assets = release.assets.filter((x) => {
|
|
||||||
return x.name === zipFile;
|
|
||||||
});
|
|
||||||
if (assets.length > 0) {
|
|
||||||
let id = assets[0].id;
|
|
||||||
await github.rest.repos.deleteReleaseAsset({
|
|
||||||
...options,
|
|
||||||
asset_id: id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(release.name);
|
|
||||||
|
|
||||||
await github.rest.repos.uploadReleaseAsset({
|
|
||||||
...options,
|
|
||||||
release_id: release.id,
|
|
||||||
name: zipFile,
|
|
||||||
data: zip.toBuffer(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
resolvePortable().catch(console.error);
|
|
@ -1,20 +1,17 @@
|
|||||||
import fs from "fs";
|
import fs from "fs-extra";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import AdmZip from "adm-zip";
|
import AdmZip from "adm-zip";
|
||||||
import { createRequire } from "module";
|
import { createRequire } from "module";
|
||||||
import fsp from "fs/promises";
|
import { getOctokit, context } from "@actions/github";
|
||||||
|
|
||||||
const target = process.argv.slice(2)[0];
|
const target = process.argv.slice(2)[0];
|
||||||
|
const alpha = process.argv.slice(2)[1];
|
||||||
|
|
||||||
const ARCH_MAP = {
|
const ARCH_MAP = {
|
||||||
"x86_64-pc-windows-msvc": "x64",
|
"x86_64-pc-windows-msvc": "x64",
|
||||||
"aarch64-pc-windows-msvc": "arm64",
|
"aarch64-pc-windows-msvc": "arm64",
|
||||||
};
|
};
|
||||||
|
|
||||||
const PROCESS_MAP = {
|
|
||||||
x64: "x64",
|
|
||||||
arm64: "arm64",
|
|
||||||
};
|
|
||||||
const arch = target ? ARCH_MAP[target] : PROCESS_MAP[process.arch];
|
|
||||||
/// Script for ci
|
/// Script for ci
|
||||||
/// 打包绿色版/便携版 (only Windows)
|
/// 打包绿色版/便携版 (only Windows)
|
||||||
async function resolvePortable() {
|
async function resolvePortable() {
|
||||||
@ -25,28 +22,64 @@ async function resolvePortable() {
|
|||||||
: `./src-tauri/target/release`;
|
: `./src-tauri/target/release`;
|
||||||
const configDir = path.join(releaseDir, ".config");
|
const configDir = path.join(releaseDir, ".config");
|
||||||
|
|
||||||
if (!fs.existsSync(releaseDir)) {
|
if (!(await fs.pathExists(releaseDir))) {
|
||||||
throw new Error("could not found the release dir");
|
throw new Error("could not found the release dir");
|
||||||
}
|
}
|
||||||
|
|
||||||
await fsp.mkdir(configDir, { recursive: true });
|
await fs.mkdir(configDir);
|
||||||
if (!fs.existsSync(path.join(configDir, "PORTABLE"))) {
|
await fs.createFile(path.join(configDir, "PORTABLE"));
|
||||||
await fsp.writeFile(path.join(configDir, "PORTABLE"), "");
|
|
||||||
}
|
|
||||||
const zip = new AdmZip();
|
const zip = new AdmZip();
|
||||||
|
|
||||||
zip.addLocalFile(path.join(releaseDir, "clash-verge.exe"));
|
zip.addLocalFile(path.join(releaseDir, "Clash Verge.exe"));
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo.exe"));
|
zip.addLocalFile(path.join(releaseDir, "clash-meta.exe"));
|
||||||
zip.addLocalFile(path.join(releaseDir, "verge-mihomo-alpha.exe"));
|
zip.addLocalFile(path.join(releaseDir, "clash-meta-alpha.exe"));
|
||||||
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
||||||
zip.addLocalFolder(configDir, ".config");
|
zip.addLocalFolder(configDir, ".config");
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const packageJson = require("../package.json");
|
const packageJson = require("../package.json");
|
||||||
const { version } = packageJson;
|
const { version } = packageJson;
|
||||||
const zipFile = `Clash.Verge_${version}_${arch}_portable.zip`;
|
|
||||||
|
const zipFile = `Clash.Verge_${version}_${ARCH_MAP[target]}_portable.zip`;
|
||||||
zip.writeZip(zipFile);
|
zip.writeZip(zipFile);
|
||||||
|
|
||||||
console.log("[INFO]: create portable zip successfully");
|
console.log("[INFO]: create portable zip successfully");
|
||||||
|
|
||||||
|
// push release assets
|
||||||
|
if (process.env.GITHUB_TOKEN === undefined) {
|
||||||
|
throw new Error("GITHUB_TOKEN is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
||||||
|
const github = getOctokit(process.env.GITHUB_TOKEN);
|
||||||
|
const tag = alpha ? "alpha" : process.env.TAG_NAME || `v${version}`;
|
||||||
|
console.log("[INFO]: upload to ", tag);
|
||||||
|
|
||||||
|
const { data: release } = await github.rest.repos.getReleaseByTag({
|
||||||
|
...options,
|
||||||
|
tag,
|
||||||
|
});
|
||||||
|
|
||||||
|
let assets = release.assets.filter((x) => {
|
||||||
|
return x.name === zipFile;
|
||||||
|
});
|
||||||
|
if (assets.length > 0) {
|
||||||
|
let id = assets[0].id;
|
||||||
|
await github.rest.repos.deleteReleaseAsset({
|
||||||
|
...options,
|
||||||
|
asset_id: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(release.name);
|
||||||
|
|
||||||
|
await github.rest.repos.uploadReleaseAsset({
|
||||||
|
...options,
|
||||||
|
release_id: release.id,
|
||||||
|
name: zipFile,
|
||||||
|
data: zip.toBuffer(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvePortable().catch(console.error);
|
resolvePortable().catch(console.error);
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
import fs from "fs/promises";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 package.json 文件中的版本号
|
|
||||||
*/
|
|
||||||
async function updatePackageVersion() {
|
|
||||||
const _dirname = process.cwd();
|
|
||||||
const packageJsonPath = path.join(_dirname, "package.json");
|
|
||||||
try {
|
|
||||||
const data = await fs.readFile(packageJsonPath, "utf8");
|
|
||||||
const packageJson = JSON.parse(data);
|
|
||||||
|
|
||||||
let result = packageJson.version;
|
|
||||||
if (!result.includes("alpha")) {
|
|
||||||
result = `${result}-alpha`;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[INFO]: Current package.json version is: ", result);
|
|
||||||
packageJson.version = result;
|
|
||||||
await fs.writeFile(
|
|
||||||
packageJsonPath,
|
|
||||||
JSON.stringify(packageJson, null, 2),
|
|
||||||
"utf8",
|
|
||||||
);
|
|
||||||
console.log(`[INFO]: package.json version updated to: ${result}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating package.json version:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 Cargo.toml 文件中的版本号
|
|
||||||
*/
|
|
||||||
async function updateCargoVersion() {
|
|
||||||
const _dirname = process.cwd();
|
|
||||||
const cargoTomlPath = path.join(_dirname, "src-tauri", "Cargo.toml");
|
|
||||||
try {
|
|
||||||
const data = await fs.readFile(cargoTomlPath, "utf8");
|
|
||||||
const lines = data.split("\n");
|
|
||||||
|
|
||||||
const updatedLines = lines.map((line) => {
|
|
||||||
if (line.startsWith("version =")) {
|
|
||||||
const versionMatch = line.match(/version\s*=\s*"([^"]+)"/);
|
|
||||||
if (versionMatch && !versionMatch[1].includes("alpha")) {
|
|
||||||
const newVersion = `${versionMatch[1]}-alpha`;
|
|
||||||
return line.replace(versionMatch[1], newVersion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return line;
|
|
||||||
});
|
|
||||||
|
|
||||||
await fs.writeFile(cargoTomlPath, updatedLines.join("\n"), "utf8");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating Cargo.toml version:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 tauri.conf.json 文件中的版本号
|
|
||||||
*/
|
|
||||||
async function updateTauriConfigVersion() {
|
|
||||||
const _dirname = process.cwd();
|
|
||||||
const tauriConfigPath = path.join(_dirname, "src-tauri", "tauri.conf.json");
|
|
||||||
try {
|
|
||||||
const data = await fs.readFile(tauriConfigPath, "utf8");
|
|
||||||
const tauriConfig = JSON.parse(data);
|
|
||||||
|
|
||||||
let version = tauriConfig.version;
|
|
||||||
if (!version.includes("alpha")) {
|
|
||||||
version = `${version}-alpha`;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[INFO]: Current tauri.conf.json version is: ", version);
|
|
||||||
tauriConfig.version = version;
|
|
||||||
await fs.writeFile(
|
|
||||||
tauriConfigPath,
|
|
||||||
JSON.stringify(tauriConfig, null, 2),
|
|
||||||
"utf8",
|
|
||||||
);
|
|
||||||
console.log(`[INFO]: tauri.conf.json version updated to: ${version}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating tauri.conf.json version:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主函数,依次更新所有文件的版本号
|
|
||||||
*/
|
|
||||||
async function main() {
|
|
||||||
await updatePackageVersion();
|
|
||||||
await updateCargoVersion();
|
|
||||||
await updateTauriConfigVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(console.error);
|
|
@ -1,197 +0,0 @@
|
|||||||
import fs from "fs/promises";
|
|
||||||
import path from "path";
|
|
||||||
import { program } from "commander";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证版本号格式
|
|
||||||
* @param {string} version
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function isValidVersion(version) {
|
|
||||||
return /^v?\d+\.\d+\.\d+(-(alpha|beta|rc)(\.\d+)?)?$/i.test(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标准化版本号(确保v前缀可选)
|
|
||||||
* @param {string} version
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function normalizeVersion(version) {
|
|
||||||
return version.startsWith("v") ? version : `v${version}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 package.json 文件中的版本号
|
|
||||||
* @param {string} newVersion 新版本号
|
|
||||||
*/
|
|
||||||
async function updatePackageVersion(newVersion) {
|
|
||||||
const _dirname = process.cwd();
|
|
||||||
const packageJsonPath = path.join(_dirname, "package.json");
|
|
||||||
try {
|
|
||||||
const data = await fs.readFile(packageJsonPath, "utf8");
|
|
||||||
const packageJson = JSON.parse(data);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[INFO]: Current package.json version is: ",
|
|
||||||
packageJson.version,
|
|
||||||
);
|
|
||||||
packageJson.version = newVersion.startsWith("v")
|
|
||||||
? newVersion.slice(1)
|
|
||||||
: newVersion;
|
|
||||||
await fs.writeFile(
|
|
||||||
packageJsonPath,
|
|
||||||
JSON.stringify(packageJson, null, 2),
|
|
||||||
"utf8",
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`[INFO]: package.json version updated to: ${packageJson.version}`,
|
|
||||||
);
|
|
||||||
return packageJson.version;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating package.json version:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 Cargo.toml 文件中的版本号
|
|
||||||
* @param {string} newVersion 新版本号
|
|
||||||
*/
|
|
||||||
async function updateCargoVersion(newVersion) {
|
|
||||||
const _dirname = process.cwd();
|
|
||||||
const cargoTomlPath = path.join(_dirname, "src-tauri", "Cargo.toml");
|
|
||||||
try {
|
|
||||||
const data = await fs.readFile(cargoTomlPath, "utf8");
|
|
||||||
const lines = data.split("\n");
|
|
||||||
|
|
||||||
const versionWithoutV = newVersion.startsWith("v")
|
|
||||||
? newVersion.slice(1)
|
|
||||||
: newVersion;
|
|
||||||
|
|
||||||
const updatedLines = lines.map((line) => {
|
|
||||||
if (line.trim().startsWith("version =")) {
|
|
||||||
return line.replace(
|
|
||||||
/version\s*=\s*"[^"]+"/,
|
|
||||||
`version = "${versionWithoutV}"`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return line;
|
|
||||||
});
|
|
||||||
|
|
||||||
await fs.writeFile(cargoTomlPath, updatedLines.join("\n"), "utf8");
|
|
||||||
console.log(`[INFO]: Cargo.toml version updated to: ${versionWithoutV}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating Cargo.toml version:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新 tauri.conf.json 文件中的版本号
|
|
||||||
* @param {string} newVersion 新版本号
|
|
||||||
*/
|
|
||||||
async function updateTauriConfigVersion(newVersion) {
|
|
||||||
const _dirname = process.cwd();
|
|
||||||
const tauriConfigPath = path.join(_dirname, "src-tauri", "tauri.conf.json");
|
|
||||||
try {
|
|
||||||
const data = await fs.readFile(tauriConfigPath, "utf8");
|
|
||||||
const tauriConfig = JSON.parse(data);
|
|
||||||
|
|
||||||
const versionWithoutV = newVersion.startsWith("v")
|
|
||||||
? newVersion.slice(1)
|
|
||||||
: newVersion;
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[INFO]: Current tauri.conf.json version is: ",
|
|
||||||
tauriConfig.version,
|
|
||||||
);
|
|
||||||
tauriConfig.version = versionWithoutV;
|
|
||||||
await fs.writeFile(
|
|
||||||
tauriConfigPath,
|
|
||||||
JSON.stringify(tauriConfig, null, 2),
|
|
||||||
"utf8",
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`[INFO]: tauri.conf.json version updated to: ${versionWithoutV}`,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating tauri.conf.json version:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前版本号(从package.json)
|
|
||||||
*/
|
|
||||||
async function getCurrentVersion() {
|
|
||||||
const _dirname = process.cwd();
|
|
||||||
const packageJsonPath = path.join(_dirname, "package.json");
|
|
||||||
try {
|
|
||||||
const data = await fs.readFile(packageJsonPath, "utf8");
|
|
||||||
const packageJson = JSON.parse(data);
|
|
||||||
return packageJson.version;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error getting current version:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主函数,更新所有文件的版本号
|
|
||||||
* @param {string} versionArg 版本参数(可以是标签或完整版本号)
|
|
||||||
*/
|
|
||||||
async function main(versionArg) {
|
|
||||||
if (!versionArg) {
|
|
||||||
console.error("Error: Version argument is required");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let newVersion;
|
|
||||||
const validTags = ["alpha", "beta", "rc"];
|
|
||||||
|
|
||||||
// 判断参数是标签还是完整版本号
|
|
||||||
if (validTags.includes(versionArg.toLowerCase())) {
|
|
||||||
// 标签模式:在当前版本基础上添加标签
|
|
||||||
const currentVersion = await getCurrentVersion();
|
|
||||||
const baseVersion = currentVersion.replace(
|
|
||||||
/-(alpha|beta|rc)(\.\d+)?$/i,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
newVersion = `${baseVersion}-${versionArg.toLowerCase()}`;
|
|
||||||
} else {
|
|
||||||
// 完整版本号模式
|
|
||||||
if (!isValidVersion(versionArg)) {
|
|
||||||
console.error(
|
|
||||||
"Error: Invalid version format. Expected format: vX.X.X or vX.X.X-tag (e.g. v2.2.3 or v2.2.3-alpha)",
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
newVersion = normalizeVersion(versionArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[INFO]: Updating versions to: ${newVersion}`);
|
|
||||||
await updatePackageVersion(newVersion);
|
|
||||||
await updateCargoVersion(newVersion);
|
|
||||||
await updateTauriConfigVersion(newVersion);
|
|
||||||
console.log("[SUCCESS]: All version updates completed successfully!");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[ERROR]: Failed to update versions:", error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Example:
|
|
||||||
// pnpm release-version 2.2.3-alpha
|
|
||||||
// 设置命令行界面
|
|
||||||
program
|
|
||||||
.name("pnpm release-version")
|
|
||||||
.description(
|
|
||||||
"Update project version numbers. Can add tag (alpha/beta/rc) or set full version (e.g. v2.2.3 or v2.2.3-alpha)",
|
|
||||||
)
|
|
||||||
.argument(
|
|
||||||
"<version>",
|
|
||||||
"version tag (alpha/beta/rc) or full version (e.g. v2.2.3 or v2.2.3-alpha)",
|
|
||||||
)
|
|
||||||
.action(main)
|
|
||||||
.parse(process.argv);
|
|
@ -1,66 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 验证IPv4地址格式
|
|
||||||
function is_valid_ipv4() {
|
|
||||||
local ip=$1
|
|
||||||
local IFS='.'
|
|
||||||
local -a octets
|
|
||||||
|
|
||||||
[[ ! $ip =~ ^([0-9]+\.){3}[0-9]+$ ]] && return 1
|
|
||||||
read -r -a octets <<<"$ip"
|
|
||||||
[ "${#octets[@]}" -ne 4 ] && return 1
|
|
||||||
|
|
||||||
for octet in "${octets[@]}"; do
|
|
||||||
if ! [[ "$octet" =~ ^[0-9]+$ ]] || ((octet < 0 || octet > 255)); then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# 验证IPv6地址格式
|
|
||||||
function is_valid_ipv6() {
|
|
||||||
local ip=$1
|
|
||||||
if [[ ! $ip =~ ^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$ ]] &&
|
|
||||||
[[ ! $ip =~ ^(([0-9a-fA-F]{0,4}:){0,7}:|(:[0-9a-fA-F]{0,4}:){0,6}:[0-9a-fA-F]{0,4})$ ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# 验证IP地址是否为有效的IPv4或IPv6
|
|
||||||
function is_valid_ip() {
|
|
||||||
is_valid_ipv4 "$1" || is_valid_ipv6 "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查参数
|
|
||||||
[ $# -lt 1 ] && echo "Usage: $0 <IP address>" && exit 1
|
|
||||||
! is_valid_ip "$1" && echo "$1 is not a valid IP address." && exit 1
|
|
||||||
|
|
||||||
# 获取网络接口和硬件端口
|
|
||||||
nic=$(route -n get default | grep "interface" | awk '{print $2}')
|
|
||||||
hardware_port=$(networksetup -listallhardwareports | awk -v dev="$nic" '
|
|
||||||
/Hardware Port:/{port=$0; gsub("Hardware Port: ", "", port)}
|
|
||||||
/Device: /{if ($2 == dev) {print port; exit}}
|
|
||||||
')
|
|
||||||
|
|
||||||
# 获取当前DNS设置
|
|
||||||
original_dns=$(networksetup -getdnsservers "$hardware_port")
|
|
||||||
|
|
||||||
# 检查当前DNS设置是否有效
|
|
||||||
is_valid_dns=false
|
|
||||||
for ip in $original_dns; do
|
|
||||||
ip=$(echo "$ip" | tr -d '[:space:]')
|
|
||||||
if [ -n "$ip" ] && (is_valid_ipv4 "$ip" || is_valid_ipv6 "$ip"); then
|
|
||||||
is_valid_dns=true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# 更新DNS设置
|
|
||||||
if [ "$is_valid_dns" = false ]; then
|
|
||||||
echo "empty" >.original_dns.txt
|
|
||||||
else
|
|
||||||
echo "$original_dns" >.original_dns.txt
|
|
||||||
fi
|
|
||||||
networksetup -setdnsservers "$hardware_port" "$1"
|
|
@ -1,20 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
nic=$(route -n get default | grep "interface" | awk '{print $2}')
|
|
||||||
|
|
||||||
hardware_port=$(networksetup -listallhardwareports | awk -v dev="$nic" '
|
|
||||||
/Hardware Port:/{
|
|
||||||
port=$0; gsub("Hardware Port: ", "", port)
|
|
||||||
}
|
|
||||||
/Device: /{
|
|
||||||
if ($2 == dev) {
|
|
||||||
print port;
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
')
|
|
||||||
|
|
||||||
if [ -f .original_dns.txt ]; then
|
|
||||||
original_dns=$(cat .original_dns.txt)
|
|
||||||
networksetup -setdnsservers "$hardware_port" $original_dns
|
|
||||||
rm -rf .original_dns.txt
|
|
||||||
fi
|
|
@ -1,5 +1,4 @@
|
|||||||
import fs from "fs";
|
import fs from "fs-extra";
|
||||||
import fsp from "fs/promises";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
const UPDATE_LOG = "UPDATELOG.md";
|
const UPDATE_LOG = "UPDATELOG.md";
|
||||||
@ -13,11 +12,11 @@ export async function resolveUpdateLog(tag) {
|
|||||||
|
|
||||||
const file = path.join(cwd, UPDATE_LOG);
|
const file = path.join(cwd, UPDATE_LOG);
|
||||||
|
|
||||||
if (!fs.existsSync(file)) {
|
if (!(await fs.pathExists(file))) {
|
||||||
throw new Error("could not found UPDATELOG.md");
|
throw new Error("could not found UPDATELOG.md");
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await fsp.readFile(file, "utf-8");
|
const data = await fs.readFile(file).then((d) => d.toString("utf8"));
|
||||||
|
|
||||||
const map = {};
|
const map = {};
|
||||||
let p = "";
|
let p = "";
|
||||||
@ -43,42 +42,3 @@ export async function resolveUpdateLog(tag) {
|
|||||||
|
|
||||||
return map[tag].join("\n").trim();
|
return map[tag].join("\n").trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveUpdateLogDefault() {
|
|
||||||
const cwd = process.cwd();
|
|
||||||
const file = path.join(cwd, UPDATE_LOG);
|
|
||||||
|
|
||||||
if (!fs.existsSync(file)) {
|
|
||||||
throw new Error("could not found UPDATELOG.md");
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await fsp.readFile(file, "utf-8");
|
|
||||||
|
|
||||||
const reTitle = /^## v[\d\.]+/;
|
|
||||||
const reEnd = /^---/;
|
|
||||||
|
|
||||||
let isCapturing = false;
|
|
||||||
let content = [];
|
|
||||||
let firstTag = "";
|
|
||||||
|
|
||||||
for (const line of data.split("\n")) {
|
|
||||||
if (reTitle.test(line) && !isCapturing) {
|
|
||||||
isCapturing = true;
|
|
||||||
firstTag = line.slice(3).trim();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCapturing) {
|
|
||||||
if (reEnd.test(line)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
content.push(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!firstTag) {
|
|
||||||
throw new Error("could not found any version tag in UPDATELOG.md");
|
|
||||||
}
|
|
||||||
|
|
||||||
return content.join("\n").trim();
|
|
||||||
}
|
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
import fetch from "node-fetch";
|
|
||||||
import { getOctokit, context } from "@actions/github";
|
|
||||||
import { resolveUpdateLog } from "./updatelog.mjs";
|
|
||||||
|
|
||||||
const UPDATE_TAG_NAME = "updater";
|
|
||||||
const UPDATE_JSON_FILE = "update-fixed-webview2.json";
|
|
||||||
const UPDATE_JSON_PROXY = "update-fixed-webview2-proxy.json";
|
|
||||||
|
|
||||||
/// generate update.json
|
|
||||||
/// upload to update tag's release asset
|
|
||||||
async function resolveUpdater() {
|
|
||||||
if (process.env.GITHUB_TOKEN === undefined) {
|
|
||||||
throw new Error("GITHUB_TOKEN is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
|
||||||
const github = getOctokit(process.env.GITHUB_TOKEN);
|
|
||||||
|
|
||||||
const { data: tags } = await github.rest.repos.listTags({
|
|
||||||
...options,
|
|
||||||
per_page: 10,
|
|
||||||
page: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// get the latest publish tag
|
|
||||||
const tag = tags.find((t) => t.name.startsWith("v"));
|
|
||||||
|
|
||||||
console.log(tag);
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
const { data: latestRelease } = await github.rest.repos.getReleaseByTag({
|
|
||||||
...options,
|
|
||||||
tag: tag.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateData = {
|
|
||||||
name: tag.name,
|
|
||||||
notes: await resolveUpdateLog(tag.name), // use updatelog.md
|
|
||||||
pub_date: new Date().toISOString(),
|
|
||||||
platforms: {
|
|
||||||
"windows-x86_64": { signature: "", url: "" },
|
|
||||||
"windows-aarch64": { signature: "", url: "" },
|
|
||||||
"windows-x86": { signature: "", url: "" },
|
|
||||||
"windows-i686": { signature: "", url: "" },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const promises = latestRelease.assets.map(async (asset) => {
|
|
||||||
const { name, browser_download_url } = asset;
|
|
||||||
|
|
||||||
// win64 url
|
|
||||||
if (name.endsWith("x64_fixed_webview2-setup.nsis.zip")) {
|
|
||||||
updateData.platforms["windows-x86_64"].url = browser_download_url;
|
|
||||||
}
|
|
||||||
// win64 signature
|
|
||||||
if (name.endsWith("x64_fixed_webview2-setup.nsis.zip.sig")) {
|
|
||||||
const sig = await getSignature(browser_download_url);
|
|
||||||
updateData.platforms["windows-x86_64"].signature = sig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// win32 url
|
|
||||||
if (name.endsWith("x86_fixed_webview2-setup.nsis.zip")) {
|
|
||||||
updateData.platforms["windows-x86"].url = browser_download_url;
|
|
||||||
updateData.platforms["windows-i686"].url = browser_download_url;
|
|
||||||
}
|
|
||||||
// win32 signature
|
|
||||||
if (name.endsWith("x86_fixed_webview2-setup.nsis.zip.sig")) {
|
|
||||||
const sig = await getSignature(browser_download_url);
|
|
||||||
updateData.platforms["windows-x86"].signature = sig;
|
|
||||||
updateData.platforms["windows-i686"].signature = sig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// win arm url
|
|
||||||
if (name.endsWith("arm64_fixed_webview2-setup.nsis.zip")) {
|
|
||||||
updateData.platforms["windows-aarch64"].url = browser_download_url;
|
|
||||||
}
|
|
||||||
// win arm signature
|
|
||||||
if (name.endsWith("arm64_fixed_webview2-setup.nsis.zip.sig")) {
|
|
||||||
const sig = await getSignature(browser_download_url);
|
|
||||||
updateData.platforms["windows-aarch64"].signature = sig;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.allSettled(promises);
|
|
||||||
console.log(updateData);
|
|
||||||
|
|
||||||
// maybe should test the signature as well
|
|
||||||
// delete the null field
|
|
||||||
Object.entries(updateData.platforms).forEach(([key, value]) => {
|
|
||||||
if (!value.url) {
|
|
||||||
console.log(`[Error]: failed to parse release for "${key}"`);
|
|
||||||
delete updateData.platforms[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 生成一个代理github的更新文件
|
|
||||||
// 使用 https://hub.fastgit.xyz/ 做github资源的加速
|
|
||||||
const updateDataNew = JSON.parse(JSON.stringify(updateData));
|
|
||||||
|
|
||||||
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
|
|
||||||
if (value.url) {
|
|
||||||
updateDataNew.platforms[key].url =
|
|
||||||
"https://download.clashverge.dev/" + value.url;
|
|
||||||
} else {
|
|
||||||
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// update the update.json
|
|
||||||
const { data: updateRelease } = await github.rest.repos.getReleaseByTag({
|
|
||||||
...options,
|
|
||||||
tag: UPDATE_TAG_NAME,
|
|
||||||
});
|
|
||||||
|
|
||||||
// delete the old assets
|
|
||||||
for (let asset of updateRelease.assets) {
|
|
||||||
if (asset.name === UPDATE_JSON_FILE) {
|
|
||||||
await github.rest.repos.deleteReleaseAsset({
|
|
||||||
...options,
|
|
||||||
asset_id: asset.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (asset.name === UPDATE_JSON_PROXY) {
|
|
||||||
await github.rest.repos
|
|
||||||
.deleteReleaseAsset({ ...options, asset_id: asset.id })
|
|
||||||
.catch(console.error); // do not break the pipeline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// upload new assets
|
|
||||||
await github.rest.repos.uploadReleaseAsset({
|
|
||||||
...options,
|
|
||||||
release_id: updateRelease.id,
|
|
||||||
name: UPDATE_JSON_FILE,
|
|
||||||
data: JSON.stringify(updateData, null, 2),
|
|
||||||
});
|
|
||||||
|
|
||||||
await github.rest.repos.uploadReleaseAsset({
|
|
||||||
...options,
|
|
||||||
release_id: updateRelease.id,
|
|
||||||
name: UPDATE_JSON_PROXY,
|
|
||||||
data: JSON.stringify(updateDataNew, null, 2),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the signature file content
|
|
||||||
async function getSignature(url) {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: { "Content-Type": "application/octet-stream" },
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.text();
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveUpdater().catch(console.error);
|
|
@ -1,15 +1,10 @@
|
|||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import { getOctokit, context } from "@actions/github";
|
import { getOctokit, context } from "@actions/github";
|
||||||
import { resolveUpdateLog, resolveUpdateLogDefault } from "./updatelog.mjs";
|
import { resolveUpdateLog } from "./updatelog.mjs";
|
||||||
|
|
||||||
// Add stable update JSON filenames
|
|
||||||
const UPDATE_TAG_NAME = "updater";
|
const UPDATE_TAG_NAME = "updater";
|
||||||
const UPDATE_JSON_FILE = "update.json";
|
const UPDATE_JSON_FILE = "update.json";
|
||||||
const UPDATE_JSON_PROXY = "update-proxy.json";
|
const UPDATE_JSON_PROXY = "update-proxy.json";
|
||||||
// Add alpha update JSON filenames
|
|
||||||
const ALPHA_TAG_NAME = "updater-alpha";
|
|
||||||
const ALPHA_UPDATE_JSON_FILE = "update.json";
|
|
||||||
const ALPHA_UPDATE_JSON_PROXY = "update-proxy.json";
|
|
||||||
|
|
||||||
/// generate update.json
|
/// generate update.json
|
||||||
/// upload to update tag's release asset
|
/// upload to update tag's release asset
|
||||||
@ -21,293 +16,171 @@ async function resolveUpdater() {
|
|||||||
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
||||||
const github = getOctokit(process.env.GITHUB_TOKEN);
|
const github = getOctokit(process.env.GITHUB_TOKEN);
|
||||||
|
|
||||||
// Fetch all tags using pagination
|
const { data: tags } = await github.rest.repos.listTags({
|
||||||
let allTags = [];
|
...options,
|
||||||
let page = 1;
|
per_page: 10,
|
||||||
const perPage = 100;
|
page: 1,
|
||||||
|
});
|
||||||
|
|
||||||
while (true) {
|
// get the latest publish tag
|
||||||
const { data: pageTags } = await github.rest.repos.listTags({
|
const tag = tags.find((t) => t.name.startsWith("v"));
|
||||||
...options,
|
|
||||||
per_page: perPage,
|
|
||||||
page: page,
|
|
||||||
});
|
|
||||||
|
|
||||||
allTags = allTags.concat(pageTags);
|
console.log(tag);
|
||||||
|
|
||||||
// Break if we received fewer tags than requested (last page)
|
|
||||||
if (pageTags.length < perPage) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
page++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tags = allTags;
|
|
||||||
console.log(`Retrieved ${tags.length} tags in total`);
|
|
||||||
|
|
||||||
// More flexible tag detection with regex patterns
|
|
||||||
const stableTagRegex = /^v\d+\.\d+\.\d+$/; // Matches vX.Y.Z format
|
|
||||||
// const preReleaseRegex = /^v\d+\.\d+\.\d+-(alpha|beta|rc|pre)/i; // Matches vX.Y.Z-alpha/beta/rc format
|
|
||||||
const preReleaseRegex = /^(alpha|beta|rc|pre)$/i; // Matches exact alpha/beta/rc/pre tags
|
|
||||||
|
|
||||||
// Get the latest stable tag and pre-release tag
|
|
||||||
const stableTag = tags.find((t) => stableTagRegex.test(t.name));
|
|
||||||
const preReleaseTag = tags.find((t) => preReleaseRegex.test(t.name));
|
|
||||||
|
|
||||||
console.log("All tags:", tags.map((t) => t.name).join(", "));
|
|
||||||
console.log("Stable tag:", stableTag ? stableTag.name : "None found");
|
|
||||||
console.log(
|
|
||||||
"Pre-release tag:",
|
|
||||||
preReleaseTag ? preReleaseTag.name : "None found",
|
|
||||||
);
|
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
// Process stable release
|
const { data: latestRelease } = await github.rest.repos.getReleaseByTag({
|
||||||
if (stableTag) {
|
...options,
|
||||||
await processRelease(github, options, stableTag, false);
|
tag: tag.name,
|
||||||
}
|
});
|
||||||
|
|
||||||
// Process pre-release if found
|
const updateData = {
|
||||||
if (preReleaseTag) {
|
name: tag.name,
|
||||||
await processRelease(github, options, preReleaseTag, true);
|
notes: await resolveUpdateLog(tag.name), // use updatelog.md
|
||||||
}
|
pub_date: new Date().toISOString(),
|
||||||
}
|
platforms: {
|
||||||
|
win64: { signature: "", url: "" }, // compatible with older formats
|
||||||
|
linux: { signature: "", url: "" }, // compatible with older formats
|
||||||
|
darwin: { signature: "", url: "" }, // compatible with older formats
|
||||||
|
"darwin-aarch64": { signature: "", url: "" },
|
||||||
|
"darwin-intel": { signature: "", url: "" },
|
||||||
|
"darwin-x86_64": { signature: "", url: "" },
|
||||||
|
"linux-x86_64": { signature: "", url: "" },
|
||||||
|
"linux-aarch64": { signature: "", url: "" },
|
||||||
|
"linux-armv7": { signature: "", url: "" },
|
||||||
|
"windows-x86_64": { signature: "", url: "" },
|
||||||
|
"windows-aarch64": { signature: "", url: "" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Process a release (stable or alpha) and generate update files
|
const promises = latestRelease.assets.map(async (asset) => {
|
||||||
async function processRelease(github, options, tag, isAlpha) {
|
const { name, browser_download_url } = asset;
|
||||||
if (!tag) return;
|
|
||||||
|
|
||||||
try {
|
// win64 url
|
||||||
const { data: release } = await github.rest.repos.getReleaseByTag({
|
if (name.endsWith("x64-setup.nsis.zip")) {
|
||||||
...options,
|
updateData.platforms.win64.url = browser_download_url;
|
||||||
tag: tag.name,
|
updateData.platforms["windows-x86_64"].url = browser_download_url;
|
||||||
});
|
|
||||||
|
|
||||||
const updateData = {
|
|
||||||
name: tag.name,
|
|
||||||
notes: await resolveUpdateLog(tag.name).catch(() =>
|
|
||||||
resolveUpdateLogDefault().catch(() => "No changelog available"),
|
|
||||||
),
|
|
||||||
pub_date: new Date().toISOString(),
|
|
||||||
platforms: {
|
|
||||||
win64: { signature: "", url: "" }, // compatible with older formats
|
|
||||||
linux: { signature: "", url: "" }, // compatible with older formats
|
|
||||||
darwin: { signature: "", url: "" }, // compatible with older formats
|
|
||||||
"darwin-aarch64": { signature: "", url: "" },
|
|
||||||
"darwin-intel": { signature: "", url: "" },
|
|
||||||
"darwin-x86_64": { signature: "", url: "" },
|
|
||||||
"linux-x86_64": { signature: "", url: "" },
|
|
||||||
"linux-x86": { signature: "", url: "" },
|
|
||||||
"linux-i686": { signature: "", url: "" },
|
|
||||||
"linux-aarch64": { signature: "", url: "" },
|
|
||||||
"linux-armv7": { signature: "", url: "" },
|
|
||||||
"windows-x86_64": { signature: "", url: "" },
|
|
||||||
"windows-aarch64": { signature: "", url: "" },
|
|
||||||
"windows-x86": { signature: "", url: "" },
|
|
||||||
"windows-i686": { signature: "", url: "" },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const promises = release.assets.map(async (asset) => {
|
|
||||||
const { name, browser_download_url } = asset;
|
|
||||||
|
|
||||||
// Process all the platform URL and signature data
|
|
||||||
// win64 url
|
|
||||||
if (name.endsWith("x64-setup.exe")) {
|
|
||||||
updateData.platforms.win64.url = browser_download_url;
|
|
||||||
updateData.platforms["windows-x86_64"].url = browser_download_url;
|
|
||||||
}
|
|
||||||
// win64 signature
|
|
||||||
if (name.endsWith("x64-setup.exe.sig")) {
|
|
||||||
const sig = await getSignature(browser_download_url);
|
|
||||||
updateData.platforms.win64.signature = sig;
|
|
||||||
updateData.platforms["windows-x86_64"].signature = sig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// win32 url
|
|
||||||
if (name.endsWith("x86-setup.exe")) {
|
|
||||||
updateData.platforms["windows-x86"].url = browser_download_url;
|
|
||||||
updateData.platforms["windows-i686"].url = browser_download_url;
|
|
||||||
}
|
|
||||||
// win32 signature
|
|
||||||
if (name.endsWith("x86-setup.exe.sig")) {
|
|
||||||
const sig = await getSignature(browser_download_url);
|
|
||||||
updateData.platforms["windows-x86"].signature = sig;
|
|
||||||
updateData.platforms["windows-i686"].signature = sig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// win arm url
|
|
||||||
if (name.endsWith("arm64-setup.exe")) {
|
|
||||||
updateData.platforms["windows-aarch64"].url = browser_download_url;
|
|
||||||
}
|
|
||||||
// win arm signature
|
|
||||||
if (name.endsWith("arm64-setup.exe.sig")) {
|
|
||||||
const sig = await getSignature(browser_download_url);
|
|
||||||
updateData.platforms["windows-aarch64"].signature = sig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// darwin url (intel)
|
|
||||||
if (name.endsWith(".app.tar.gz") && !name.includes("aarch")) {
|
|
||||||
updateData.platforms.darwin.url = browser_download_url;
|
|
||||||
updateData.platforms["darwin-intel"].url = browser_download_url;
|
|
||||||
updateData.platforms["darwin-x86_64"].url = browser_download_url;
|
|
||||||
}
|
|
||||||
// darwin signature (intel)
|
|
||||||
if (name.endsWith(".app.tar.gz.sig") && !name.includes("aarch")) {
|
|
||||||
const sig = await getSignature(browser_download_url);
|
|
||||||
updateData.platforms.darwin.signature = sig;
|
|
||||||
updateData.platforms["darwin-intel"].signature = sig;
|
|
||||||
updateData.platforms["darwin-x86_64"].signature = sig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// darwin url (aarch)
|
|
||||||
if (name.endsWith("aarch64.app.tar.gz")) {
|
|
||||||
updateData.platforms["darwin-aarch64"].url = browser_download_url;
|
|
||||||
// 使linux可以检查更新
|
|
||||||
updateData.platforms.linux.url = browser_download_url;
|
|
||||||
updateData.platforms["linux-x86_64"].url = browser_download_url;
|
|
||||||
updateData.platforms["linux-x86"].url = browser_download_url;
|
|
||||||
updateData.platforms["linux-i686"].url = browser_download_url;
|
|
||||||
updateData.platforms["linux-aarch64"].url = browser_download_url;
|
|
||||||
updateData.platforms["linux-armv7"].url = browser_download_url;
|
|
||||||
}
|
|
||||||
// darwin signature (aarch)
|
|
||||||
if (name.endsWith("aarch64.app.tar.gz.sig")) {
|
|
||||||
const sig = await getSignature(browser_download_url);
|
|
||||||
updateData.platforms["darwin-aarch64"].signature = sig;
|
|
||||||
updateData.platforms.linux.signature = sig;
|
|
||||||
updateData.platforms["linux-x86_64"].signature = sig;
|
|
||||||
updateData.platforms["linux-x86"].url = browser_download_url;
|
|
||||||
updateData.platforms["linux-i686"].url = browser_download_url;
|
|
||||||
updateData.platforms["linux-aarch64"].signature = sig;
|
|
||||||
updateData.platforms["linux-armv7"].signature = sig;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.allSettled(promises);
|
|
||||||
console.log(updateData);
|
|
||||||
|
|
||||||
// maybe should test the signature as well
|
|
||||||
// delete the null field
|
|
||||||
Object.entries(updateData.platforms).forEach(([key, value]) => {
|
|
||||||
if (!value.url) {
|
|
||||||
console.log(`[Error]: failed to parse release for "${key}"`);
|
|
||||||
delete updateData.platforms[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generate a proxy update file for accelerated GitHub resources
|
|
||||||
const updateDataNew = JSON.parse(JSON.stringify(updateData));
|
|
||||||
|
|
||||||
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
|
|
||||||
if (value.url) {
|
|
||||||
updateDataNew.platforms[key].url =
|
|
||||||
"https://download.clashverge.dev/" + value.url;
|
|
||||||
} else {
|
|
||||||
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get the appropriate updater release based on isAlpha flag
|
|
||||||
const releaseTag = isAlpha ? ALPHA_TAG_NAME : UPDATE_TAG_NAME;
|
|
||||||
console.log(
|
|
||||||
`Processing ${isAlpha ? "alpha" : "stable"} release:`,
|
|
||||||
releaseTag,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let updateRelease;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Try to get the existing release
|
|
||||||
const response = await github.rest.repos.getReleaseByTag({
|
|
||||||
...options,
|
|
||||||
tag: releaseTag,
|
|
||||||
});
|
|
||||||
updateRelease = response.data;
|
|
||||||
console.log(
|
|
||||||
`Found existing ${releaseTag} release with ID: ${updateRelease.id}`,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
// If release doesn't exist, create it
|
|
||||||
if (error.status === 404) {
|
|
||||||
console.log(
|
|
||||||
`Release with tag ${releaseTag} not found, creating new release...`,
|
|
||||||
);
|
|
||||||
const createResponse = await github.rest.repos.createRelease({
|
|
||||||
...options,
|
|
||||||
tag_name: releaseTag,
|
|
||||||
name: isAlpha
|
|
||||||
? "Auto-update Alpha Channel"
|
|
||||||
: "Auto-update Stable Channel",
|
|
||||||
body: `This release contains the update information for ${isAlpha ? "alpha" : "stable"} channel.`,
|
|
||||||
prerelease: isAlpha,
|
|
||||||
});
|
|
||||||
updateRelease = createResponse.data;
|
|
||||||
console.log(
|
|
||||||
`Created new ${releaseTag} release with ID: ${updateRelease.id}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// If it's another error, throw it
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// File names based on release type
|
|
||||||
const jsonFile = isAlpha ? ALPHA_UPDATE_JSON_FILE : UPDATE_JSON_FILE;
|
|
||||||
const proxyFile = isAlpha ? ALPHA_UPDATE_JSON_PROXY : UPDATE_JSON_PROXY;
|
|
||||||
|
|
||||||
// Delete existing assets with these names
|
|
||||||
for (let asset of updateRelease.assets) {
|
|
||||||
if (asset.name === jsonFile) {
|
|
||||||
await github.rest.repos.deleteReleaseAsset({
|
|
||||||
...options,
|
|
||||||
asset_id: asset.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (asset.name === proxyFile) {
|
|
||||||
await github.rest.repos
|
|
||||||
.deleteReleaseAsset({ ...options, asset_id: asset.id })
|
|
||||||
.catch(console.error); // do not break the pipeline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload new assets
|
|
||||||
await github.rest.repos.uploadReleaseAsset({
|
|
||||||
...options,
|
|
||||||
release_id: updateRelease.id,
|
|
||||||
name: jsonFile,
|
|
||||||
data: JSON.stringify(updateData, null, 2),
|
|
||||||
});
|
|
||||||
|
|
||||||
await github.rest.repos.uploadReleaseAsset({
|
|
||||||
...options,
|
|
||||||
release_id: updateRelease.id,
|
|
||||||
name: proxyFile,
|
|
||||||
data: JSON.stringify(updateDataNew, null, 2),
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Successfully uploaded ${isAlpha ? "alpha" : "stable"} update files to ${releaseTag}`,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
`Failed to process ${isAlpha ? "alpha" : "stable"} release:`,
|
|
||||||
error.message,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
// win64 signature
|
||||||
if (error.status === 404) {
|
if (name.endsWith("x64-setup.nsis.zip.sig")) {
|
||||||
console.log(`Release not found for tag: ${tag.name}, skipping...`);
|
const sig = await getSignature(browser_download_url);
|
||||||
|
updateData.platforms.win64.signature = sig;
|
||||||
|
updateData.platforms["windows-x86_64"].signature = sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// win arm url
|
||||||
|
if (name.endsWith("arm64-setup.nsis.zip")) {
|
||||||
|
updateData.platforms["windows-aarch64"].url = browser_download_url;
|
||||||
|
}
|
||||||
|
// win arm signature
|
||||||
|
if (name.endsWith("arm64-setup.nsis.zip.sig")) {
|
||||||
|
const sig = await getSignature(browser_download_url);
|
||||||
|
updateData.platforms["windows-aarch64"].signature = sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// darwin url (intel)
|
||||||
|
if (name.endsWith(".app.tar.gz") && !name.includes("aarch")) {
|
||||||
|
updateData.platforms.darwin.url = browser_download_url;
|
||||||
|
updateData.platforms["darwin-intel"].url = browser_download_url;
|
||||||
|
updateData.platforms["darwin-x86_64"].url = browser_download_url;
|
||||||
|
}
|
||||||
|
// darwin signature (intel)
|
||||||
|
if (name.endsWith(".app.tar.gz.sig") && !name.includes("aarch")) {
|
||||||
|
const sig = await getSignature(browser_download_url);
|
||||||
|
updateData.platforms.darwin.signature = sig;
|
||||||
|
updateData.platforms["darwin-intel"].signature = sig;
|
||||||
|
updateData.platforms["darwin-x86_64"].signature = sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// darwin url (aarch)
|
||||||
|
if (name.endsWith("aarch64.app.tar.gz")) {
|
||||||
|
updateData.platforms["darwin-aarch64"].url = browser_download_url;
|
||||||
|
}
|
||||||
|
// darwin signature (aarch)
|
||||||
|
if (name.endsWith("aarch64.app.tar.gz.sig")) {
|
||||||
|
const sig = await getSignature(browser_download_url);
|
||||||
|
updateData.platforms["darwin-aarch64"].signature = sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// linux x64 url
|
||||||
|
if (name.endsWith("amd64.AppImage.tar.gz")) {
|
||||||
|
updateData.platforms.linux.url = browser_download_url;
|
||||||
|
updateData.platforms["linux-x86_64"].url = browser_download_url;
|
||||||
|
// 暂时使用x64版本的url和sig,使得可以检查更新,但aarch64版本还不支持构建appimage
|
||||||
|
updateData.platforms["linux-aarch64"].url = browser_download_url;
|
||||||
|
updateData.platforms["linux-armv7"].url = browser_download_url;
|
||||||
|
}
|
||||||
|
// linux x64 signature
|
||||||
|
if (name.endsWith("amd64.AppImage.tar.gz.sig")) {
|
||||||
|
const sig = await getSignature(browser_download_url);
|
||||||
|
updateData.platforms.linux.signature = sig;
|
||||||
|
updateData.platforms["linux-x86_64"].signature = sig;
|
||||||
|
// 暂时使用x64版本的url和sig,使得可以检查更新,但aarch64版本还不支持构建appimage
|
||||||
|
updateData.platforms["linux-aarch64"].signature = sig;
|
||||||
|
updateData.platforms["linux-armv7"].signature = sig;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.allSettled(promises);
|
||||||
|
console.log(updateData);
|
||||||
|
|
||||||
|
// maybe should test the signature as well
|
||||||
|
// delete the null field
|
||||||
|
Object.entries(updateData.platforms).forEach(([key, value]) => {
|
||||||
|
if (!value.url) {
|
||||||
|
console.log(`[Error]: failed to parse release for "${key}"`);
|
||||||
|
delete updateData.platforms[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成一个代理github的更新文件
|
||||||
|
// 使用 https://hub.fastgit.xyz/ 做github资源的加速
|
||||||
|
const updateDataNew = JSON.parse(JSON.stringify(updateData));
|
||||||
|
|
||||||
|
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
|
||||||
|
if (value.url) {
|
||||||
|
updateDataNew.platforms[key].url =
|
||||||
|
"https://mirror.ghproxy.com/" + value.url;
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
|
||||||
`Failed to get release for tag: ${tag.name}`,
|
}
|
||||||
error.message,
|
});
|
||||||
);
|
|
||||||
|
// update the update.json
|
||||||
|
const { data: updateRelease } = await github.rest.repos.getReleaseByTag({
|
||||||
|
...options,
|
||||||
|
tag: UPDATE_TAG_NAME,
|
||||||
|
});
|
||||||
|
|
||||||
|
// delete the old assets
|
||||||
|
for (let asset of updateRelease.assets) {
|
||||||
|
if (asset.name === UPDATE_JSON_FILE) {
|
||||||
|
await github.rest.repos.deleteReleaseAsset({
|
||||||
|
...options,
|
||||||
|
asset_id: asset.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asset.name === UPDATE_JSON_PROXY) {
|
||||||
|
await github.rest.repos
|
||||||
|
.deleteReleaseAsset({ ...options, asset_id: asset.id })
|
||||||
|
.catch(console.error); // do not break the pipeline
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// upload new assets
|
||||||
|
await github.rest.repos.uploadReleaseAsset({
|
||||||
|
...options,
|
||||||
|
release_id: updateRelease.id,
|
||||||
|
name: UPDATE_JSON_FILE,
|
||||||
|
data: JSON.stringify(updateData, null, 2),
|
||||||
|
});
|
||||||
|
|
||||||
|
await github.rest.repos.uploadReleaseAsset({
|
||||||
|
...options,
|
||||||
|
release_id: updateRelease.id,
|
||||||
|
name: UPDATE_JSON_PROXY,
|
||||||
|
data: JSON.stringify(updateDataNew, null, 2),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the signature file content
|
// get the signature file content
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import clc from "cli-color";
|
|
||||||
|
|
||||||
export const log_success = (msg, ...optionalParams) =>
|
|
||||||
console.log(clc.green(msg), ...optionalParams);
|
|
||||||
export const log_error = (msg, ...optionalParams) =>
|
|
||||||
console.log(clc.red(msg), ...optionalParams);
|
|
||||||
export const log_info = (msg, ...optionalParams) =>
|
|
||||||
console.log(clc.bgBlue(msg), ...optionalParams);
|
|
||||||
var debugMsg = clc.xterm(245);
|
|
||||||
export const log_debug = (msg, ...optionalParams) =>
|
|
||||||
console.log(debugMsg(msg), ...optionalParams);
|
|
@ -1 +0,0 @@
|
|||||||
avoid-breaking-exported-api = true
|
|
2
src-tauri/.gitignore
vendored
@ -1,8 +1,6 @@
|
|||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
/target/
|
/target/
|
||||||
gen/
|
|
||||||
WixTools
|
WixTools
|
||||||
resources
|
resources
|
||||||
sidecar
|
sidecar
|
||||||
|
|
||||||
|
6671
src-tauri/Cargo.lock
generated
118
src-tauri/Cargo.toml
Executable file → Normal file
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "clash-verge"
|
name = "clash-verge"
|
||||||
version = "2.2.3"
|
version = "1.5.2"
|
||||||
description = "clash verge"
|
description = "clash verge"
|
||||||
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
@ -9,85 +9,45 @@ default-run = "clash-verge"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[package.metadata.bundle]
|
|
||||||
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.1.0", features = [] }
|
tauri-build = { version = "1", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
warp = "0.3"
|
warp = "0.3"
|
||||||
anyhow = "1.0.97"
|
which = "6.0.0"
|
||||||
dirs = "6.0"
|
anyhow = "1.0"
|
||||||
open = "5.3"
|
dirs = "5.0"
|
||||||
|
open = "5.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
ctrlc = "3.4"
|
||||||
dunce = "1.0"
|
dunce = "1.0"
|
||||||
log4rs = "1"
|
log4rs = "1"
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
chrono = "0.4.40"
|
chrono = "0.4"
|
||||||
sysinfo = "0.34"
|
sysinfo = "0.30"
|
||||||
boa_engine = "0.20.0"
|
rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
once_cell = "1.21.3"
|
once_cell = "1.18"
|
||||||
port_scanner = "0.1.5"
|
port_scanner = "0.1.5"
|
||||||
delay_timer = "0.11.6"
|
delay_timer = "0.11.5"
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
percent-encoding = "2.3.1"
|
percent-encoding = "2.3.1"
|
||||||
tokio = { version = "1.44", features = [
|
window-shadows = { version = "0.2" }
|
||||||
"rt-multi-thread",
|
tokio = { version = "1", features = ["full"] }
|
||||||
"macros",
|
|
||||||
"time",
|
|
||||||
"sync",
|
|
||||||
] }
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
reqwest = { version = "0.12", features = ["json", "rustls-tls", "cookies"] }
|
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
||||||
regex = "1.11.1"
|
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
|
||||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", rev = "3d748b5" }
|
auto-launch = { git="https://github.com/zzzgydi/auto-launch", branch = "main" }
|
||||||
image = "0.25.6"
|
tauri = { version = "1.5", features = [ "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
|
||||||
imageproc = "0.25.0"
|
|
||||||
tauri = { version = "2.4.0", features = [
|
|
||||||
"protocol-asset",
|
|
||||||
"devtools",
|
|
||||||
"tray-icon",
|
|
||||||
"image-ico",
|
|
||||||
"image-png",
|
|
||||||
] }
|
|
||||||
network-interface = { version = "2.0.1", features = ["serde"] }
|
|
||||||
tauri-plugin-shell = "2.2.0"
|
|
||||||
tauri-plugin-dialog = "2.2.0"
|
|
||||||
tauri-plugin-fs = "2.2.0"
|
|
||||||
tauri-plugin-process = "2.2.0"
|
|
||||||
tauri-plugin-clipboard-manager = "2.2.2"
|
|
||||||
tauri-plugin-deep-link = "2.2.0"
|
|
||||||
tauri-plugin-devtools = "2.0.0"
|
|
||||||
zip = "2.5.0"
|
|
||||||
reqwest_dav = "0.1.15"
|
|
||||||
aes-gcm = { version = "0.10.3", features = ["std"] }
|
|
||||||
base64 = "0.22.1"
|
|
||||||
getrandom = "0.3.2"
|
|
||||||
tokio-tungstenite = "0.26.2"
|
|
||||||
futures = "0.3"
|
|
||||||
sys-locale = "0.3.2"
|
|
||||||
async-trait = "0.1.88"
|
|
||||||
mihomo_api = { path = "src_crates/crate_mihomo_api" }
|
|
||||||
ab_glyph = "0.2.29"
|
|
||||||
tungstenite = "0.26.2"
|
|
||||||
libc = "0.2"
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
runas = "=1.2.0"
|
runas = "=1.0.0" # 高版本会返回错误 Status
|
||||||
deelevate = "0.2.0"
|
deelevate = "0.2.0"
|
||||||
winreg = "0.55.0"
|
winreg = "0.52.0"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
users = "0.11.0"
|
#openssl
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
|
||||||
tauri-plugin-autostart = "2.2.0"
|
|
||||||
tauri-plugin-global-shortcut = "2.2.0"
|
|
||||||
tauri-plugin-updater = "2.6.1"
|
|
||||||
tauri-plugin-window-state = "2.2.1"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["custom-protocol"]
|
default = ["custom-protocol"]
|
||||||
@ -99,39 +59,3 @@ panic = "abort"
|
|||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
lto = true
|
lto = true
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
strip = true
|
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
incremental = true
|
|
||||||
|
|
||||||
[profile.fast-release]
|
|
||||||
inherits = "release" # 继承 release 的配置
|
|
||||||
panic = "abort" # 与 release 相同
|
|
||||||
codegen-units = 256 # 增加编译单元,提升编译速度
|
|
||||||
lto = false # 禁用 LTO,提升编译速度
|
|
||||||
opt-level = 0 # 禁用优化,大幅提升编译速度
|
|
||||||
debug = true # 保留调试信息
|
|
||||||
strip = false # 不剥离符号,保留调试信息
|
|
||||||
|
|
||||||
[profile.fast-dev]
|
|
||||||
inherits = "dev" # 继承 dev 的配置
|
|
||||||
codegen-units = 256 # 增加编译单元,提升编译速度
|
|
||||||
opt-level = 0 # 禁用优化,进一步提升编译速度
|
|
||||||
incremental = true # 启用增量编译
|
|
||||||
debug = true # 保留调试信息
|
|
||||||
strip = false # 不剥离符号,保留调试信息
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "app_lib"
|
|
||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
tempfile = "3.19.1"
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
members = ["src_crates/crate_mihomo_api"]
|
|
||||||
|
|
||||||
# [patch.crates-io]
|
|
||||||
# bitflags = { git = "https://github.com/bitflags/bitflags", rev = "2.9.0" }
|
|
||||||
# zerocopy = { git = "https://github.com/google/zerocopy", rev = "v0.8.24" }
|
|
||||||
# tungstenite = { git = "https://github.com/snapview/tungstenite-rs", rev = "v0.26.2" }
|
|
||||||
|
17
src-tauri/Info.plist
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>Clash Verge</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>clash</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"identifier": "desktop-capability",
|
|
||||||
"platforms": ["macOS", "windows", "linux"],
|
|
||||||
"webviews": ["main"],
|
|
||||||
"windows": ["main"],
|
|
||||||
"permissions": [
|
|
||||||
"global-shortcut:default",
|
|
||||||
"updater:default",
|
|
||||||
"dialog:default",
|
|
||||||
"dialog:allow-ask",
|
|
||||||
"dialog:allow-message",
|
|
||||||
"updater:default",
|
|
||||||
"updater:allow-check",
|
|
||||||
"updater:allow-download-and-install",
|
|
||||||
"process:allow-restart",
|
|
||||||
"deep-link:default",
|
|
||||||
"window-state:default",
|
|
||||||
"window-state:default",
|
|
||||||
"autostart:default"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
{
|
|
||||||
"identifier": "migrated",
|
|
||||||
"description": "permissions that were migrated from v1",
|
|
||||||
"local": true,
|
|
||||||
"windows": ["main"],
|
|
||||||
"permissions": [
|
|
||||||
"core:default",
|
|
||||||
"fs:allow-read-file",
|
|
||||||
"fs:allow-exists",
|
|
||||||
{
|
|
||||||
"identifier": "fs:scope",
|
|
||||||
"allow": ["$APPDATA/**", "$RESOURCE/../**", "**"]
|
|
||||||
},
|
|
||||||
"fs:allow-write-file",
|
|
||||||
{
|
|
||||||
"identifier": "fs:scope",
|
|
||||||
"allow": ["$APPDATA/**", "$RESOURCE/../**", "**"]
|
|
||||||
},
|
|
||||||
"fs:allow-app-read",
|
|
||||||
"fs:allow-app-read-recursive",
|
|
||||||
"fs:allow-appcache-read",
|
|
||||||
"fs:allow-appcache-read-recursive",
|
|
||||||
"fs:allow-appconfig-read",
|
|
||||||
"fs:allow-appconfig-read-recursive",
|
|
||||||
"core:window:allow-create",
|
|
||||||
"core:window:allow-center",
|
|
||||||
"core:window:allow-request-user-attention",
|
|
||||||
"core:window:allow-set-resizable",
|
|
||||||
"core:window:allow-set-maximizable",
|
|
||||||
"core:window:allow-set-minimizable",
|
|
||||||
"core:window:allow-set-closable",
|
|
||||||
"core:window:allow-set-title",
|
|
||||||
"core:window:allow-maximize",
|
|
||||||
"core:window:allow-unmaximize",
|
|
||||||
"core:window:allow-minimize",
|
|
||||||
"core:window:allow-unminimize",
|
|
||||||
"core:window:allow-show",
|
|
||||||
"core:window:allow-hide",
|
|
||||||
"core:window:allow-close",
|
|
||||||
"core:window:allow-set-decorations",
|
|
||||||
"core:window:allow-set-always-on-top",
|
|
||||||
"core:window:allow-set-content-protected",
|
|
||||||
"core:window:allow-set-size",
|
|
||||||
"core:window:allow-set-min-size",
|
|
||||||
"core:window:allow-set-max-size",
|
|
||||||
"core:window:allow-set-position",
|
|
||||||
"core:window:allow-set-fullscreen",
|
|
||||||
"core:window:allow-set-focus",
|
|
||||||
"core:window:allow-set-icon",
|
|
||||||
"core:window:allow-set-skip-taskbar",
|
|
||||||
"core:window:allow-set-cursor-grab",
|
|
||||||
"core:window:allow-set-cursor-visible",
|
|
||||||
"core:window:allow-set-cursor-icon",
|
|
||||||
"core:window:allow-set-cursor-position",
|
|
||||||
"core:window:allow-set-ignore-cursor-events",
|
|
||||||
"core:window:allow-start-dragging",
|
|
||||||
"core:window:allow-maximize",
|
|
||||||
"core:window:allow-toggle-maximize",
|
|
||||||
"core:window:allow-unmaximize",
|
|
||||||
"core:window:allow-minimize",
|
|
||||||
"core:window:allow-unminimize",
|
|
||||||
"core:window:allow-set-maximizable",
|
|
||||||
"core:window:allow-set-minimizable",
|
|
||||||
"core:webview:allow-print",
|
|
||||||
"shell:allow-execute",
|
|
||||||
"shell:allow-open",
|
|
||||||
"shell:allow-kill",
|
|
||||||
"shell:allow-spawn",
|
|
||||||
"shell:allow-stdin-write",
|
|
||||||
"dialog:allow-open",
|
|
||||||
"global-shortcut:allow-is-registered",
|
|
||||||
"global-shortcut:allow-register",
|
|
||||||
"global-shortcut:allow-register-all",
|
|
||||||
"global-shortcut:allow-unregister",
|
|
||||||
"global-shortcut:allow-unregister-all",
|
|
||||||
"process:allow-restart",
|
|
||||||
"process:allow-exit",
|
|
||||||
"clipboard-manager:allow-read-text",
|
|
||||||
"clipboard-manager:allow-write-text",
|
|
||||||
"shell:default",
|
|
||||||
"dialog:default"
|
|
||||||
]
|
|
||||||
}
|
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
src-tauri/icons/icon-new.icns
Normal file
BIN
src-tauri/icons/icon-shrink.png
Normal file
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 65 KiB |
BIN
src-tauri/icons/mac-tray-icon-sys.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
src-tauri/icons/mac-tray-icon-tun.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src-tauri/icons/mac-tray-icon.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 47 KiB |
BIN
src-tauri/icons/tray-icon-sys.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 54 KiB |
BIN
src-tauri/icons/tray-icon-tun.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 4.2 KiB |
BIN
src-tauri/icons/tray-icon.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 22 KiB |
@ -1,4 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
chmod +x /usr/bin/install-service
|
|
||||||
chmod +x /usr/bin/uninstall-service
|
|
||||||
chmod +x /usr/bin/clash-verge-service
|
|
@ -1,2 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
/usr/bin/uninstall-service
|
|
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>com.apple.security.app-sandbox</key>
|
|
||||||
<false/>
|
|
||||||
<key>com.apple.security.application-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>io.github.clash-verge-rev.clash-verge-rev</string>
|
|
||||||
</array>
|
|
||||||
<key>com.apple.security.inherit</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,216 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::{
|
|
||||||
feat, logging,
|
|
||||||
utils::{dirs, logging::Type},
|
|
||||||
wrap_err,
|
|
||||||
};
|
|
||||||
use tauri::Manager;
|
|
||||||
|
|
||||||
/// 打开应用程序所在目录
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn open_app_dir() -> CmdResult<()> {
|
|
||||||
let app_dir = wrap_err!(dirs::app_home_dir())?;
|
|
||||||
wrap_err!(open::that(app_dir))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 打开核心所在目录
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn open_core_dir() -> CmdResult<()> {
|
|
||||||
let core_dir = wrap_err!(tauri::utils::platform::current_exe())?;
|
|
||||||
let core_dir = core_dir.parent().ok_or("failed to get core dir")?;
|
|
||||||
wrap_err!(open::that(core_dir))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 打开日志目录
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn open_logs_dir() -> CmdResult<()> {
|
|
||||||
let log_dir = wrap_err!(dirs::app_logs_dir())?;
|
|
||||||
wrap_err!(open::that(log_dir))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 打开网页链接
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn open_web_url(url: String) -> CmdResult<()> {
|
|
||||||
wrap_err!(open::that(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 打开/关闭开发者工具
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn open_devtools(app_handle: tauri::AppHandle) {
|
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
|
||||||
if !window.is_devtools_open() {
|
|
||||||
window.open_devtools();
|
|
||||||
} else {
|
|
||||||
window.close_devtools();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 退出应用
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn exit_app() {
|
|
||||||
feat::quit(Some(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 重启应用
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn restart_app() -> CmdResult<()> {
|
|
||||||
feat::restart_app();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取便携版标识
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_portable_flag() -> CmdResult<bool> {
|
|
||||||
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取应用目录
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_app_dir() -> CmdResult<String> {
|
|
||||||
let app_home_dir = wrap_err!(dirs::app_home_dir())?
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
Ok(app_home_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取当前自启动状态
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_auto_launch_status() -> CmdResult<bool> {
|
|
||||||
use crate::core::sysopt::Sysopt;
|
|
||||||
wrap_err!(Sysopt::global().get_launch_status())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 下载图标缓存
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
|
|
||||||
let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
|
|
||||||
let icon_path = icon_cache_dir.join(&name);
|
|
||||||
|
|
||||||
// 如果文件已存在,直接返回路径
|
|
||||||
if icon_path.exists() {
|
|
||||||
return Ok(icon_path.to_string_lossy().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保缓存目录存在
|
|
||||||
if !icon_cache_dir.exists() {
|
|
||||||
let _ = std::fs::create_dir_all(&icon_cache_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用临时文件名来下载
|
|
||||||
let temp_path = icon_cache_dir.join(format!("{}.downloading", &name));
|
|
||||||
|
|
||||||
// 下载文件到临时位置
|
|
||||||
let response = wrap_err!(reqwest::get(&url).await)?;
|
|
||||||
|
|
||||||
// 检查内容类型是否为图片
|
|
||||||
let content_type = response
|
|
||||||
.headers()
|
|
||||||
.get(reqwest::header::CONTENT_TYPE)
|
|
||||||
.and_then(|v| v.to_str().ok())
|
|
||||||
.unwrap_or("");
|
|
||||||
|
|
||||||
let is_image = content_type.starts_with("image/");
|
|
||||||
|
|
||||||
// 获取响应内容
|
|
||||||
let content = wrap_err!(response.bytes().await)?;
|
|
||||||
|
|
||||||
// 检查内容是否为HTML (针对CDN错误页面)
|
|
||||||
let is_html = content.len() > 15
|
|
||||||
&& (content.starts_with(b"<!DOCTYPE html")
|
|
||||||
|| content.starts_with(b"<html")
|
|
||||||
|| content.starts_with(b"<?xml"));
|
|
||||||
|
|
||||||
// 只有当内容确实是图片时才保存
|
|
||||||
if is_image && !is_html {
|
|
||||||
{
|
|
||||||
let mut file = match std::fs::File::create(&temp_path) {
|
|
||||||
Ok(file) => file,
|
|
||||||
Err(_) => {
|
|
||||||
if icon_path.exists() {
|
|
||||||
return Ok(icon_path.to_string_lossy().to_string());
|
|
||||||
} else {
|
|
||||||
return Err("Failed to create temporary file".into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 再次检查目标文件是否已存在,避免重命名覆盖其他线程已完成的文件
|
|
||||||
if !icon_path.exists() {
|
|
||||||
match std::fs::rename(&temp_path, &icon_path) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(_) => {
|
|
||||||
let _ = std::fs::remove_file(&temp_path);
|
|
||||||
if icon_path.exists() {
|
|
||||||
return Ok(icon_path.to_string_lossy().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = std::fs::remove_file(&temp_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(icon_path.to_string_lossy().to_string())
|
|
||||||
} else {
|
|
||||||
let _ = std::fs::remove_file(&temp_path);
|
|
||||||
Err(format!("下载的内容不是有效图片: {}", url))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct IconInfo {
|
|
||||||
name: String,
|
|
||||||
previous_t: String,
|
|
||||||
current_t: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 复制图标文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
|
|
||||||
use std::{fs, path::Path};
|
|
||||||
|
|
||||||
let file_path = Path::new(&path);
|
|
||||||
|
|
||||||
let icon_dir = wrap_err!(dirs::app_home_dir())?.join("icons");
|
|
||||||
if !icon_dir.exists() {
|
|
||||||
let _ = fs::create_dir_all(&icon_dir);
|
|
||||||
}
|
|
||||||
let ext = match file_path.extension() {
|
|
||||||
Some(e) => e.to_string_lossy().to_string(),
|
|
||||||
None => "ico".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let dest_path = icon_dir.join(format!(
|
|
||||||
"{0}-{1}.{ext}",
|
|
||||||
icon_info.name, icon_info.current_t
|
|
||||||
));
|
|
||||||
if file_path.exists() {
|
|
||||||
if icon_info.previous_t.trim() != "" {
|
|
||||||
fs::remove_file(
|
|
||||||
icon_dir.join(format!("{0}-{1}.png", icon_info.name, icon_info.previous_t)),
|
|
||||||
)
|
|
||||||
.unwrap_or_default();
|
|
||||||
fs::remove_file(
|
|
||||||
icon_dir.join(format!("{0}-{1}.ico", icon_info.name, icon_info.previous_t)),
|
|
||||||
)
|
|
||||||
.unwrap_or_default();
|
|
||||||
}
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Cmd,
|
|
||||||
true,
|
|
||||||
"Copying icon file path: {:?} -> file dist: {:?}",
|
|
||||||
path,
|
|
||||||
dest_path
|
|
||||||
);
|
|
||||||
match fs::copy(file_path, &dest_path) {
|
|
||||||
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
|
|
||||||
Err(err) => Err(err.to_string()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err("file not found".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,223 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::{config::*, core::*, feat, module::mihomo::MihomoManager, wrap_err};
|
|
||||||
use serde_yaml::Mapping;
|
|
||||||
|
|
||||||
/// 复制Clash环境变量
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn copy_clash_env() -> CmdResult {
|
|
||||||
feat::copy_clash_env();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取Clash信息
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_clash_info() -> CmdResult<ClashInfo> {
|
|
||||||
Ok(Config::clash().latest().get_client_info())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 修改Clash配置
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
|
|
||||||
wrap_err!(feat::patch_clash(payload).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 修改Clash模式
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn patch_clash_mode(payload: String) -> CmdResult {
|
|
||||||
feat::change_clash_mode(payload);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 切换Clash核心
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>> {
|
|
||||||
log::info!(target: "app", "changing core to {clash_core}");
|
|
||||||
|
|
||||||
match CoreManager::global()
|
|
||||||
.change_core(Some(clash_core.clone()))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {
|
|
||||||
log::info!(target: "app", "core changed to {clash_core}");
|
|
||||||
handle::Handle::notice_message("config_core::change_success", &clash_core);
|
|
||||||
handle::Handle::refresh_clash();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
let error_msg = err.to_string();
|
|
||||||
log::error!(target: "app", "failed to change core: {error_msg}");
|
|
||||||
handle::Handle::notice_message("config_core::change_error", &error_msg);
|
|
||||||
Ok(Some(error_msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 重启核心
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn restart_core() -> CmdResult {
|
|
||||||
wrap_err!(CoreManager::global().restart_core().await)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取代理延迟
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn clash_api_get_proxy_delay(
|
|
||||||
name: String,
|
|
||||||
url: Option<String>,
|
|
||||||
timeout: i32,
|
|
||||||
) -> CmdResult<serde_json::Value> {
|
|
||||||
MihomoManager::global()
|
|
||||||
.test_proxy_delay(&name, url, timeout)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 测试URL延迟
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn test_delay(url: String) -> CmdResult<u32> {
|
|
||||||
Ok(feat::test_delay(url).await.unwrap_or(10000u32))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 保存DNS配置到单独文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
|
|
||||||
use crate::utils::dirs;
|
|
||||||
use serde_yaml;
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
// 获取DNS配置文件路径
|
|
||||||
let dns_path = dirs::app_home_dir()
|
|
||||||
.map_err(|e| e.to_string())?
|
|
||||||
.join("dns_config.yaml");
|
|
||||||
|
|
||||||
// 保存DNS配置到文件
|
|
||||||
let yaml_str = serde_yaml::to_string(&dns_config).map_err(|e| e.to_string())?;
|
|
||||||
fs::write(&dns_path, yaml_str).map_err(|e| e.to_string())?;
|
|
||||||
log::info!(target: "app", "DNS config saved to {:?}", dns_path);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 应用或撤销DNS配置
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn apply_dns_config(apply: bool) -> CmdResult {
|
|
||||||
use crate::{
|
|
||||||
config::Config,
|
|
||||||
core::{handle, CoreManager},
|
|
||||||
utils::dirs,
|
|
||||||
};
|
|
||||||
use tauri::async_runtime;
|
|
||||||
|
|
||||||
// 使用spawn来处理异步操作
|
|
||||||
async_runtime::spawn(async move {
|
|
||||||
if apply {
|
|
||||||
// 读取DNS配置文件
|
|
||||||
let dns_path = match dirs::app_home_dir() {
|
|
||||||
Ok(path) => path.join("dns_config.yaml"),
|
|
||||||
Err(e) => {
|
|
||||||
log::error!(target: "app", "Failed to get home dir: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !dns_path.exists() {
|
|
||||||
log::warn!(target: "app", "DNS config file not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dns_yaml = match std::fs::read_to_string(&dns_path) {
|
|
||||||
Ok(content) => content,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!(target: "app", "Failed to read DNS config: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 解析DNS配置并创建patch
|
|
||||||
let patch_config = match serde_yaml::from_str::<serde_yaml::Mapping>(&dns_yaml) {
|
|
||||||
Ok(config) => {
|
|
||||||
let mut patch = serde_yaml::Mapping::new();
|
|
||||||
patch.insert("dns".into(), config.into());
|
|
||||||
patch
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!(target: "app", "Failed to parse DNS config: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
log::info!(target: "app", "Applying DNS config from file");
|
|
||||||
|
|
||||||
// 重新生成配置,确保DNS配置被正确应用
|
|
||||||
// 这里不调用patch_clash以避免将DNS配置写入config.yaml
|
|
||||||
Config::runtime()
|
|
||||||
.latest()
|
|
||||||
.patch_config(patch_config.clone());
|
|
||||||
|
|
||||||
// 首先重新生成配置
|
|
||||||
if let Err(err) = Config::generate().await {
|
|
||||||
log::error!(target: "app", "Failed to regenerate config with DNS: {}", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 然后应用新配置
|
|
||||||
if let Err(err) = CoreManager::global().update_config().await {
|
|
||||||
log::error!(target: "app", "Failed to apply config with DNS: {}", err);
|
|
||||||
} else {
|
|
||||||
log::info!(target: "app", "DNS config successfully applied");
|
|
||||||
handle::Handle::refresh_clash();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 当关闭DNS设置时,不需要对配置进行任何修改
|
|
||||||
// 直接重新生成配置,让enhance函数自动跳过DNS配置的加载
|
|
||||||
log::info!(target: "app", "DNS settings disabled, regenerating config");
|
|
||||||
|
|
||||||
// 重新生成配置
|
|
||||||
if let Err(err) = Config::generate().await {
|
|
||||||
log::error!(target: "app", "Failed to regenerate config: {}", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 应用新配置
|
|
||||||
match CoreManager::global().update_config().await {
|
|
||||||
Ok(_) => {
|
|
||||||
log::info!(target: "app", "Config regenerated successfully");
|
|
||||||
handle::Handle::refresh_clash();
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::error!(target: "app", "Failed to apply regenerated config: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 检查DNS配置文件是否存在
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn check_dns_config_exists() -> CmdResult<bool> {
|
|
||||||
use crate::utils::dirs;
|
|
||||||
|
|
||||||
let dns_path = dirs::app_home_dir()
|
|
||||||
.map_err(|e| e.to_string())?
|
|
||||||
.join("dns_config.yaml");
|
|
||||||
|
|
||||||
Ok(dns_path.exists())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取DNS配置文件内容
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn get_dns_config_content() -> CmdResult<String> {
|
|
||||||
use crate::utils::dirs;
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
let dns_path = dirs::app_home_dir()
|
|
||||||
.map_err(|e| e.to_string())?
|
|
||||||
.join("dns_config.yaml");
|
|
||||||
|
|
||||||
if !dns_path.exists() {
|
|
||||||
return Err("DNS config file not found".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let content = fs::read_to_string(&dns_path).map_err(|e| e.to_string())?;
|
|
||||||
Ok(content)
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
use crate::module::lightweight;
|
|
||||||
|
|
||||||
use super::CmdResult;
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn entry_lightweight_mode() -> CmdResult {
|
|
||||||
lightweight::entry_lightweight_mode();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
// Common result type used by command functions
|
|
||||||
pub type CmdResult<T = ()> = Result<T, String>;
|
|
||||||
|
|
||||||
// Command modules
|
|
||||||
pub mod app;
|
|
||||||
pub mod clash;
|
|
||||||
pub mod lightweight;
|
|
||||||
pub mod media_unlock_checker;
|
|
||||||
pub mod network;
|
|
||||||
pub mod profile;
|
|
||||||
pub mod proxy;
|
|
||||||
pub mod runtime;
|
|
||||||
pub mod save_profile;
|
|
||||||
pub mod service;
|
|
||||||
pub mod system;
|
|
||||||
pub mod uwp;
|
|
||||||
pub mod validate;
|
|
||||||
pub mod verge;
|
|
||||||
pub mod webdav;
|
|
||||||
|
|
||||||
// Re-export all command functions for backwards compatibility
|
|
||||||
pub use app::*;
|
|
||||||
pub use clash::*;
|
|
||||||
pub use lightweight::*;
|
|
||||||
pub use media_unlock_checker::*;
|
|
||||||
pub use network::*;
|
|
||||||
pub use profile::*;
|
|
||||||
pub use proxy::*;
|
|
||||||
pub use runtime::*;
|
|
||||||
pub use save_profile::*;
|
|
||||||
pub use service::*;
|
|
||||||
pub use system::*;
|
|
||||||
pub use uwp::*;
|
|
||||||
pub use validate::*;
|
|
||||||
pub use verge::*;
|
|
||||||
pub use webdav::*;
|
|
@ -1,63 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::wrap_err;
|
|
||||||
use network_interface::NetworkInterface;
|
|
||||||
use serde_yaml::Mapping;
|
|
||||||
use sysproxy::{Autoproxy, Sysproxy};
|
|
||||||
|
|
||||||
/// get the system proxy
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_sys_proxy() -> CmdResult<Mapping> {
|
|
||||||
let current = wrap_err!(Sysproxy::get_system_proxy())?;
|
|
||||||
let mut map = Mapping::new();
|
|
||||||
map.insert("enable".into(), current.enable.into());
|
|
||||||
map.insert(
|
|
||||||
"server".into(),
|
|
||||||
format!("{}:{}", current.host, current.port).into(),
|
|
||||||
);
|
|
||||||
map.insert("bypass".into(), current.bypass.into());
|
|
||||||
|
|
||||||
Ok(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get the system proxy
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_auto_proxy() -> CmdResult<Mapping> {
|
|
||||||
let current = wrap_err!(Autoproxy::get_auto_proxy())?;
|
|
||||||
|
|
||||||
let mut map = Mapping::new();
|
|
||||||
map.insert("enable".into(), current.enable.into());
|
|
||||||
map.insert("url".into(), current.url.into());
|
|
||||||
|
|
||||||
Ok(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取网络接口列表
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_network_interfaces() -> Vec<String> {
|
|
||||||
use sysinfo::Networks;
|
|
||||||
let mut result = Vec::new();
|
|
||||||
let networks = Networks::new_with_refreshed_list();
|
|
||||||
for (interface_name, _) in &networks {
|
|
||||||
result.push(interface_name.clone());
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取网络接口详细信息
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> {
|
|
||||||
use network_interface::{NetworkInterface, NetworkInterfaceConfig};
|
|
||||||
|
|
||||||
let names = get_network_interfaces();
|
|
||||||
let interfaces = wrap_err!(NetworkInterface::show())?;
|
|
||||||
|
|
||||||
let mut result = Vec::new();
|
|
||||||
|
|
||||||
for interface in interfaces {
|
|
||||||
if names.contains(&interface.name) {
|
|
||||||
result.push(interface);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
@ -1,244 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::{
|
|
||||||
config::{Config, IProfiles, PrfItem, PrfOption},
|
|
||||||
core::{handle, tray::Tray, CoreManager},
|
|
||||||
feat, logging, ret_err,
|
|
||||||
utils::{dirs, help, logging::Type},
|
|
||||||
wrap_err,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 获取配置文件列表
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_profiles() -> CmdResult<IProfiles> {
|
|
||||||
let _ = Tray::global().update_menu();
|
|
||||||
Ok(Config::profiles().data().clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 增强配置文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn enhance_profiles() -> CmdResult {
|
|
||||||
wrap_err!(feat::enhance_profiles().await)?;
|
|
||||||
handle::Handle::refresh_clash();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 导入配置文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
|
|
||||||
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
|
|
||||||
wrap_err!(Config::profiles().data().append_item(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 重新排序配置文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
|
|
||||||
wrap_err!(Config::profiles().data().reorder(active_id, over_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 创建配置文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
|
|
||||||
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
|
|
||||||
wrap_err!(Config::profiles().data().append_item(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 更新配置文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
|
|
||||||
wrap_err!(feat::update_profile(index, option).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 删除配置文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn delete_profile(index: String) -> CmdResult {
|
|
||||||
let should_update = wrap_err!({ Config::profiles().data().delete_item(index) })?;
|
|
||||||
if should_update {
|
|
||||||
wrap_err!(CoreManager::global().update_config().await)?;
|
|
||||||
handle::Handle::refresh_clash();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 修改profiles的配置
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
|
||||||
logging!(info, Type::Cmd, true, "开始修改配置文件");
|
|
||||||
|
|
||||||
// 保存当前配置,以便在验证失败时恢复
|
|
||||||
let current_profile = Config::profiles().latest().current.clone();
|
|
||||||
logging!(info, Type::Cmd, true, "当前配置: {:?}", current_profile);
|
|
||||||
|
|
||||||
// 如果要切换配置,先检查目标配置文件是否有语法错误
|
|
||||||
if let Some(new_profile) = profiles.current.as_ref() {
|
|
||||||
if current_profile.as_ref() != Some(new_profile) {
|
|
||||||
logging!(info, Type::Cmd, true, "正在切换到新配置: {}", new_profile);
|
|
||||||
|
|
||||||
// 获取目标配置文件路径
|
|
||||||
let profiles_config = Config::profiles();
|
|
||||||
let profiles_data = profiles_config.latest();
|
|
||||||
let config_file_result = match profiles_data.get_item(new_profile) {
|
|
||||||
Ok(item) => {
|
|
||||||
if let Some(file) = &item.file {
|
|
||||||
let path = dirs::app_profiles_dir().map(|dir| dir.join(file));
|
|
||||||
path.ok()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::Cmd, true, "获取目标配置信息失败: {}", e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果获取到文件路径,检查YAML语法
|
|
||||||
if let Some(file_path) = config_file_result {
|
|
||||||
if !file_path.exists() {
|
|
||||||
logging!(
|
|
||||||
error,
|
|
||||||
Type::Cmd,
|
|
||||||
true,
|
|
||||||
"目标配置文件不存在: {}",
|
|
||||||
file_path.display()
|
|
||||||
);
|
|
||||||
handle::Handle::notice_message(
|
|
||||||
"config_validate::file_not_found",
|
|
||||||
format!("{}", file_path.display()),
|
|
||||||
);
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
match std::fs::read_to_string(&file_path) {
|
|
||||||
Ok(content) => match serde_yaml::from_str::<serde_yaml::Value>(&content) {
|
|
||||||
Ok(_) => {
|
|
||||||
logging!(info, Type::Cmd, true, "目标配置文件语法正确");
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
let error_msg = format!(" {}", err);
|
|
||||||
logging!(
|
|
||||||
error,
|
|
||||||
Type::Cmd,
|
|
||||||
true,
|
|
||||||
"目标配置文件存在YAML语法错误:{}",
|
|
||||||
error_msg
|
|
||||||
);
|
|
||||||
handle::Handle::notice_message(
|
|
||||||
"config_validate::yaml_syntax_error",
|
|
||||||
&error_msg,
|
|
||||||
);
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
let error_msg = format!("无法读取目标配置文件: {}", err);
|
|
||||||
logging!(error, Type::Cmd, true, "{}", error_msg);
|
|
||||||
handle::Handle::notice_message(
|
|
||||||
"config_validate::file_read_error",
|
|
||||||
&error_msg,
|
|
||||||
);
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新profiles配置
|
|
||||||
logging!(info, Type::Cmd, true, "正在更新配置草稿");
|
|
||||||
let _ = Config::profiles().draft().patch_config(profiles);
|
|
||||||
|
|
||||||
// 更新配置并进行验证
|
|
||||||
match CoreManager::global().update_config().await {
|
|
||||||
Ok((true, _)) => {
|
|
||||||
logging!(info, Type::Cmd, true, "配置更新成功");
|
|
||||||
handle::Handle::refresh_clash();
|
|
||||||
let _ = Tray::global().update_tooltip();
|
|
||||||
Config::profiles().apply();
|
|
||||||
wrap_err!(Config::profiles().data().save_file())?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
Ok((false, error_msg)) => {
|
|
||||||
logging!(warn, Type::Cmd, true, "配置验证失败: {}", error_msg);
|
|
||||||
Config::profiles().discard();
|
|
||||||
// 如果验证失败,恢复到之前的配置
|
|
||||||
if let Some(prev_profile) = current_profile {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Cmd,
|
|
||||||
true,
|
|
||||||
"尝试恢复到之前的配置: {}",
|
|
||||||
prev_profile
|
|
||||||
);
|
|
||||||
let restore_profiles = IProfiles {
|
|
||||||
current: Some(prev_profile),
|
|
||||||
items: None,
|
|
||||||
};
|
|
||||||
// 静默恢复,不触发验证
|
|
||||||
wrap_err!({ Config::profiles().draft().patch_config(restore_profiles) })?;
|
|
||||||
Config::profiles().apply();
|
|
||||||
wrap_err!(Config::profiles().data().save_file())?;
|
|
||||||
logging!(info, Type::Cmd, true, "成功恢复到之前的配置");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送验证错误通知
|
|
||||||
handle::Handle::notice_message("config_validate::error", &error_msg);
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
logging!(warn, Type::Cmd, true, "更新过程发生错误: {}", e);
|
|
||||||
Config::profiles().discard();
|
|
||||||
handle::Handle::notice_message("config_validate::boot_error", e.to_string());
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 根据profile name修改profiles
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn patch_profiles_config_by_profile_index(
|
|
||||||
_app_handle: tauri::AppHandle,
|
|
||||||
profile_index: String,
|
|
||||||
) -> CmdResult<bool> {
|
|
||||||
logging!(info, Type::Cmd, true, "切换配置到: {}", profile_index);
|
|
||||||
|
|
||||||
let profiles = IProfiles {
|
|
||||||
current: Some(profile_index),
|
|
||||||
items: None,
|
|
||||||
};
|
|
||||||
patch_profiles_config(profiles).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 修改某个profile item的
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
|
||||||
wrap_err!(Config::profiles().data().patch_item(index, profile))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 查看配置文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult {
|
|
||||||
let file = {
|
|
||||||
wrap_err!(Config::profiles().latest().get_item(&index))?
|
|
||||||
.file
|
|
||||||
.clone()
|
|
||||||
.ok_or("the file field is null")
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let path = wrap_err!(dirs::app_profiles_dir())?.join(file);
|
|
||||||
if !path.exists() {
|
|
||||||
ret_err!("the file not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
wrap_err!(help::open_file(app_handle, path))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 读取配置文件内容
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn read_profile_file(index: String) -> CmdResult<String> {
|
|
||||||
let profiles = Config::profiles();
|
|
||||||
let profiles = profiles.latest();
|
|
||||||
let item = wrap_err!(profiles.get_item(&index))?;
|
|
||||||
let data = wrap_err!(item.read_file())?;
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::module::mihomo::MihomoManager;
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
|
|
||||||
let mannager = MihomoManager::global();
|
|
||||||
|
|
||||||
mannager
|
|
||||||
.refresh_proxies()
|
|
||||||
.await
|
|
||||||
.map(|_| mannager.get_proxies())
|
|
||||||
.or_else(|_| Ok(mannager.get_proxies()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
|
|
||||||
let mannager = MihomoManager::global();
|
|
||||||
|
|
||||||
mannager
|
|
||||||
.refresh_providers_proxies()
|
|
||||||
.await
|
|
||||||
.map(|_| mannager.get_providers_proxies())
|
|
||||||
.or_else(|_| Ok(mannager.get_providers_proxies()))
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::{config::*, wrap_err};
|
|
||||||
use anyhow::Context;
|
|
||||||
use serde_yaml::Mapping;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// 获取运行时配置
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_runtime_config() -> CmdResult<Option<Mapping>> {
|
|
||||||
Ok(Config::runtime().latest().config.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取运行时YAML配置
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_runtime_yaml() -> CmdResult<String> {
|
|
||||||
let runtime = Config::runtime();
|
|
||||||
let runtime = runtime.latest();
|
|
||||||
let config = runtime.config.as_ref();
|
|
||||||
wrap_err!(config
|
|
||||||
.ok_or(anyhow::anyhow!("failed to parse config to yaml file"))
|
|
||||||
.and_then(
|
|
||||||
|config| serde_yaml::to_string(config).context("failed to convert config to yaml")
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取运行时存在的键
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_runtime_exists() -> CmdResult<Vec<String>> {
|
|
||||||
Ok(Config::runtime().latest().exists_keys.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取运行时日志
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
|
|
||||||
Ok(Config::runtime().latest().chain_logs.clone())
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::{config::*, core::*, utils::dirs, wrap_err};
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
/// 保存profiles的配置
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
|
|
||||||
if file_data.is_none() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在异步操作前完成所有文件操作
|
|
||||||
let (file_path, original_content, is_merge_file) = {
|
|
||||||
let profiles = Config::profiles();
|
|
||||||
let profiles_guard = profiles.latest();
|
|
||||||
let item = wrap_err!(profiles_guard.get_item(&index))?;
|
|
||||||
// 确定是否为merge类型文件
|
|
||||||
let is_merge = item.itype.as_ref().is_some_and(|t| t == "merge");
|
|
||||||
let content = wrap_err!(item.read_file())?;
|
|
||||||
let path = item.file.clone().ok_or("file field is null")?;
|
|
||||||
let profiles_dir = wrap_err!(dirs::app_profiles_dir())?;
|
|
||||||
(profiles_dir.join(path), content, is_merge)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 保存新的配置文件
|
|
||||||
wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?;
|
|
||||||
|
|
||||||
let file_path_str = file_path.to_string_lossy().to_string();
|
|
||||||
println!(
|
|
||||||
"[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}",
|
|
||||||
file_path_str, is_merge_file
|
|
||||||
);
|
|
||||||
|
|
||||||
// 对于 merge 文件,只进行语法验证,不进行后续内核验证
|
|
||||||
if is_merge_file {
|
|
||||||
println!("[cmd配置save] 检测到merge文件,只进行语法验证");
|
|
||||||
match CoreManager::global()
|
|
||||||
.validate_config_file(&file_path_str, Some(true))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok((true, _)) => {
|
|
||||||
println!("[cmd配置save] merge文件语法验证通过");
|
|
||||||
// 成功后尝试更新整体配置
|
|
||||||
if let Err(e) = CoreManager::global().update_config().await {
|
|
||||||
println!("[cmd配置save] 更新整体配置时发生错误: {}", e);
|
|
||||||
log::warn!(target: "app", "更新整体配置时发生错误: {}", e);
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Ok((false, error_msg)) => {
|
|
||||||
println!("[cmd配置save] merge文件语法验证失败: {}", error_msg);
|
|
||||||
// 恢复原始配置文件
|
|
||||||
wrap_err!(fs::write(&file_path, original_content))?;
|
|
||||||
// 发送合并文件专用错误通知
|
|
||||||
let result = (false, error_msg.clone());
|
|
||||||
crate::cmd::validate::handle_yaml_validation_notice(&result, "合并配置文件");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("[cmd配置save] 验证过程发生错误: {}", e);
|
|
||||||
// 恢复原始配置文件
|
|
||||||
wrap_err!(fs::write(&file_path, original_content))?;
|
|
||||||
return Err(e.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 非merge文件使用完整验证流程
|
|
||||||
match CoreManager::global()
|
|
||||||
.validate_config_file(&file_path_str, None)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok((true, _)) => {
|
|
||||||
println!("[cmd配置save] 验证成功");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Ok((false, error_msg)) => {
|
|
||||||
println!("[cmd配置save] 验证失败: {}", error_msg);
|
|
||||||
// 恢复原始配置文件
|
|
||||||
wrap_err!(fs::write(&file_path, original_content))?;
|
|
||||||
|
|
||||||
// 智能判断错误类型
|
|
||||||
let is_script_error = file_path_str.ends_with(".js")
|
|
||||||
|| error_msg.contains("Script syntax error")
|
|
||||||
|| error_msg.contains("Script must contain a main function")
|
|
||||||
|| error_msg.contains("Failed to read script file");
|
|
||||||
|
|
||||||
if error_msg.contains("YAML syntax error")
|
|
||||||
|| error_msg.contains("Failed to read file:")
|
|
||||||
|| (!file_path_str.ends_with(".js") && !is_script_error)
|
|
||||||
{
|
|
||||||
// 普通YAML错误使用YAML通知处理
|
|
||||||
println!("[cmd配置save] YAML配置文件验证失败,发送通知");
|
|
||||||
let result = (false, error_msg.clone());
|
|
||||||
crate::cmd::validate::handle_yaml_validation_notice(&result, "YAML配置文件");
|
|
||||||
} else if is_script_error {
|
|
||||||
// 脚本错误使用专门的通知处理
|
|
||||||
println!("[cmd配置save] 脚本文件验证失败,发送通知");
|
|
||||||
let result = (false, error_msg.clone());
|
|
||||||
crate::cmd::validate::handle_script_validation_notice(&result, "脚本文件");
|
|
||||||
} else {
|
|
||||||
// 普通配置错误使用一般通知
|
|
||||||
println!("[cmd配置save] 其他类型验证失败,发送一般通知");
|
|
||||||
handle::Handle::notice_message("config_validate::error", &error_msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("[cmd配置save] 验证过程发生错误: {}", e);
|
|
||||||
// 恢复原始配置文件
|
|
||||||
wrap_err!(fs::write(&file_path, original_content))?;
|
|
||||||
Err(e.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::{
|
|
||||||
core::{service, CoreManager},
|
|
||||||
utils::i18n::t,
|
|
||||||
};
|
|
||||||
|
|
||||||
async fn execute_service_operation(
|
|
||||||
service_op: impl std::future::Future<Output = Result<(), impl ToString + std::fmt::Debug>>,
|
|
||||||
op_type: &str,
|
|
||||||
) -> CmdResult {
|
|
||||||
if service_op.await.is_err() {
|
|
||||||
let emsg = format!("{} {} failed", op_type, "Service");
|
|
||||||
return Err(t(emsg.as_str()));
|
|
||||||
}
|
|
||||||
if CoreManager::global().restart_core().await.is_err() {
|
|
||||||
let emsg = format!("{} {} failed", "Restart", "Core");
|
|
||||||
return Err(t(emsg.as_str()));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn install_service() -> CmdResult {
|
|
||||||
execute_service_operation(service::install_service(), "Install").await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn uninstall_service() -> CmdResult {
|
|
||||||
execute_service_operation(service::uninstall_service(), "Uninstall").await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn reinstall_service() -> CmdResult {
|
|
||||||
execute_service_operation(service::reinstall_service(), "Reinstall").await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn repair_service() -> CmdResult {
|
|
||||||
execute_service_operation(service::force_reinstall_service(), "Repair").await
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::{
|
|
||||||
core::{handle, CoreManager},
|
|
||||||
module::sysinfo::PlatformSpecification,
|
|
||||||
};
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::{
|
|
||||||
sync::atomic::{AtomicI64, Ordering},
|
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
|
||||||
};
|
|
||||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
|
||||||
|
|
||||||
// 存储应用启动时间的全局变量
|
|
||||||
static APP_START_TIME: Lazy<AtomicI64> = Lazy::new(|| {
|
|
||||||
// 获取当前系统时间,转换为毫秒级时间戳
|
|
||||||
let now = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.as_millis() as i64;
|
|
||||||
|
|
||||||
AtomicI64::new(now)
|
|
||||||
});
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn export_diagnostic_info() -> CmdResult<()> {
|
|
||||||
let sysinfo = PlatformSpecification::new_async().await;
|
|
||||||
let info = format!("{:?}", sysinfo);
|
|
||||||
|
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
|
||||||
let cliboard = app_handle.clipboard();
|
|
||||||
if cliboard.write_text(info).is_err() {
|
|
||||||
log::error!(target: "app", "Failed to write to clipboard");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn get_system_info() -> CmdResult<String> {
|
|
||||||
let sysinfo = PlatformSpecification::new_async().await;
|
|
||||||
let info = format!("{:?}", sysinfo);
|
|
||||||
Ok(info)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取当前内核运行模式
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn get_running_mode() -> Result<String, String> {
|
|
||||||
Ok(CoreManager::global().get_running_mode().await.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取应用的运行时间(毫秒)
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_app_uptime() -> CmdResult<i64> {
|
|
||||||
let start_time = APP_START_TIME.load(Ordering::Relaxed);
|
|
||||||
let now = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.as_millis() as i64;
|
|
||||||
|
|
||||||
Ok(now - start_time)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 检查应用是否以管理员身份运行
|
|
||||||
#[tauri::command]
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn is_admin() -> CmdResult<bool> {
|
|
||||||
use deelevate::{PrivilegeLevel, Token};
|
|
||||||
|
|
||||||
let result = Token::with_current_process()
|
|
||||||
.and_then(|token| token.privilege_level())
|
|
||||||
.map(|level| level != PrivilegeLevel::NotPrivileged)
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 非Windows平台检测是否以管理员身份运行
|
|
||||||
#[tauri::command]
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
pub fn is_admin() -> CmdResult<bool> {
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
Ok(unsafe { libc::geteuid() } == 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
Ok(unsafe { libc::geteuid() } == 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
|
||||||
{
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
|
|
||||||
/// Platform-specific implementation for UWP functionality
|
|
||||||
#[cfg(windows)]
|
|
||||||
mod platform {
|
|
||||||
use super::CmdResult;
|
|
||||||
use crate::{core::win_uwp, wrap_err};
|
|
||||||
|
|
||||||
pub async fn invoke_uwp_tool() -> CmdResult {
|
|
||||||
wrap_err!(win_uwp::invoke_uwptools().await)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stub implementation for non-Windows platforms
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
mod platform {
|
|
||||||
use super::CmdResult;
|
|
||||||
|
|
||||||
pub async fn invoke_uwp_tool() -> CmdResult {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Command exposed to Tauri
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn invoke_uwp_tool() -> CmdResult {
|
|
||||||
platform::invoke_uwp_tool().await
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
/// 发送脚本验证通知消息
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn script_validate_notice(status: String, msg: String) -> CmdResult {
|
|
||||||
handle::Handle::notice_message(&status, &msg);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 处理脚本验证相关的所有消息通知
|
|
||||||
/// 统一通知接口,保持消息类型一致性
|
|
||||||
pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str) {
|
|
||||||
if !result.0 {
|
|
||||||
let error_msg = &result.1;
|
|
||||||
|
|
||||||
// 根据错误消息内容判断错误类型
|
|
||||||
let status = if error_msg.starts_with("File not found:") {
|
|
||||||
"config_validate::file_not_found"
|
|
||||||
} else if error_msg.starts_with("Failed to read script file:") {
|
|
||||||
"config_validate::script_error"
|
|
||||||
} else if error_msg.starts_with("Script syntax error:") {
|
|
||||||
"config_validate::script_syntax_error"
|
|
||||||
} else if error_msg == "Script must contain a main function" {
|
|
||||||
"config_validate::script_missing_main"
|
|
||||||
} else {
|
|
||||||
// 如果是其他类型错误,作为一般脚本错误处理
|
|
||||||
"config_validate::script_error"
|
|
||||||
};
|
|
||||||
|
|
||||||
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
|
|
||||||
handle::Handle::notice_message(status, error_msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 验证指定脚本文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
|
|
||||||
log::info!(target: "app", "验证脚本文件: {}", file_path);
|
|
||||||
|
|
||||||
match CoreManager::global()
|
|
||||||
.validate_config_file(&file_path, None)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(result) => {
|
|
||||||
handle_script_validation_notice(&result, "脚本文件");
|
|
||||||
Ok(result.0) // 返回验证结果布尔值
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let error_msg = e.to_string();
|
|
||||||
log::error!(target: "app", "验证脚本文件过程发生错误: {}", error_msg);
|
|
||||||
handle::Handle::notice_message("config_validate::process_terminated", &error_msg);
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 处理YAML验证相关的所有消息通知
|
|
||||||
/// 统一通知接口,保持消息类型一致性
|
|
||||||
pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
|
|
||||||
if !result.0 {
|
|
||||||
let error_msg = &result.1;
|
|
||||||
println!("[通知] 处理{}验证错误: {}", file_type, error_msg);
|
|
||||||
|
|
||||||
// 检查是否为merge文件
|
|
||||||
let is_merge_file = file_type.contains("合并");
|
|
||||||
|
|
||||||
// 根据错误消息内容判断错误类型
|
|
||||||
let status = if error_msg.starts_with("File not found:") {
|
|
||||||
"config_validate::file_not_found"
|
|
||||||
} else if error_msg.starts_with("Failed to read file:") {
|
|
||||||
"config_validate::yaml_read_error"
|
|
||||||
} else if error_msg.starts_with("YAML syntax error:") {
|
|
||||||
if is_merge_file {
|
|
||||||
"config_validate::merge_syntax_error"
|
|
||||||
} else {
|
|
||||||
"config_validate::yaml_syntax_error"
|
|
||||||
}
|
|
||||||
} else if error_msg.contains("mapping values are not allowed") {
|
|
||||||
if is_merge_file {
|
|
||||||
"config_validate::merge_mapping_error"
|
|
||||||
} else {
|
|
||||||
"config_validate::yaml_mapping_error"
|
|
||||||
}
|
|
||||||
} else if error_msg.contains("did not find expected key") {
|
|
||||||
if is_merge_file {
|
|
||||||
"config_validate::merge_key_error"
|
|
||||||
} else {
|
|
||||||
"config_validate::yaml_key_error"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 如果是其他类型错误,根据文件类型作为一般错误处理
|
|
||||||
if is_merge_file {
|
|
||||||
"config_validate::merge_error"
|
|
||||||
} else {
|
|
||||||
"config_validate::yaml_error"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
|
|
||||||
println!("[通知] 发送通知: status={}, msg={}", status, error_msg);
|
|
||||||
handle::Handle::notice_message(status, error_msg);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::{config::*, feat, wrap_err};
|
|
||||||
|
|
||||||
/// 获取Verge配置
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_verge_config() -> CmdResult<IVergeResponse> {
|
|
||||||
let verge = Config::verge();
|
|
||||||
let verge_data = verge.data().clone();
|
|
||||||
Ok(IVergeResponse::from(verge_data))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 修改Verge配置
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn patch_verge_config(payload: IVerge) -> CmdResult {
|
|
||||||
wrap_err!(feat::patch_verge(payload, false).await)
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
use super::CmdResult;
|
|
||||||
use crate::{config::*, core, feat, wrap_err};
|
|
||||||
use reqwest_dav::list_cmd::ListFile;
|
|
||||||
|
|
||||||
/// 保存 WebDAV 配置
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn save_webdav_config(url: String, username: String, password: String) -> CmdResult<()> {
|
|
||||||
let patch = IVerge {
|
|
||||||
webdav_url: Some(url),
|
|
||||||
webdav_username: Some(username),
|
|
||||||
webdav_password: Some(password),
|
|
||||||
..IVerge::default()
|
|
||||||
};
|
|
||||||
Config::verge().draft().patch_config(patch.clone());
|
|
||||||
Config::verge().apply();
|
|
||||||
Config::verge()
|
|
||||||
.data()
|
|
||||||
.save_file()
|
|
||||||
.map_err(|err| err.to_string())?;
|
|
||||||
core::backup::WebDavClient::global().reset();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 创建 WebDAV 备份并上传
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn create_webdav_backup() -> CmdResult<()> {
|
|
||||||
wrap_err!(feat::create_backup_and_upload_webdav().await)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 列出 WebDAV 上的备份文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn list_webdav_backup() -> CmdResult<Vec<ListFile>> {
|
|
||||||
wrap_err!(feat::list_wevdav_backup().await)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 删除 WebDAV 上的备份文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn delete_webdav_backup(filename: String) -> CmdResult<()> {
|
|
||||||
wrap_err!(feat::delete_webdav_backup(filename).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 从 WebDAV 恢复备份文件
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn restore_webdav_backup(filename: String) -> CmdResult<()> {
|
|
||||||
wrap_err!(feat::restore_webdav_backup(filename).await)
|
|
||||||
}
|
|
326
src-tauri/src/cmds.rs
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
use crate::{
|
||||||
|
config::*,
|
||||||
|
core::*,
|
||||||
|
feat,
|
||||||
|
utils::{dirs, help, resolve},
|
||||||
|
};
|
||||||
|
use crate::{ret_err, wrap_err};
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
use sysproxy::Sysproxy;
|
||||||
|
use tauri::api;
|
||||||
|
type CmdResult<T = ()> = Result<T, String>;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_profiles() -> CmdResult<IProfiles> {
|
||||||
|
Ok(Config::profiles().data().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn enhance_profiles() -> CmdResult {
|
||||||
|
wrap_err!(CoreManager::global().update_config().await)?;
|
||||||
|
handle::Handle::refresh_clash();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
|
||||||
|
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
|
||||||
|
wrap_err!(Config::profiles().data().append_item(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
|
||||||
|
wrap_err!(Config::profiles().data().reorder(active_id, over_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
|
||||||
|
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
|
||||||
|
wrap_err!(Config::profiles().data().append_item(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
|
||||||
|
wrap_err!(feat::update_profile(index, option).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn delete_profile(index: String) -> CmdResult {
|
||||||
|
let should_update = wrap_err!({ Config::profiles().data().delete_item(index) })?;
|
||||||
|
if should_update {
|
||||||
|
wrap_err!(CoreManager::global().update_config().await)?;
|
||||||
|
handle::Handle::refresh_clash();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 修改profiles的
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult {
|
||||||
|
wrap_err!({ Config::profiles().draft().patch_config(profiles) })?;
|
||||||
|
|
||||||
|
match CoreManager::global().update_config().await {
|
||||||
|
Ok(_) => {
|
||||||
|
handle::Handle::refresh_clash();
|
||||||
|
Config::profiles().apply();
|
||||||
|
wrap_err!(Config::profiles().data().save_file())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
Config::profiles().discard();
|
||||||
|
log::error!(target: "app", "{err}");
|
||||||
|
Err(format!("{err}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 修改某个profile item的
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
||||||
|
wrap_err!(Config::profiles().data().patch_item(index, profile))?;
|
||||||
|
wrap_err!(timer::Timer::global().refresh())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult {
|
||||||
|
let file = {
|
||||||
|
wrap_err!(Config::profiles().latest().get_item(&index))?
|
||||||
|
.file
|
||||||
|
.clone()
|
||||||
|
.ok_or("the file field is null")
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let path = wrap_err!(dirs::app_profiles_dir())?.join(file);
|
||||||
|
if !path.exists() {
|
||||||
|
ret_err!("the file not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
wrap_err!(help::open_file(app_handle, path))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn read_profile_file(index: String) -> CmdResult<String> {
|
||||||
|
let profiles = Config::profiles();
|
||||||
|
let profiles = profiles.latest();
|
||||||
|
let item = wrap_err!(profiles.get_item(&index))?;
|
||||||
|
let data = wrap_err!(item.read_file())?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
|
||||||
|
if file_data.is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let profiles = Config::profiles();
|
||||||
|
let profiles = profiles.latest();
|
||||||
|
let item = wrap_err!(profiles.get_item(&index))?;
|
||||||
|
wrap_err!(item.save_file(file_data.unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_clash_info() -> CmdResult<ClashInfo> {
|
||||||
|
Ok(Config::clash().latest().get_client_info())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_runtime_config() -> CmdResult<Option<Mapping>> {
|
||||||
|
Ok(Config::runtime().latest().config.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_runtime_yaml() -> CmdResult<String> {
|
||||||
|
let runtime = Config::runtime();
|
||||||
|
let runtime = runtime.latest();
|
||||||
|
let config = runtime.config.as_ref();
|
||||||
|
wrap_err!(config
|
||||||
|
.ok_or(anyhow::anyhow!("failed to parse config to yaml file"))
|
||||||
|
.and_then(
|
||||||
|
|config| serde_yaml::to_string(config).context("failed to convert config to yaml")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_runtime_exists() -> CmdResult<Vec<String>> {
|
||||||
|
Ok(Config::runtime().latest().exists_keys.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
|
||||||
|
Ok(Config::runtime().latest().chain_logs.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
|
||||||
|
wrap_err!(feat::patch_clash(payload).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_verge_config() -> CmdResult<IVerge> {
|
||||||
|
Ok(Config::verge().data().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn patch_verge_config(payload: IVerge) -> CmdResult {
|
||||||
|
wrap_err!(feat::patch_verge(payload).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn change_clash_core(clash_core: Option<String>) -> CmdResult {
|
||||||
|
wrap_err!(CoreManager::global().change_core(clash_core).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// restart the sidecar
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn restart_sidecar() -> CmdResult {
|
||||||
|
wrap_err!(CoreManager::global().run_core().await)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn grant_permission(_core: String) -> CmdResult {
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
|
return wrap_err!(manager::grant_permission(_core));
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||||
|
return Err("Unsupported target".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get the system proxy
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_sys_proxy() -> CmdResult<Mapping> {
|
||||||
|
let current = wrap_err!(Sysproxy::get_system_proxy())?;
|
||||||
|
|
||||||
|
let mut map = Mapping::new();
|
||||||
|
map.insert("enable".into(), current.enable.into());
|
||||||
|
map.insert(
|
||||||
|
"server".into(),
|
||||||
|
format!("{}:{}", current.host, current.port).into(),
|
||||||
|
);
|
||||||
|
map.insert("bypass".into(), current.bypass.into());
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_clash_logs() -> CmdResult<VecDeque<String>> {
|
||||||
|
Ok(logger::Logger::global().get_log())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn open_app_dir() -> CmdResult<()> {
|
||||||
|
let app_dir = wrap_err!(dirs::app_home_dir())?;
|
||||||
|
wrap_err!(open::that(app_dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn open_core_dir() -> CmdResult<()> {
|
||||||
|
let core_dir = wrap_err!(tauri::utils::platform::current_exe())?;
|
||||||
|
let core_dir = core_dir.parent().ok_or("failed to get core dir")?;
|
||||||
|
wrap_err!(open::that(core_dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn open_logs_dir() -> CmdResult<()> {
|
||||||
|
let log_dir = wrap_err!(dirs::app_logs_dir())?;
|
||||||
|
wrap_err!(open::that(log_dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn open_web_url(url: String) -> CmdResult<()> {
|
||||||
|
wrap_err!(open::that(url))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub mod uwp {
|
||||||
|
use super::*;
|
||||||
|
use crate::core::win_uwp;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn invoke_uwp_tool() -> CmdResult {
|
||||||
|
wrap_err!(win_uwp::invoke_uwptools().await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn clash_api_get_proxy_delay(
|
||||||
|
name: String,
|
||||||
|
url: Option<String>,
|
||||||
|
timeout: i32,
|
||||||
|
) -> CmdResult<clash_api::DelayRes> {
|
||||||
|
match clash_api::get_proxy_delay(name, url, timeout).await {
|
||||||
|
Ok(res) => Ok(res),
|
||||||
|
Err(err) => Err(err.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_portable_flag() -> CmdResult<bool> {
|
||||||
|
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn test_delay(url: String) -> CmdResult<u32> {
|
||||||
|
Ok(feat::test_delay(url).await.unwrap_or(10000u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn exit_app(app_handle: tauri::AppHandle) {
|
||||||
|
let _ = resolve::save_window_size_position(&app_handle, true);
|
||||||
|
resolve::resolve_reset();
|
||||||
|
api::process::kill_children();
|
||||||
|
app_handle.exit(0);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub mod service {
|
||||||
|
use super::*;
|
||||||
|
use crate::core::win_service;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn check_service() -> CmdResult<win_service::JsonResponse> {
|
||||||
|
wrap_err!(win_service::check_service().await)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn install_service() -> CmdResult {
|
||||||
|
wrap_err!(win_service::install_service().await)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn uninstall_service() -> CmdResult {
|
||||||
|
wrap_err!(win_service::uninstall_service().await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub mod service {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn check_service() -> CmdResult {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn install_service() -> CmdResult {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn uninstall_service() -> CmdResult {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub mod uwp {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn invoke_uwp_tool() -> CmdResult {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@ pub struct IClashTemp(pub Mapping);
|
|||||||
impl IClashTemp {
|
impl IClashTemp {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let template = Self::template();
|
let template = Self::template();
|
||||||
match dirs::clash_path().and_then(|path| help::read_mapping(&path)) {
|
match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) {
|
||||||
Ok(mut map) => {
|
Ok(mut map) => {
|
||||||
template.0.keys().for_each(|key| {
|
template.0.keys().for_each(|key| {
|
||||||
if !map.contains_key(key) {
|
if !map.contains_key(key) {
|
||||||
@ -32,17 +32,14 @@ impl IClashTemp {
|
|||||||
pub fn template() -> Self {
|
pub fn template() -> Self {
|
||||||
let mut map = Mapping::new();
|
let mut map = Mapping::new();
|
||||||
let mut tun = Mapping::new();
|
let mut tun = Mapping::new();
|
||||||
tun.insert("enable".into(), false.into());
|
tun.insert("stack".into(), "gVisor".into());
|
||||||
tun.insert("stack".into(), "gvisor".into());
|
tun.insert("device".into(), "Meta".into());
|
||||||
tun.insert("auto-route".into(), true.into());
|
tun.insert("auto-route".into(), true.into());
|
||||||
tun.insert("strict-route".into(), false.into());
|
tun.insert("strict-route".into(), true.into());
|
||||||
tun.insert("auto-detect-interface".into(), true.into());
|
tun.insert("auto-detect-interface".into(), true.into());
|
||||||
tun.insert("dns-hijack".into(), vec!["any:53"].into());
|
tun.insert("dns-hijack".into(), vec!["any:53", "tcp://any:53"].into());
|
||||||
|
tun.insert("mtu".into(), 9000.into());
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
map.insert("redir-port".into(), 7895.into());
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
map.insert("tproxy-port".into(), 7896.into());
|
|
||||||
map.insert("mixed-port".into(), 7897.into());
|
map.insert("mixed-port".into(), 7897.into());
|
||||||
map.insert("socks-port".into(), 7898.into());
|
map.insert("socks-port".into(), 7898.into());
|
||||||
map.insert("port".into(), 7899.into());
|
map.insert("port".into(), 7899.into());
|
||||||
@ -50,29 +47,18 @@ impl IClashTemp {
|
|||||||
map.insert("allow-lan".into(), false.into());
|
map.insert("allow-lan".into(), false.into());
|
||||||
map.insert("mode".into(), "rule".into());
|
map.insert("mode".into(), "rule".into());
|
||||||
map.insert("external-controller".into(), "127.0.0.1:9097".into());
|
map.insert("external-controller".into(), "127.0.0.1:9097".into());
|
||||||
let mut cors_map = Mapping::new();
|
|
||||||
cors_map.insert("allow-private-network".into(), true.into());
|
|
||||||
cors_map.insert("allow-origins".into(), vec!["*"].into());
|
|
||||||
map.insert("secret".into(), "".into());
|
map.insert("secret".into(), "".into());
|
||||||
map.insert("tun".into(), tun.into());
|
map.insert("tun".into(), tun.into());
|
||||||
map.insert("external-controller-cors".into(), cors_map.into());
|
|
||||||
map.insert("unified-delay".into(), true.into());
|
|
||||||
Self(map)
|
Self(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn guard(mut config: Mapping) -> Mapping {
|
fn guard(mut config: Mapping) -> Mapping {
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
let redir_port = Self::guard_redir_port(&config);
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
let tproxy_port = Self::guard_tproxy_port(&config);
|
|
||||||
let mixed_port = Self::guard_mixed_port(&config);
|
let mixed_port = Self::guard_mixed_port(&config);
|
||||||
let socks_port = Self::guard_socks_port(&config);
|
let socks_port = Self::guard_socks_port(&config);
|
||||||
let port = Self::guard_port(&config);
|
let port = Self::guard_port(&config);
|
||||||
let ctrl = Self::guard_server_ctrl(&config);
|
let ctrl = Self::guard_server_ctrl(&config);
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
config.insert("redir-port".into(), redir_port.into());
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
config.insert("tproxy-port".into(), tproxy_port.into());
|
|
||||||
config.insert("mixed-port".into(), mixed_port.into());
|
config.insert("mixed-port".into(), mixed_port.into());
|
||||||
config.insert("socks-port".into(), socks_port.into());
|
config.insert("socks-port".into(), socks_port.into());
|
||||||
config.insert("port".into(), port.into());
|
config.insert("port".into(), port.into());
|
||||||
@ -124,53 +110,19 @@ impl IClashTemp {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
pub fn guard_redir_port(config: &Mapping) -> u16 {
|
|
||||||
let mut port = config
|
|
||||||
.get("redir-port")
|
|
||||||
.and_then(|value| match value {
|
|
||||||
Value::String(val_str) => val_str.parse().ok(),
|
|
||||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.unwrap_or(7895);
|
|
||||||
if port == 0 {
|
|
||||||
port = 7895;
|
|
||||||
}
|
|
||||||
port
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub fn guard_tproxy_port(config: &Mapping) -> u16 {
|
|
||||||
let mut port = config
|
|
||||||
.get("tproxy-port")
|
|
||||||
.and_then(|value| match value {
|
|
||||||
Value::String(val_str) => val_str.parse().ok(),
|
|
||||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.unwrap_or(7896);
|
|
||||||
if port == 0 {
|
|
||||||
port = 7896;
|
|
||||||
}
|
|
||||||
port
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn guard_mixed_port(config: &Mapping) -> u16 {
|
pub fn guard_mixed_port(config: &Mapping) -> u16 {
|
||||||
let raw_value = config.get("mixed-port");
|
let mut port = config
|
||||||
|
.get("mixed-port")
|
||||||
let mut port = raw_value
|
|
||||||
.and_then(|value| match value {
|
.and_then(|value| match value {
|
||||||
Value::String(val_str) => val_str.parse().ok(),
|
Value::String(val_str) => val_str.parse().ok(),
|
||||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.unwrap_or(7897);
|
.unwrap_or(7897);
|
||||||
|
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
port = 7897;
|
port = 7897;
|
||||||
}
|
}
|
||||||
|
|
||||||
port
|
port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
|
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
|
||||||
use crate::{
|
use crate::{
|
||||||
config::PrfItem,
|
enhance,
|
||||||
core::{handle, CoreManager},
|
utils::{dirs, help},
|
||||||
enhance, logging,
|
|
||||||
utils::{dirs, help, logging::Type},
|
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use std::path::PathBuf;
|
use std::{env::temp_dir, path::PathBuf};
|
||||||
use tokio::time::{sleep, Duration};
|
|
||||||
|
|
||||||
pub const RUNTIME_CONFIG: &str = "clash-verge.yaml";
|
pub const RUNTIME_CONFIG: &str = "clash-verge.yaml";
|
||||||
pub const CHECK_CONFIG: &str = "clash-verge-check.yaml";
|
pub const CHECK_CONFIG: &str = "clash-verge-check.yaml";
|
||||||
@ -49,80 +46,21 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 初始化订阅
|
/// 初始化订阅
|
||||||
pub async fn init_config() -> Result<()> {
|
pub fn init_config() -> Result<()> {
|
||||||
if Self::profiles()
|
crate::log_err!(Self::generate());
|
||||||
.data()
|
if let Err(err) = Self::generate_file(ConfigType::Run) {
|
||||||
.get_item(&"Merge".to_string())
|
log::error!(target: "app", "{err}");
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
let merge_item = PrfItem::from_merge(Some("Merge".to_string()))?;
|
|
||||||
Self::profiles().data().append_item(merge_item.clone())?;
|
|
||||||
}
|
|
||||||
if Self::profiles()
|
|
||||||
.data()
|
|
||||||
.get_item(&"Script".to_string())
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
let script_item = PrfItem::from_script(Some("Script".to_string()))?;
|
|
||||||
Self::profiles().data().append_item(script_item.clone())?;
|
|
||||||
}
|
|
||||||
// 生成运行时配置
|
|
||||||
if let Err(err) = Self::generate().await {
|
|
||||||
logging!(error, Type::Config, true, "生成运行时配置失败: {}", err);
|
|
||||||
} else {
|
|
||||||
logging!(info, Type::Config, true, "生成运行时配置成功");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成运行时配置文件并验证
|
let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG);
|
||||||
let config_result = Self::generate_file(ConfigType::Run);
|
// 如果不存在就将默认的clash文件拿过来
|
||||||
|
if !runtime_path.exists() {
|
||||||
let validation_result = if config_result.is_ok() {
|
help::save_yaml(
|
||||||
// 验证配置文件
|
&runtime_path,
|
||||||
logging!(info, Type::Config, true, "开始验证配置");
|
&Config::clash().latest().0,
|
||||||
|
Some("# Clash Verge Runtime"),
|
||||||
match CoreManager::global().validate_config().await {
|
)?;
|
||||||
Ok((is_valid, error_msg)) => {
|
|
||||||
if !is_valid {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Config,
|
|
||||||
true,
|
|
||||||
"[首次启动] 配置验证失败,使用默认最小配置启动: {}",
|
|
||||||
error_msg
|
|
||||||
);
|
|
||||||
CoreManager::global()
|
|
||||||
.use_default_config("config_validate::boot_error", &error_msg)
|
|
||||||
.await?;
|
|
||||||
Some(("config_validate::boot_error", error_msg))
|
|
||||||
} else {
|
|
||||||
logging!(info, Type::Config, true, "配置验证成功");
|
|
||||||
Some(("config_validate::success", String::new()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
logging!(warn, Type::Config, true, "验证进程执行失败: {}", err);
|
|
||||||
CoreManager::global()
|
|
||||||
.use_default_config("config_validate::process_terminated", "")
|
|
||||||
.await?;
|
|
||||||
Some(("config_validate::process_terminated", String::new()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
logging!(warn, Type::Config, true, "生成配置文件失败,使用默认配置");
|
|
||||||
CoreManager::global()
|
|
||||||
.use_default_config("config_validate::error", "")
|
|
||||||
.await?;
|
|
||||||
Some(("config_validate::error", String::new()))
|
|
||||||
};
|
|
||||||
|
|
||||||
// 在单独的任务中发送通知
|
|
||||||
if let Some((msg_type, msg_content)) = validation_result {
|
|
||||||
tauri::async_runtime::spawn(async move {
|
|
||||||
sleep(Duration::from_secs(2)).await;
|
|
||||||
handle::Handle::notice_message(msg_type, &msg_content);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +68,7 @@ impl Config {
|
|||||||
pub fn generate_file(typ: ConfigType) -> Result<PathBuf> {
|
pub fn generate_file(typ: ConfigType) -> Result<PathBuf> {
|
||||||
let path = match typ {
|
let path = match typ {
|
||||||
ConfigType::Run => dirs::app_home_dir()?.join(RUNTIME_CONFIG),
|
ConfigType::Run => dirs::app_home_dir()?.join(RUNTIME_CONFIG),
|
||||||
ConfigType::Check => dirs::app_home_dir()?.join(CHECK_CONFIG),
|
ConfigType::Check => temp_dir().join(CHECK_CONFIG),
|
||||||
};
|
};
|
||||||
|
|
||||||
let runtime = Config::runtime();
|
let runtime = Config::runtime();
|
||||||
@ -145,8 +83,8 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 生成订阅存好
|
/// 生成订阅存好
|
||||||
pub async fn generate() -> Result<()> {
|
pub fn generate() -> Result<()> {
|
||||||
let (config, exists_keys, logs) = enhance::enhance().await;
|
let (config, exists_keys, logs) = enhance::enhance();
|
||||||
|
|
||||||
*Config::runtime().draft() = IRuntime {
|
*Config::runtime().draft() = IRuntime {
|
||||||
config: Some(config),
|
config: Some(config),
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
use crate::utils::dirs::get_encryption_key;
|
|
||||||
use aes_gcm::{
|
|
||||||
aead::{Aead, KeyInit},
|
|
||||||
Aes256Gcm, Key,
|
|
||||||
};
|
|
||||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
|
|
||||||
const NONCE_LENGTH: usize = 12;
|
|
||||||
|
|
||||||
/// Encrypt data
|
|
||||||
pub fn encrypt_data(data: &str) -> Result<String, Box<dyn std::error::Error>> {
|
|
||||||
let encryption_key = get_encryption_key()?;
|
|
||||||
let key = Key::<Aes256Gcm>::from_slice(&encryption_key);
|
|
||||||
let cipher = Aes256Gcm::new(key);
|
|
||||||
|
|
||||||
// Generate random nonce
|
|
||||||
let mut nonce = vec![0u8; NONCE_LENGTH];
|
|
||||||
getrandom::fill(&mut nonce)?;
|
|
||||||
|
|
||||||
// Encrypt data
|
|
||||||
let ciphertext = cipher
|
|
||||||
.encrypt(nonce.as_slice().into(), data.as_bytes())
|
|
||||||
.map_err(|e| format!("Encryption failed: {}", e))?;
|
|
||||||
|
|
||||||
// Concatenate nonce and ciphertext and encode them in base64
|
|
||||||
let mut combined = nonce;
|
|
||||||
combined.extend(ciphertext);
|
|
||||||
Ok(STANDARD.encode(combined))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypt data
|
|
||||||
pub fn decrypt_data(encrypted: &str) -> Result<String, Box<dyn std::error::Error>> {
|
|
||||||
let encryption_key = get_encryption_key()?;
|
|
||||||
let key = Key::<Aes256Gcm>::from_slice(&encryption_key);
|
|
||||||
let cipher = Aes256Gcm::new(key);
|
|
||||||
// Decode from base64
|
|
||||||
let data = STANDARD.decode(encrypted)?;
|
|
||||||
if data.len() < NONCE_LENGTH {
|
|
||||||
return Err("Invalid encrypted data".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Separate nonce and ciphertext
|
|
||||||
let (nonce, ciphertext) = data.split_at(NONCE_LENGTH);
|
|
||||||
|
|
||||||
// Decrypt data
|
|
||||||
let plaintext = cipher
|
|
||||||
.decrypt(nonce.into(), ciphertext)
|
|
||||||
.map_err(|e| format!("Decryption failed: {}", e))?;
|
|
||||||
|
|
||||||
String::from_utf8(plaintext).map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialize encrypted function
|
|
||||||
pub fn serialize_encrypted<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
T: Serialize,
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
// 如果序列化失败,返回 None
|
|
||||||
let json = match serde_json::to_string(value) {
|
|
||||||
Ok(j) => j,
|
|
||||||
Err(_) => return serializer.serialize_none(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果加密失败,返回 None
|
|
||||||
match encrypt_data(&json) {
|
|
||||||
Ok(encrypted) => serializer.serialize_str(&encrypted),
|
|
||||||
Err(_) => serializer.serialize_none(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialize decrypted function
|
|
||||||
pub fn deserialize_encrypted<'a, T, D>(deserializer: D) -> Result<T, D::Error>
|
|
||||||
where
|
|
||||||
T: for<'de> Deserialize<'de> + Default,
|
|
||||||
D: Deserializer<'a>,
|
|
||||||
{
|
|
||||||
// 如果反序列化字符串失败,返回默认值
|
|
||||||
let encrypted = match String::deserialize(deserializer) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return Ok(T::default()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果解密失败,返回默认值
|
|
||||||
let decrypted_string = match decrypt_data(&encrypted) {
|
|
||||||
Ok(data) => data,
|
|
||||||
Err(_) => return Ok(T::default()),
|
|
||||||
};
|
|
||||||
// 如果 JSON 解析失败,返回默认值
|
|
||||||
match serde_json::from_str(&decrypted_string) {
|
|
||||||
Ok(value) => Ok(value),
|
|
||||||
Err(_) => Ok(T::default()),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +1,15 @@
|
|||||||
mod clash;
|
mod clash;
|
||||||
#[allow(clippy::module_inception)]
|
|
||||||
mod config;
|
mod config;
|
||||||
mod draft;
|
mod draft;
|
||||||
mod encrypt;
|
|
||||||
mod prfitem;
|
mod prfitem;
|
||||||
mod profiles;
|
mod profiles;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
mod verge;
|
mod verge;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::clash::*;
|
||||||
clash::*, config::*, draft::*, encrypt::*, prfitem::*, profiles::*, runtime::*, verge::*,
|
pub use self::config::*;
|
||||||
};
|
pub use self::draft::*;
|
||||||
|
pub use self::prfitem::*;
|
||||||
pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) {
|
pub use self::profiles::*;
|
||||||
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
|
pub use self::runtime::*;
|
||||||
}
|
pub use self::verge::*;
|
||||||
"#;
|
|
||||||
|