mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-15 13:50:47 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1d914e61d | ||
|
|
6dba62a3b4 | ||
|
|
acab77a1b4 | ||
|
|
1bf445ddcc | ||
|
|
700011688b | ||
|
|
0cc9bb2f30 | ||
|
|
7d29c0c6ee | ||
|
|
321017413d | ||
|
|
7f045943e2 | ||
|
|
fa07dfbc9a | ||
|
|
119aaee546 | ||
|
|
5f573ca2d6 | ||
|
|
e5dd127bcc | ||
|
|
7528c238c4 | ||
|
|
10601e873e | ||
|
|
d77d655897 | ||
|
|
ec6f259794 | ||
|
|
7a564b4ea9 | ||
|
|
cd4ff68b5b | ||
|
|
1927b5f957 | ||
|
|
58047cbbd1 | ||
|
|
ddf455508f | ||
|
|
afde2f34f4 | ||
|
|
9f6eb46e90 | ||
|
|
cf9f235270 | ||
|
|
44851466cf | ||
|
|
847a0a6afd | ||
|
|
81c56d46c1 | ||
|
|
31c0910919 | ||
|
|
87f55cfce7 | ||
|
|
bba71aaa4c | ||
|
|
a019b26ceb | ||
|
|
a4617d1fed | ||
|
|
4d72d2d0df | ||
|
|
277ded4c44 | ||
|
|
5f9096dd6e | ||
|
|
410b5bd317 | ||
|
|
a7041657c9 | ||
|
|
88764d763c | ||
|
|
8edfbbb1c6 | ||
|
|
7730cd1c5b | ||
|
|
cad1c983e1 | ||
|
|
5480e57e67 | ||
|
|
5bcb057bf9 | ||
|
|
c30eaa3678 | ||
|
|
6c6e0812b8 | ||
|
|
afa591c279 | ||
|
|
e9d63aee5e | ||
|
|
781313e8f0 | ||
|
|
c3f7ff7aa2 | ||
|
|
5397808f16 | ||
|
|
279836151c | ||
|
|
6f424ebd2b | ||
|
|
c7f5bc4e0d | ||
|
|
90e193099f | ||
|
|
b3a1fb8d23 | ||
|
|
a8e51cc6bb | ||
|
|
53867bc3a9 | ||
|
|
ae5d3c478a | ||
|
|
654152391b | ||
|
|
63a77b1c7d | ||
|
|
9a0703676b | ||
|
|
95281632a1 | ||
|
|
b17dd39f31 | ||
|
|
1af326cefc | ||
|
|
5103868119 | ||
|
|
c57a962109 | ||
|
|
36926df26c | ||
|
|
9d81a13c58 | ||
|
|
511fab9a9d | ||
|
|
88529af8c8 | ||
|
|
425096e8af | ||
|
|
8a4e2327c1 | ||
|
|
74b1687be9 | ||
|
|
6477dd61c3 | ||
|
|
6ded9bdcde | ||
|
|
13dc3feb9f | ||
|
|
c7462716e5 | ||
|
|
bf189bb144 | ||
|
|
0c6631ebb0 | ||
|
|
93e7ac1bce | ||
|
|
b921098182 | ||
|
|
440f95f617 | ||
|
|
b9667ad349 | ||
|
|
4e7cdbfcc0 | ||
|
|
966fd68087 | ||
|
|
334cec3bde | ||
|
|
6e16133393 | ||
|
|
5e976c2fe1 | ||
|
|
d81aa5f233 | ||
|
|
e5fc0de39a | ||
|
|
6c62350cc3 | ||
|
|
d1649e3017 | ||
|
|
2869a35f1e | ||
|
|
98f12a9c72 | ||
|
|
6dc8a2f232 | ||
|
|
6511f3868e | ||
|
|
7da5a804f9 | ||
|
|
20ed7a3abe | ||
|
|
fd98caccd2 | ||
|
|
a5f494bda2 | ||
|
|
d4d8ef3849 | ||
|
|
b16cbd5379 | ||
|
|
9e6689ef08 | ||
|
|
e0c35c5ee3 | ||
|
|
670055aba1 | ||
|
|
a780e44e69 | ||
|
|
5c9b46f031 | ||
|
|
f5e75d5287 | ||
|
|
c2d8277a1a | ||
|
|
66e98518a7 | ||
|
|
089b73bbfd | ||
|
|
d2c52d09e1 | ||
|
|
84143ec761 | ||
|
|
f451a26f8c | ||
|
|
e1220a189b | ||
|
|
57d4149807 | ||
|
|
86c3b241b1 | ||
|
|
a49000712d | ||
|
|
35b2066d4c | ||
|
|
92e0762fc4 | ||
|
|
6b8630d357 | ||
|
|
a1e77070f0 | ||
|
|
6926744ca2 | ||
|
|
13855b9bc2 | ||
|
|
1889f18183 | ||
|
|
a981be80ef | ||
|
|
60d3a1927b | ||
|
|
620841592f | ||
|
|
2128e1f788 | ||
|
|
256a3f697b | ||
|
|
a701450362 | ||
|
|
9e4e0c81a4 | ||
|
|
421bbd090e | ||
|
|
4adf678480 | ||
|
|
a9a782d5c9 | ||
|
|
ee5e5ee8a6 | ||
|
|
a940445081 | ||
|
|
65653594c7 | ||
|
|
ac8f62bea2 | ||
|
|
eb8ba8b369 | ||
|
|
c18821288e | ||
|
|
7d40410dea | ||
|
|
349be20a6c | ||
|
|
1901a6c97c | ||
|
|
bb72b92ae9 | ||
|
|
8a1740d38b | ||
|
|
d75d3bd86e | ||
|
|
b277a1e760 | ||
|
|
522eccdd0e | ||
|
|
f3b9eedcf7 | ||
|
|
3bbcdbe5ca | ||
|
|
cceb0a6eb4 | ||
|
|
cb5a2e7ce3 | ||
|
|
609008f087 | ||
|
|
bae3576e93 | ||
|
|
9ce343fb45 | ||
|
|
cf08628200 | ||
|
|
c06c15450f | ||
|
|
772b87e733 | ||
|
|
c80c659180 | ||
|
|
0cde6cfce9 | ||
|
|
e6a0369036 | ||
|
|
ca50e35435 | ||
|
|
0193ba7bf9 | ||
|
|
c40cdf6b55 | ||
|
|
a82bcbe86e | ||
|
|
895e54f7ec | ||
|
|
c41db51f81 | ||
|
|
2c1303c2bd | ||
|
|
c8aeae3f83 | ||
|
|
1b477ed0b2 | ||
|
|
5aba848741 | ||
|
|
593751eda2 | ||
|
|
b53f54f3f4 | ||
|
|
bfb18cf003 | ||
|
|
9c6f5bc991 | ||
|
|
63cd4905f9 | ||
|
|
2417d064e1 | ||
|
|
d91e19e166 | ||
|
|
65b4d8713d | ||
|
|
a67abda72d | ||
|
|
8e27834e35 | ||
|
|
ee3f7df417 | ||
|
|
f9b8a658a1 | ||
|
|
1c044f053f | ||
|
|
712b8ff19b | ||
|
|
4ab2720ac4 | ||
|
|
af0e72d119 | ||
|
|
bd62a4ecc0 | ||
|
|
4ffb8b415f | ||
|
|
0992556b4a | ||
|
|
be6b53c760 | ||
|
|
797c0f90aa | ||
|
|
d52f00c1b1 | ||
|
|
f26abcd2a9 | ||
|
|
863a80df43 | ||
|
|
19accbd538 | ||
|
|
8e48e4ed10 | ||
|
|
eafa08066d | ||
|
|
231517b5db | ||
|
|
45193e017f | ||
|
|
2515deefed | ||
|
|
c84bb91f4a | ||
|
|
af094bfcd7 | ||
|
|
a5752f7b00 | ||
|
|
23e551e384 | ||
|
|
a0b12b8797 | ||
|
|
16c3dcc616 | ||
|
|
5afe11e55b | ||
|
|
6f61759a39 | ||
|
|
57b17ab8d3 | ||
|
|
fc84dc561c | ||
|
|
bd8eccdcea | ||
|
|
b4e25951b4 | ||
|
|
aa72fa9a42 | ||
|
|
f0ae631cb0 | ||
|
|
787463a226 | ||
|
|
8d5d72957b | ||
|
|
7d805cb83e | ||
|
|
a1286ad057 | ||
|
|
9713343323 | ||
|
|
b282217e5c | ||
|
|
4b29a140b5 | ||
|
|
b6af7b7440 | ||
|
|
bcb8e831c5 | ||
|
|
a9e6391417 | ||
|
|
721929a2a1 | ||
|
|
b35d0ac16f | ||
|
|
3198c3f023 | ||
|
|
a3df3fc2a6 | ||
|
|
507d85ac2f | ||
|
|
236e5c56c3 | ||
|
|
5b63f350ed | ||
|
|
5f885bbe32 | ||
|
|
6e170993dc | ||
|
|
ee6b857336 | ||
|
|
195f470bca | ||
|
|
37c2428f49 | ||
|
|
34793f9880 | ||
|
|
04e50824f1 | ||
|
|
da7faf815f | ||
|
|
af9b67186e | ||
|
|
c5fa64a213 | ||
|
|
5c42658a2e | ||
|
|
3c1bcd7d72 | ||
|
|
328803b894 | ||
|
|
fbe44cf9c1 | ||
|
|
6db559f346 | ||
|
|
f0c7aca3ff | ||
|
|
8316c75c78 | ||
|
|
e56a989944 | ||
|
|
cea78289c1 | ||
|
|
1a2d45555d | ||
|
|
11879e224c | ||
|
|
c8628f32df | ||
|
|
1bb1cb3511 | ||
|
|
711aaf05d3 | ||
|
|
430c946b61 | ||
|
|
2995f61087 | ||
|
|
2aaa91f4cb | ||
|
|
104fd11b04 | ||
|
|
c7dd93004e | ||
|
|
86dcf0bdcf | ||
|
|
09a4e7e083 | ||
|
|
5bb4539e3f | ||
|
|
cad1cf2895 | ||
|
|
623ed69a37 | ||
|
|
7838fa1e75 | ||
|
|
dd34ced070 | ||
|
|
66483d0ef6 | ||
|
|
54858ce1fb | ||
|
|
dfa5cff1b7 | ||
|
|
2b552d6ddb | ||
|
|
af7b33d35b | ||
|
|
d8e386e394 | ||
|
|
bae65a523a | ||
|
|
aa767695af | ||
|
|
494e888b52 | ||
|
|
5292067284 | ||
|
|
9286e921a6 | ||
|
|
3ab985aacc | ||
|
|
418fc1c702 | ||
|
|
b5da9c9629 | ||
|
|
86936d5322 | ||
|
|
c1489647d1 | ||
|
|
af0a3be66d | ||
|
|
d0917dae73 | ||
|
|
f383a18f77 | ||
|
|
f8035710df | ||
|
|
4e34eb156b | ||
|
|
c997fbbeed | ||
|
|
54ba9fc98d | ||
|
|
eb7d22f610 | ||
|
|
90b1c6e153 | ||
|
|
5797bb7f8c | ||
|
|
2ca8e6716d | ||
|
|
c888703b05 | ||
|
|
cd6885f115 | ||
|
|
643df7c15d | ||
|
|
ff0de29f17 | ||
|
|
7ae2414067 | ||
|
|
d28075221c | ||
|
|
d0c8f7fcc1 | ||
|
|
28f8bb9199 | ||
|
|
19a31450e5 | ||
|
|
a0fd24bb90 | ||
|
|
f2ad62e446 | ||
|
|
7aeaab6e5c | ||
|
|
afee21dae4 | ||
|
|
3cf51de850 | ||
|
|
4f8ed63cf0 | ||
|
|
135d14b170 | ||
|
|
762c991035 | ||
|
|
357069505a | ||
|
|
2af7ddfdef | ||
|
|
bd24a3f0ed | ||
|
|
127501ce49 | ||
|
|
666878e288 | ||
|
|
9388b73c1f | ||
|
|
29cf57d4b9 | ||
|
|
176ad1a741 | ||
|
|
56ad05cbf9 | ||
|
|
1727d0b6d4 | ||
|
|
cbc8782bf6 | ||
|
|
76eb063f7d | ||
|
|
edf6e7cc42 | ||
|
|
3a5ce1dd19 | ||
|
|
550a7e0bb9 | ||
|
|
1393828078 | ||
|
|
32a5044026 | ||
|
|
bfa7e1a2e6 | ||
|
|
9f151594ff | ||
|
|
a611f7d8a7 | ||
|
|
d5f1af91f1 | ||
|
|
008234988c | ||
|
|
9056e8a298 | ||
|
|
e6a4da8064 | ||
|
|
8f10f98db1 | ||
|
|
a1a9c2b7bb | ||
|
|
6897ead070 | ||
|
|
ae8c302bf4 | ||
|
|
aab8c8b3a1 | ||
|
|
dca9b51b4b | ||
|
|
a14f76eaf8 | ||
|
|
3e4bdf2d64 | ||
|
|
b03dcd71f4 | ||
|
|
25a3c2524f | ||
|
|
882f1a2a4a | ||
|
|
bd2d86fd8f | ||
|
|
271550205b | ||
|
|
45d4f0e89c | ||
|
|
1d41cf691b | ||
|
|
8c1caf8555 | ||
|
|
0fa77441f5 | ||
|
|
6456c597ed | ||
|
|
683667f8ae | ||
|
|
34aa26813f | ||
|
|
99dda5496e | ||
|
|
9ce5d27d6e | ||
|
|
90b850154d | ||
|
|
aed3060a11 | ||
|
|
3cd6e88c37 | ||
|
|
c2c628e8e0 | ||
|
|
a572708fd8 | ||
|
|
cbd8c89097 | ||
|
|
affdfc045e | ||
|
|
adb969d370 | ||
|
|
22e2e751a2 | ||
|
|
2e6e9a0db4 | ||
|
|
643e15b752 | ||
|
|
ecc272aa20 | ||
|
|
d3dc40e788 | ||
|
|
b83ef994f8 | ||
|
|
caca8b2bc4 | ||
|
|
64727ad660 | ||
|
|
6b3f5eea16 | ||
|
|
38b306a438 | ||
|
|
e24bb8f9cc | ||
|
|
0c00a39754 | ||
|
|
8e8182f707 | ||
|
|
871881c460 | ||
|
|
cbd1fa44d7 | ||
|
|
ad6fe696c0 | ||
|
|
44adf55975 | ||
|
|
ddd24eb3ac | ||
|
|
cbab199f80 | ||
|
|
687530a9cd | ||
|
|
4ff186497c | ||
|
|
45020fceda | ||
|
|
51760a5b95 | ||
|
|
a271ba6ce5 | ||
|
|
82bed4910e | ||
|
|
c82cefe80e | ||
|
|
d110e462aa | ||
|
|
1d9179ba7e | ||
|
|
dd52522905 | ||
|
|
ddcd4a8192 | ||
|
|
c38dd5da14 | ||
|
|
803f749063 | ||
|
|
192b15a02d | ||
|
|
a5a791a7f8 | ||
|
|
02d0a7e61f | ||
|
|
da0106b1e8 | ||
|
|
ee9e9182dc | ||
|
|
6a62009485 | ||
|
|
22241f8f60 | ||
|
|
a1f8ab7308 | ||
|
|
5a677fbbeb | ||
|
|
fcc1d4f9b5 | ||
|
|
e3da884bde | ||
|
|
8e3273a32c | ||
|
|
0827829cc5 | ||
|
|
bf06cbc87d | ||
|
|
7e373ff39c | ||
|
|
87e3125e67 | ||
|
|
131cd6c3b8 | ||
|
|
e42ae0e8c5 | ||
|
|
c765016063 | ||
|
|
614a2cf00b | ||
|
|
be97a27ba3 | ||
|
|
b3dc48d07e | ||
|
|
4c8673fcb2 | ||
|
|
51a7b4fe75 | ||
|
|
2bb9f648ba | ||
|
|
c3fe2d31ef | ||
|
|
4617ec0997 | ||
|
|
d8090277af | ||
|
|
108a05ce96 | ||
|
|
d808e59156 | ||
|
|
ba3cd9b006 | ||
|
|
aad5bb812c | ||
|
|
6ab597db8f | ||
|
|
85302144b2 | ||
|
|
ba90c6a1e1 | ||
|
|
f439e93a2b | ||
|
|
94b07b51d6 | ||
|
|
9e3bc6db8c | ||
|
|
531307ebe1 | ||
|
|
df4c69e9cc | ||
|
|
9ef4362b23 | ||
|
|
3ec7c6d2b8 | ||
|
|
ec7f912eae | ||
|
|
e7812396df | ||
|
|
4fa8b1f118 | ||
|
|
af9b1b777e | ||
|
|
096b1b0d81 | ||
|
|
7a81a673f3 | ||
|
|
38980b1249 | ||
|
|
b34e4cf551 | ||
|
|
8339fabb17 | ||
|
|
ca35994ccb | ||
|
|
92d3a59d0d | ||
|
|
4f6ceec0d5 | ||
|
|
9df0998f3e | ||
|
|
e672d19622 | ||
|
|
16478d314b | ||
|
|
a5de24a545 | ||
|
|
35ae5cdba1 | ||
|
|
d1a099b480 | ||
|
|
eb1768e212 | ||
|
|
056af768e5 | ||
|
|
0866b93175 | ||
|
|
635f63e9e5 | ||
|
|
f315a98afd | ||
|
|
b0089d162b | ||
|
|
e1fc9547d8 | ||
|
|
cfd4532440 | ||
|
|
1eb4a0d834 | ||
|
|
29d8df40b9 | ||
|
|
dbb4877be6 | ||
|
|
e66b8e7894 | ||
|
|
8654bad6b0 | ||
|
|
e021e45e9a | ||
|
|
272c2f3467 | ||
|
|
0b641992e7 | ||
|
|
55ac25b1a6 | ||
|
|
40db928436 | ||
|
|
2eb5b5802d | ||
|
|
7f2bced0d4 | ||
|
|
a6cb903fe6 | ||
|
|
2b0ba4dc95 | ||
|
|
f6f288f02b | ||
|
|
c4e558d377 | ||
|
|
8190432903 | ||
|
|
20cb3e49fd | ||
|
|
4fb8611e55 | ||
|
|
e41f910063 | ||
|
|
4e800d165b | ||
|
|
f1bd56ea13 | ||
|
|
dbd46f2b76 | ||
|
|
18b3db46b0 | ||
|
|
0e2e919a58 | ||
|
|
1125dc0562 | ||
|
|
d73036a3b6 | ||
|
|
9c24b3089c | ||
|
|
d0d35da2f1 | ||
|
|
3db69bd816 | ||
|
|
3fab4ce34a | ||
|
|
2a2de29224 | ||
|
|
2ea22feb95 | ||
|
|
a5f2b163f1 | ||
|
|
f3a3d0e8b0 | ||
|
|
92a3f0cae3 | ||
|
|
d18c303e12 | ||
|
|
b40136a790 | ||
|
|
645b92bc28 | ||
|
|
afb3842776 | ||
|
|
e02f23e0aa | ||
|
|
6e9c6e60bb | ||
|
|
600019f980 | ||
|
|
2d9eef834b | ||
|
|
33f575ed4d | ||
|
|
7d42d5cb0f | ||
|
|
838e401796 | ||
|
|
78d5cb5eca | ||
|
|
b308251022 | ||
|
|
49e5a557ca | ||
|
|
e66a4f6aca | ||
|
|
204bfa36e5 | ||
|
|
a8fe5a71f7 | ||
|
|
c03e10b5dc | ||
|
|
0dcdd7fed6 | ||
|
|
221a1e89eb | ||
|
|
4eeb883464 | ||
|
|
e6c8f762db | ||
|
|
7f267fa727 | ||
|
|
f5160ef277 | ||
|
|
5f2ac77923 | ||
|
|
3e81dcd78f | ||
|
|
f644036e08 | ||
|
|
c763fdd233 | ||
|
|
6026f432da | ||
|
|
d193460649 | ||
|
|
02ff2d3c2e | ||
|
|
72544ccb54 | ||
|
|
ba1b9796e8 | ||
|
|
2f7b3bb9a0 | ||
|
|
5c323fc40f | ||
|
|
d3d47d448a | ||
|
|
9c5cda793d | ||
|
|
243f5b1b96 | ||
|
|
a57ffba8ba | ||
|
|
8f080389fe | ||
|
|
2a13696d06 | ||
|
|
af4463fef1 | ||
|
|
c8aa72186e | ||
|
|
c1a2e99f63 | ||
|
|
9d874e5c6f |
@@ -11,11 +11,11 @@ body:
|
||||
## 在提交问题之前,请确认以下事项:
|
||||
|
||||
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填写一个简洁明了的标题,以便他人快速检索
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似 issue,否则请在已有的 issue 下进行讨论
|
||||
3. 请 **务必** 给 issue 填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 查看 [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) 版本更新日志
|
||||
5. 请 **务必** 尝试 [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) 版本,确定问题是否仍然存在
|
||||
6. 请 **务必** 按照模板规范详细描述问题以及尝试更新 Alpha 版本,否则issue将会被直接关闭
|
||||
6. 请 **务必** 按照模板规范详细描述问题以及尝试更新 AutoBuild 版本,否则 issue 将会被直接关闭
|
||||
|
||||
## Before submitting the issue, please make sure of the following checklist:
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ body:
|
||||
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下进行讨论
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似 issue,否则请在已有的 issue 下进行讨论
|
||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 先下载 [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) 版本测试,确保该功能还未实现
|
||||
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
|
||||
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
|
||||
|
||||
+18
-18
@@ -35,7 +35,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check tag and package.json version
|
||||
id: check_tag
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Delete Old Alpha Tags Except Latest
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -180,23 +180,23 @@ jobs:
|
||||
needs: delete_old_assets
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Fetch UPDATE logs
|
||||
id: fetch_update_logs
|
||||
run: |
|
||||
if [ -f "UPDATELOG.md" ]; then
|
||||
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
|
||||
if [ -f "Changelog.md" ]; then
|
||||
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' Changelog.md)
|
||||
if [ -n "$UPDATE_LOGS" ]; then
|
||||
echo "Found update logs"
|
||||
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
|
||||
echo "$UPDATE_LOGS" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
else
|
||||
echo "No update sections found in UPDATELOG.md"
|
||||
echo "No update sections found in Changelog.md"
|
||||
fi
|
||||
else
|
||||
echo "UPDATELOG.md file not found"
|
||||
echo "Changelog.md file not found"
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
@@ -275,7 +275,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -305,9 +305,9 @@ jobs:
|
||||
echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -360,7 +360,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -375,9 +375,9 @@ jobs:
|
||||
save-if: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -491,7 +491,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
@@ -503,9 +503,9 @@ jobs:
|
||||
save-if: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -522,8 +522,8 @@ jobs:
|
||||
|
||||
- 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
|
||||
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/133.0.3065.92/Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab
|
||||
Expand .\Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ 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
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
|
||||
+116
-103
@@ -31,35 +31,22 @@ jobs:
|
||||
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Fetch UPDATE logs
|
||||
id: fetch_update_logs
|
||||
run: |
|
||||
if [ -f "UPDATELOG.md" ]; then
|
||||
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
|
||||
if [ -n "$UPDATE_LOGS" ]; then
|
||||
echo "Found update logs"
|
||||
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
|
||||
echo "$UPDATE_LOGS" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
else
|
||||
echo "No update sections found in UPDATELOG.md"
|
||||
fi
|
||||
else
|
||||
echo "UPDATELOG.md file not found"
|
||||
fi
|
||||
run: bash ./scripts/extract_update_logs.sh
|
||||
shell: bash
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@v4.2.0
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -90,20 +77,20 @@ jobs:
|
||||
|
||||
### Windows (不再支持Win7)
|
||||
#### 正常版本(推荐)
|
||||
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup_windows.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup_windows.exe)
|
||||
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup.exe)
|
||||
|
||||
#### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用)
|
||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_fixed_webview2-setup.exe) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64_fixed_webview2-setup.exe)
|
||||
|
||||
### macOS
|
||||
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64_darwin.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_darwin.dmg)
|
||||
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64.dmg)
|
||||
|
||||
### Linux
|
||||
#### DEB包(Debian系) 使用 apt ./路径 安装
|
||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64_linux.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
|
||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
|
||||
|
||||
#### RPM包(Redhat系) 使用 dnf ./路径 安装
|
||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.x86_64_linux.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.armhfp.rpm)
|
||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.x86_64.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.armhfp.rpm)
|
||||
|
||||
### FAQ
|
||||
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
|
||||
@@ -122,7 +109,7 @@ jobs:
|
||||
body_path: release.txt
|
||||
prerelease: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
generate_release_notes: true
|
||||
generate_release_notes: false
|
||||
|
||||
clean_old_assets:
|
||||
name: Clean Old Release Assets
|
||||
@@ -150,15 +137,18 @@ jobs:
|
||||
target: aarch64-apple-darwin
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
- os: ubuntu-22.04
|
||||
- os: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: "1.91.0"
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
@@ -166,17 +156,28 @@ jobs:
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
shared-key: autobuild-${{ runner.os }}-${{ matrix.target }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/Cargo.lock') }}
|
||||
prefix-key: "v1-rust"
|
||||
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
workspaces: |
|
||||
. -> target
|
||||
cache-all-crates: true
|
||||
cache-workspace-crates: true
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
if: matrix.os == 'ubuntu-24.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||
sudo apt install \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
build-essential \
|
||||
curl \
|
||||
wget \
|
||||
file \
|
||||
libxdo-dev \
|
||||
libssl-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev
|
||||
|
||||
- name: Install x86 OpenSSL (macOS only)
|
||||
if: matrix.target == 'x86_64-apple-darwin'
|
||||
@@ -187,24 +188,24 @@ jobs:
|
||||
echo "OPENSSL_LIB_DIR=$(brew --prefix openssl@3)/lib" >> $GITHUB_ENV
|
||||
echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> $GITHUB_ENV
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@v4.2.0
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}
|
||||
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
@@ -214,6 +215,13 @@ jobs:
|
||||
- name: Release ${{ env.TAG_CHANNEL }} Version
|
||||
run: pnpm release-version autobuild-latest
|
||||
|
||||
- name: Add Rust Target
|
||||
run: |
|
||||
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
|
||||
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
|
||||
rustup target list --installed
|
||||
echo "Rust target ${{ matrix.target }} installed."
|
||||
|
||||
- name: Tauri build for Windows-macOS-Linux
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
@@ -245,6 +253,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# It should be ubuntu-22.04 to match the cross-compilation environment
|
||||
# ortherwise it is hard to resolve the dependencies
|
||||
- os: ubuntu-22.04
|
||||
target: aarch64-unknown-linux-gnu
|
||||
arch: arm64
|
||||
@@ -254,10 +264,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: "1.91.0"
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
@@ -265,30 +278,32 @@ jobs:
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
shared-key: autobuild-${{ runner.os }}-${{ matrix.target }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/Cargo.lock') }}
|
||||
prefix-key: "v1-rust"
|
||||
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
workspaces: |
|
||||
. -> target
|
||||
cache-all-crates: true
|
||||
cache-workspace-crates: true
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}
|
||||
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
@@ -298,8 +313,8 @@ jobs:
|
||||
- name: Release ${{ env.TAG_CHANNEL }} Version
|
||||
run: pnpm release-version autobuild-latest
|
||||
|
||||
- name: Setup for linux
|
||||
run: |
|
||||
- name: "Setup for linux"
|
||||
run: |-
|
||||
sudo ls -lR /etc/apt/
|
||||
|
||||
cat > /tmp/sources.list << EOF
|
||||
@@ -318,14 +333,9 @@ jobs:
|
||||
sudo mv /tmp/sources.list /etc/apt/sources.list
|
||||
|
||||
sudo dpkg --add-architecture ${{ matrix.arch }}
|
||||
sudo apt-get update -y
|
||||
sudo apt-get -f install -y
|
||||
sudo apt update
|
||||
|
||||
sudo apt-get install -y \
|
||||
linux-libc-dev:${{ matrix.arch }} \
|
||||
libc6-dev:${{ matrix.arch }}
|
||||
|
||||
sudo apt-get install -y \
|
||||
sudo apt install -y \
|
||||
libxslt1.1:${{ matrix.arch }} \
|
||||
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
|
||||
libayatana-appindicator3-dev:${{ matrix.arch }} \
|
||||
@@ -347,6 +357,13 @@ jobs:
|
||||
gcc-arm-linux-gnueabihf \
|
||||
g++-arm-linux-gnueabihf
|
||||
|
||||
- name: Add Rust Target
|
||||
run: |
|
||||
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
|
||||
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
|
||||
rustup target list --installed
|
||||
echo "Rust target ${{ matrix.target }} installed."
|
||||
|
||||
- name: Tauri Build for Linux
|
||||
run: |
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
@@ -378,8 +395,8 @@ jobs:
|
||||
prerelease: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: |
|
||||
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
|
||||
target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
target/${{ matrix.target }}/release/bundle/rpm/*.rpm
|
||||
|
||||
autobuild-x86-arm-windows_webview2:
|
||||
name: Autobuild x86 and ARM Windows with WebView2
|
||||
@@ -398,7 +415,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
@@ -406,30 +423,32 @@ jobs:
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
shared-key: autobuild-${{ runner.os }}-${{ matrix.target }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/Cargo.lock') }}
|
||||
prefix-key: "v1-rust"
|
||||
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
workspaces: |
|
||||
. -> target
|
||||
cache-all-crates: true
|
||||
cache-workspace-crates: true
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}
|
||||
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
@@ -441,11 +460,18 @@ jobs:
|
||||
|
||||
- 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
|
||||
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/133.0.3065.92/Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab
|
||||
Expand .\Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ 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: Add Rust Target
|
||||
run: |
|
||||
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
|
||||
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
|
||||
rustup target list --installed
|
||||
echo "Rust target ${{ matrix.target }} installed."
|
||||
|
||||
- name: Tauri build for Windows
|
||||
id: build
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
@@ -461,19 +487,19 @@ jobs:
|
||||
|
||||
- name: Rename
|
||||
run: |
|
||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
|
||||
$files = Get-ChildItem ".\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"
|
||||
$files = Get-ChildItem ".\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"
|
||||
$files = Get-ChildItem ".\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
|
||||
@@ -486,7 +512,7 @@ jobs:
|
||||
name: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
|
||||
prerelease: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
||||
files: target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
||||
|
||||
- name: Portable Bundle
|
||||
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --${{ env.TAG_NAME }}
|
||||
@@ -505,32 +531,19 @@ jobs:
|
||||
]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Fetch UPDATE logs
|
||||
id: fetch_update_logs
|
||||
run: |
|
||||
if [ -f "UPDATELOG.md" ]; then
|
||||
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
|
||||
if [ -n "$UPDATE_LOGS" ]; then
|
||||
echo "Found update logs"
|
||||
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
|
||||
echo "$UPDATE_LOGS" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
else
|
||||
echo "No update sections found in UPDATELOG.md"
|
||||
fi
|
||||
else
|
||||
echo "UPDATELOG.md file not found"
|
||||
fi
|
||||
run: bash ./scripts/extract_update_logs.sh
|
||||
shell: bash
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@v4.2.0
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
@@ -565,20 +578,20 @@ jobs:
|
||||
|
||||
### Windows (不再支持Win7)
|
||||
#### 正常版本(推荐)
|
||||
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup_windows.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup_windows.exe)
|
||||
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup.exe)
|
||||
|
||||
#### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用)
|
||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_fixed_webview2-setup.exe) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64_fixed_webview2-setup.exe)
|
||||
|
||||
### macOS
|
||||
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64_darwin.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_darwin.dmg)
|
||||
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64.dmg)
|
||||
|
||||
### Linux
|
||||
#### DEB包(Debian系) 使用 apt ./路径 安装
|
||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64_linux.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
|
||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
|
||||
|
||||
#### RPM包(Redhat系) 使用 dnf ./路径 安装
|
||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.x86_64_linux.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.armhfp.rpm)
|
||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.x86_64.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.armhfp.rpm)
|
||||
|
||||
### FAQ
|
||||
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
|
||||
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
autobuild_version: ${{ steps.check.outputs.autobuild_version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 50
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
autobuild_version: ${{ steps.check.outputs.autobuild_version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 50
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
echo "🔍 Finding last commit with Tauri-related changes..."
|
||||
|
||||
# Define patterns for Tauri-related files
|
||||
TAURI_PATTERNS="src/ src-tauri/src src-tauri/Cargo.toml src-tauri/Cargo.lock src-tauri/tauri.*.conf.json src-tauri/build.rs src-tauri/capabilities"
|
||||
TAURI_PATTERNS="src/ src-tauri/src src-tauri/Cargo.toml Cargo.lock src-tauri/tauri.*.conf.json src-tauri/build.rs src-tauri/capabilities"
|
||||
|
||||
# Get the last commit that changed any of these patterns (excluding build artifacts)
|
||||
LAST_TAURI_COMMIT=""
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
needs: check_current_version
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Clean old assets from release
|
||||
env:
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -41,9 +41,9 @@ jobs:
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
+33
-18
@@ -70,24 +70,22 @@ jobs:
|
||||
|
||||
- name: Checkout Repository
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust Stable
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Add Rust Target
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
uses: dtolnay/rust-toolchain@1.91.0
|
||||
|
||||
- name: Rust Cache
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
save-if: false
|
||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
prefix-key: "v1-rust"
|
||||
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
workspaces: |
|
||||
. -> target
|
||||
cache-all-crates: true
|
||||
shared-key: autobuild-shared
|
||||
cache-workspace-crates: true
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-22.04' && github.event.inputs[matrix.input] == 'true'
|
||||
@@ -103,11 +101,20 @@ jobs:
|
||||
|
||||
- name: Install Node
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "24.13.1"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
restore-keys: |
|
||||
pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}
|
||||
lookup-only: true
|
||||
|
||||
- name: Pnpm install and check
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
run: |
|
||||
@@ -118,6 +125,14 @@ jobs:
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
run: pnpm release-version ${{ env.TAG_NAME }}
|
||||
|
||||
- name: Add Rust Target
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
run: |
|
||||
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
|
||||
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
|
||||
rustup target list --installed
|
||||
echo "Rust target ${{ matrix.target }} installed."
|
||||
|
||||
- name: Tauri build
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
@@ -138,24 +153,24 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts (macOS)
|
||||
if: matrix.os == 'macos-latest' && github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
|
||||
path: target/${{ matrix.target }}/release/bundle/dmg/*.dmg
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Artifacts (Windows)
|
||||
if: matrix.os == 'windows-latest' && github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*.exe
|
||||
path: target/${{ matrix.target }}/release/bundle/nsis/*.exe
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Artifacts (Linux)
|
||||
if: matrix.os == 'ubuntu-22.04' && github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
path: target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
name: Frontend Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
HUSKY: 0
|
||||
|
||||
jobs:
|
||||
frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Check frontend changes
|
||||
id: check_frontend
|
||||
uses: dorny/paths-filter@v3
|
||||
with:
|
||||
filters: |
|
||||
frontend:
|
||||
- 'src/**'
|
||||
- '**/*.js'
|
||||
- '**/*.ts'
|
||||
- '**/*.tsx'
|
||||
- '**/*.css'
|
||||
- '**/*.scss'
|
||||
- '**/*.json'
|
||||
- '**/*.md'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'eslint.config.ts'
|
||||
- 'tsconfig.json'
|
||||
- 'vite.config.*'
|
||||
|
||||
- name: Skip if no frontend changes
|
||||
if: steps.check_frontend.outputs.frontend != 'true'
|
||||
run: echo "No frontend changes, skipping frontend checks."
|
||||
|
||||
- name: Install pnpm
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
with:
|
||||
node-version: "24.13.1"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Restore pnpm cache
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}"
|
||||
restore-keys: |
|
||||
pnpm-shared-stable-${{ runner.os }}-
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
|
||||
- name: Run Prettier check
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
run: pnpm format:check
|
||||
|
||||
- name: Run ESLint
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
run: pnpm lint
|
||||
|
||||
- name: Run TypeScript typecheck
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
run: pnpm typecheck
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
echo "Manual trigger detected: skipping changes check and running clippy."
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
@@ -58,11 +58,13 @@ jobs:
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
prefix-key: "v1-rust"
|
||||
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
workspaces: |
|
||||
. -> target
|
||||
cache-all-crates: true
|
||||
save-if: false
|
||||
shared-key: autobuild-${{ runner.os }}-${{ matrix.target }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/Cargo.lock') }}
|
||||
cache-workspace-crates: true
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -69,24 +69,11 @@ jobs:
|
||||
needs: [release, release-for-linux-arm, release-for-fixed-webview2]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Fetch UPDATE logs
|
||||
id: fetch_update_logs
|
||||
run: |
|
||||
if [ -f "UPDATELOG.md" ]; then
|
||||
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
|
||||
if [ -n "$UPDATE_LOGS" ]; then
|
||||
echo "Found update logs"
|
||||
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
|
||||
echo "$UPDATE_LOGS" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
else
|
||||
echo "No update sections found in UPDATELOG.md"
|
||||
fi
|
||||
else
|
||||
echo "UPDATELOG.md file not found"
|
||||
fi
|
||||
run: bash ./scripts/extract_update_logs.sh
|
||||
shell: bash
|
||||
|
||||
- name: Set Env
|
||||
@@ -134,6 +121,7 @@ jobs:
|
||||
|
||||
### 稳定机场VPN推荐
|
||||
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
Created at ${{ env.BUILDTIME }}.
|
||||
EOF
|
||||
|
||||
@@ -169,10 +157,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: "1.91.0"
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
@@ -180,8 +171,13 @@ jobs:
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
save-if: false
|
||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
prefix-key: "v1-rust"
|
||||
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
workspaces: |
|
||||
. -> target
|
||||
cache-all-crates: true
|
||||
cache-workspace-crates: true
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
@@ -199,9 +195,9 @@ jobs:
|
||||
echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -213,9 +209,16 @@ jobs:
|
||||
pnpm i
|
||||
pnpm run prebuild ${{ matrix.target }}
|
||||
|
||||
- name: Add Rust Target
|
||||
run: |
|
||||
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
|
||||
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
|
||||
rustup target list --installed
|
||||
echo "Rust target ${{ matrix.target }} installed."
|
||||
|
||||
- name: Tauri build
|
||||
# 上游 5.24 修改了 latest.json 的生成逻辑,且依赖 tauri-plugin-update 2.10.0 暂未发布,故锁定在 0.5.23 版本
|
||||
uses: tauri-apps/tauri-action@v0.5.23
|
||||
uses: tauri-apps/tauri-action@v0.6.1
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -253,10 +256,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: "1.91.0"
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
@@ -264,13 +270,18 @@ jobs:
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
save-if: false
|
||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
prefix-key: "v1-rust"
|
||||
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
workspaces: |
|
||||
. -> target
|
||||
cache-all-crates: true
|
||||
cache-workspace-crates: true
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -326,6 +337,13 @@ jobs:
|
||||
gcc-arm-linux-gnueabihf \
|
||||
g++-arm-linux-gnueabihf
|
||||
|
||||
- name: Add Rust Target
|
||||
run: |
|
||||
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
|
||||
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
|
||||
rustup target list --installed
|
||||
echo "Rust target ${{ matrix.target }} installed."
|
||||
|
||||
- name: Build for Linux
|
||||
run: |
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
@@ -358,8 +376,8 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
prerelease: ${{ contains(github.ref_name, '-rc') }}
|
||||
files: |
|
||||
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
|
||||
target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
target/${{ matrix.target }}/release/bundle/rpm/*.rpm
|
||||
|
||||
release-for-fixed-webview2:
|
||||
name: Release Build for Fixed WebView2
|
||||
@@ -377,7 +395,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: "1.91.0"
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
@@ -385,13 +409,18 @@ jobs:
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
save-if: false
|
||||
save-if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
prefix-key: "v1-rust"
|
||||
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
workspaces: |
|
||||
. -> target
|
||||
cache-all-crates: true
|
||||
cache-workspace-crates: true
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -405,15 +434,21 @@ jobs:
|
||||
|
||||
- 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
|
||||
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/133.0.3065.92/Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab
|
||||
Expand .\Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ 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: Add Rust Target
|
||||
run: |
|
||||
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
|
||||
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
|
||||
rustup target list --installed
|
||||
echo "Rust target ${{ matrix.target }} installed."
|
||||
|
||||
- name: Tauri build
|
||||
id: build
|
||||
# 上游 5.24 修改了 latest.json 的生成逻辑,且依赖 tauri-plugin-update 2.10.0 暂未发布,故锁定在 0.5.23 版本
|
||||
uses: tauri-apps/tauri-action@v0.5.23
|
||||
uses: tauri-apps/tauri-action@v0.6.1
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -425,19 +460,19 @@ jobs:
|
||||
|
||||
- name: Rename
|
||||
run: |
|
||||
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
|
||||
$files = Get-ChildItem ".\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"
|
||||
$files = Get-ChildItem ".\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"
|
||||
$files = Get-ChildItem ".\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
|
||||
@@ -451,7 +486,7 @@ jobs:
|
||||
body: "See release notes for detailed changelog."
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
prerelease: ${{ contains(github.ref_name, '-rc') }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
||||
files: target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
||||
|
||||
- name: Portable Bundle
|
||||
run: pnpm portable-fixed-webview2 ${{ matrix.target }}
|
||||
@@ -465,12 +500,12 @@ jobs:
|
||||
needs: [update_tag]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -491,12 +526,12 @@ jobs:
|
||||
needs: [update_tag]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -518,7 +553,7 @@ jobs:
|
||||
needs: [update_tag, release-update]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get Version
|
||||
@@ -548,30 +583,17 @@ jobs:
|
||||
]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Fetch UPDATE logs
|
||||
id: fetch_update_logs
|
||||
run: |
|
||||
if [ -f "UPDATELOG.md" ]; then
|
||||
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
|
||||
if [ -n "$UPDATE_LOGS" ]; then
|
||||
echo "Found update logs"
|
||||
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
|
||||
echo "$UPDATE_LOGS" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
else
|
||||
echo "No update sections found in UPDATELOG.md"
|
||||
fi
|
||||
else
|
||||
echo "UPDATELOG.md file not found"
|
||||
fi
|
||||
run: bash ./scripts/extract_update_logs.sh
|
||||
shell: bash
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -625,6 +647,7 @@ jobs:
|
||||
|
||||
### 稳定机场VPN推荐
|
||||
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
Created at ${{ env.BUILDTIME }}.
|
||||
EOF
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
rustfmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Check Rust changes
|
||||
id: check_rust
|
||||
@@ -39,42 +39,6 @@ jobs:
|
||||
if: steps.check_rust.outputs.rust == 'true'
|
||||
run: cargo fmt --manifest-path ./src-tauri/Cargo.toml --all -- --check
|
||||
|
||||
prettier:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check Web changes
|
||||
id: check_web
|
||||
uses: dorny/paths-filter@v3
|
||||
with:
|
||||
filters: |
|
||||
web:
|
||||
- 'src/**'
|
||||
- '**/*.js'
|
||||
- '**/*.ts'
|
||||
- '**/*.tsx'
|
||||
- '**/*.css'
|
||||
- '**/*.scss'
|
||||
- '**/*.json'
|
||||
- '**/*.md'
|
||||
- '**/*.json'
|
||||
|
||||
- name: Skip if no Web changes
|
||||
if: steps.check_web.outputs.web != 'true'
|
||||
run: echo "No web changes, skipping prettier."
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
if: steps.check_web.outputs.web == 'true'
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
- run: corepack enable
|
||||
if: steps.check_web.outputs.web == 'true'
|
||||
- run: pnpm install --frozen-lockfile
|
||||
if: steps.check_web.outputs.web == 'true'
|
||||
- run: pnpm format:check
|
||||
if: steps.check_web.outputs.web == 'true'
|
||||
|
||||
# taplo:
|
||||
# name: taplo (.toml files)
|
||||
# runs-on: ubuntu-latest
|
||||
@@ -10,12 +10,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -34,12 +34,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24.13.1"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
+3
-1
@@ -11,4 +11,6 @@ scripts/_env.sh
|
||||
.idea
|
||||
.old
|
||||
.eslintcache
|
||||
target
|
||||
.changelog_backups
|
||||
target
|
||||
CLAUDE.md
|
||||
|
||||
+5
-35
@@ -1,44 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(git rev-parse --show-toplevel)"
|
||||
cd "$ROOT_DIR"
|
||||
if ! command -v "cargo-make" >/dev/null 2>&1; then
|
||||
echo "❌ cargo-make is required for pre-commit checks."
|
||||
cargo install --force cargo-make
|
||||
fi
|
||||
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "❌ pnpm is required for pre-commit checks."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[pre-commit] Running lint-staged for JS/TS files..."
|
||||
pnpm exec lint-staged
|
||||
|
||||
RUST_FILES="$(git diff --cached --name-only --diff-filter=ACMR | grep -E '^src-tauri/.*\.rs$' || true)"
|
||||
if [ -n "$RUST_FILES" ]; then
|
||||
echo "[pre-commit] Formatting Rust changes with cargo fmt..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo fmt
|
||||
)
|
||||
while IFS= read -r file; do
|
||||
[ -n "$file" ] && git add "$file"
|
||||
done <<< "$RUST_FILES"
|
||||
|
||||
echo "[pre-commit] Linting Rust changes with cargo clippy..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo clippy-all
|
||||
if ! command -v clash-verge-logging-check >/dev/null 2>&1; then
|
||||
echo "[pre-commit] Installing clash-verge-logging-check..."
|
||||
cargo install --git https://github.com/clash-verge-rev/clash-verge-logging-check.git
|
||||
fi
|
||||
clash-verge-logging-check
|
||||
)
|
||||
fi
|
||||
|
||||
TS_FILES="$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(ts|tsx)$' || true)"
|
||||
if [ -n "$TS_FILES" ]; then
|
||||
echo "[pre-commit] Running TypeScript type check..."
|
||||
pnpm typecheck
|
||||
fi
|
||||
|
||||
echo "[pre-commit] All checks completed successfully."
|
||||
cargo make pre-commit
|
||||
|
||||
+4
-38
@@ -1,43 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
remote_name="${1:-origin}"
|
||||
remote_url="${2:-unknown}"
|
||||
|
||||
ROOT_DIR="$(git rev-parse --show-toplevel)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "❌ pnpm is required for pre-push checks."
|
||||
exit 1
|
||||
if ! command -v "cargo-make" >/dev/null 2>&1; then
|
||||
echo "❌ cargo-make is required for pre-push checks."
|
||||
cargo install --force cargo-make
|
||||
fi
|
||||
|
||||
echo "[pre-push] Preparing to push to '$remote_name' ($remote_url). Running full validation..."
|
||||
|
||||
echo "[pre-push] Checking Prettier formatting..."
|
||||
pnpm format:check
|
||||
|
||||
echo "[pre-push] Running ESLint..."
|
||||
pnpm lint
|
||||
|
||||
echo "[pre-push] Running TypeScript type checking..."
|
||||
pnpm typecheck
|
||||
|
||||
if command -v cargo >/dev/null 2>&1; then
|
||||
echo "[pre-push] Verifying Rust formatting..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo fmt --check
|
||||
)
|
||||
|
||||
echo "[pre-push] Running cargo clippy..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo clippy-all
|
||||
)
|
||||
else
|
||||
echo "[pre-push] ⚠️ cargo not found; skipping Rust checks."
|
||||
fi
|
||||
|
||||
echo "[pre-push] All checks passed."
|
||||
exit 0
|
||||
cargo make pre-push
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
queue_rules:
|
||||
- name: LetMeMergeForYou
|
||||
batch_size: 3
|
||||
allow_queue_branch_edit: true
|
||||
queue_conditions: []
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
# README.md
|
||||
# UPDATELOG.md
|
||||
# Changelog.md
|
||||
# CONTRIBUTING.md
|
||||
|
||||
.changelog_backups
|
||||
pnpm-lock.yaml
|
||||
|
||||
src-tauri/target/
|
||||
|
||||
+64
-79
@@ -1,151 +1,136 @@
|
||||
# CONTRIBUTING
|
||||
|
||||
Thank you for your interest in contributing to Clash Verge Rev! This document provides guidelines and instructions to help you set up your development environment and start contributing.
|
||||
Thank you for your interest in contributing to **Clash Verge Rev**! This guide provides instructions to help you set up your development environment and start contributing effectively.
|
||||
|
||||
## Internationalization (i18n)
|
||||
|
||||
We welcome translations and improvements to existing locales. Please follow the detailed guidelines in [CONTRIBUTING_i18n.md](docs/CONTRIBUTING_i18n.md) for instructions on extracting strings, file naming conventions, testing translations, and submitting translation PRs.
|
||||
We welcome translations and improvements to existing locales. For details on contributing translations, please see [CONTRIBUTING_i18n.md](docs/CONTRIBUTING_i18n.md).
|
||||
|
||||
## Development Setup
|
||||
|
||||
Before you start contributing to the project, you need to set up your development environment. Here are the steps you need to follow:
|
||||
Before contributing, you need to set up your development environment. Follow the steps below carefully.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Install Rust and Node.js**: Our project requires both Rust and Node.js. Please follow the instructions provided [here](https://tauri.app/start/prerequisites/) to install them on your system.
|
||||
1. **Install Rust and Node.js**
|
||||
Our project requires both Rust and Node.js. Follow the official installation instructions [here](https://tauri.app/start/prerequisites/).
|
||||
|
||||
### Setup for Windows Users
|
||||
### Windows Users
|
||||
|
||||
> [!NOTE]
|
||||
> **If you are using a Windows ARM device, you additionally need to install [LLVM](https://github.com/llvm/llvm-project/releases) (including clang) and set the environment variable.**
|
||||
>
|
||||
> Because the `ring` crate is compiled based on `clang` under Windows ARM.
|
||||
> [!NOTE]
|
||||
> **Windows ARM users must also install [LLVM](https://github.com/llvm/llvm-project/releases) (including clang) and set the corresponding environment variables.**
|
||||
> The `ring` crate depends on `clang` when building on Windows ARM.
|
||||
|
||||
If you're a Windows user, you may need to perform some additional steps:
|
||||
Additional steps for Windows:
|
||||
|
||||
- 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
|
||||
- Ensure Rust and Node.js are added to your system `PATH`.
|
||||
|
||||
When you setup `Rust` environment, Only use toolchain with `Windows MSVC` , to change settings follow command:
|
||||
- Install the GNU `patch` tool.
|
||||
|
||||
```shell
|
||||
- Use the MSVC toolchain for Rust:
|
||||
|
||||
```bash
|
||||
rustup target add x86_64-pc-windows-msvc
|
||||
rustup set default-host x86_64-pc-windows-msvc
|
||||
```
|
||||
|
||||
### Install Node.js Package
|
||||
### Install Node.js Package Manager
|
||||
|
||||
After installing Rust and Node.js, install the necessary Node.js and Node Package Manager:
|
||||
Enable `corepack`:
|
||||
|
||||
```shell
|
||||
npm install pnpm -g
|
||||
```bash
|
||||
corepack enable
|
||||
```
|
||||
|
||||
### Install Dependencies
|
||||
### Install Project Dependencies
|
||||
|
||||
Install node packages
|
||||
Node.js dependencies:
|
||||
|
||||
```shell
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Install apt packages ONLY for Ubuntu
|
||||
Ubuntu-only system packages:
|
||||
|
||||
```shell
|
||||
apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||
```bash
|
||||
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||
```
|
||||
|
||||
### Download the Mihomo Core Binary
|
||||
### Download the Mihomo Core Binary (Automatic)
|
||||
|
||||
You have two options for downloading the clash binary:
|
||||
|
||||
- Automatically download it via the provided script:
|
||||
|
||||
```shell
|
||||
pnpm run prebuild
|
||||
# Use '--force' or '-f' to update both the Mihomo core version
|
||||
# and the Clash Verge Rev service version to the latest available.
|
||||
pnpm run prebuild --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).
|
||||
```bash
|
||||
pnpm run prebuild
|
||||
pnpm run prebuild --force # Re-download and overwrite Mihomo core and service binaries
|
||||
```
|
||||
|
||||
### Run the Development Server
|
||||
|
||||
To run the development server, use the following command:
|
||||
|
||||
```shell
|
||||
pnpm dev
|
||||
# If an app instance already exists, use a different command
|
||||
pnpm dev:diff
|
||||
# To using tauri built-in dev tool
|
||||
pnpm dev:tauri
|
||||
```bash
|
||||
pnpm dev # Standard
|
||||
pnpm dev:diff # If an app instance already exists
|
||||
pnpm dev:tauri # Run Tauri development mode
|
||||
```
|
||||
|
||||
### Build the Project
|
||||
|
||||
To build this project:
|
||||
Standard build:
|
||||
|
||||
```shell
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
For a faster build, use the following command
|
||||
Fast build for testing:
|
||||
|
||||
```shell
|
||||
```bash
|
||||
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.
|
||||
### Clean Build
|
||||
|
||||
The `Artifacts` will display in the `log` in the Terminal.
|
||||
|
||||
### Build clean
|
||||
|
||||
To clean rust build:
|
||||
|
||||
```shell
|
||||
```bash
|
||||
pnpm clean
|
||||
```
|
||||
|
||||
### Portable Version (Windows Only)
|
||||
|
||||
To package portable version after the build:
|
||||
|
||||
```shell
|
||||
```bash
|
||||
pnpm portable
|
||||
```
|
||||
|
||||
## Contributing Your Changes
|
||||
|
||||
#### Before commit your changes
|
||||
### Before Committing
|
||||
|
||||
If you changed the rust code, it's recommanded to execute code style formatting and quailty checks.
|
||||
|
||||
1. Code quailty checks
|
||||
**Code quality checks:**
|
||||
|
||||
```bash
|
||||
# For rust backend
|
||||
$ clash-verge-rev: pnpm clippy
|
||||
# For frontend (not yet).
|
||||
# Rust backend
|
||||
cargo clippy-all
|
||||
# Frontend
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
2. Code style formatting
|
||||
**Code formatting:**
|
||||
|
||||
```bash
|
||||
# For rust backend
|
||||
$ clash-verge-rev: cd src-tauri
|
||||
$ clash-verge-rev/src-tauri: cargo fmt
|
||||
# For frontend
|
||||
$ clash-verge-rev: pnpm format:check
|
||||
$ clash-verge-rev: pnpm format
|
||||
# Rust backend
|
||||
cargo fmt
|
||||
# Frontend
|
||||
pnpm format
|
||||
```
|
||||
|
||||
Once you have made your changes:
|
||||
### Signing your commit
|
||||
|
||||
Signed commits are required to verify authorship and ensure your contributions can be merged. Reference signing-commits [here](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits).
|
||||
|
||||
### Submitting Your Changes
|
||||
|
||||
1. Fork the repository.
|
||||
2. Create a new branch for your feature or bug fix.
|
||||
3. Commit your changes with clear and concise commit messages.
|
||||
4. Push your branch to your fork and submit a pull request to our repository.
|
||||
|
||||
We appreciate your contributions and look forward to your active participation in our project!
|
||||
2. Create a new branch for your feature or bug fix.
|
||||
|
||||
3. Commit your changes with clear messages and make sure it's signed.
|
||||
|
||||
4. Push your branch and submit a pull request.
|
||||
|
||||
We appreciate your contributions and look forward to your participation!
|
||||
|
||||
Generated
+1556
-1365
File diff suppressed because it is too large
Load Diff
+143
@@ -0,0 +1,143 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"src-tauri",
|
||||
"crates/clash-verge-draft",
|
||||
"crates/clash-verge-logging",
|
||||
"crates/clash-verge-signal",
|
||||
"crates/tauri-plugin-clash-verge-sysinfo",
|
||||
"crates/clash-verge-i18n",
|
||||
"crates/clash-verge-limiter",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
lto = "thin"
|
||||
opt-level = 3
|
||||
debug = false
|
||||
strip = true
|
||||
overflow-checks = false
|
||||
rpath = false
|
||||
|
||||
[profile.dev]
|
||||
incremental = true
|
||||
codegen-units = 64
|
||||
opt-level = 0
|
||||
debug = true
|
||||
strip = "none"
|
||||
overflow-checks = true
|
||||
lto = false
|
||||
rpath = false
|
||||
|
||||
[profile.fast-release]
|
||||
inherits = "release"
|
||||
codegen-units = 64
|
||||
incremental = true
|
||||
lto = false
|
||||
opt-level = 0
|
||||
debug = true
|
||||
strip = false
|
||||
|
||||
[profile.debug-release]
|
||||
inherits = "fast-release"
|
||||
codegen-units = 1
|
||||
split-debuginfo = "unpacked"
|
||||
|
||||
[workspace.dependencies]
|
||||
clash-verge-draft = { path = "crates/clash-verge-draft" }
|
||||
clash-verge-logging = { path = "crates/clash-verge-logging" }
|
||||
clash-verge-signal = { path = "crates/clash-verge-signal" }
|
||||
clash-verge-i18n = { path = "crates/clash-verge-i18n" }
|
||||
clash-verge-limiter = { path = "crates/clash-verge-limiter" }
|
||||
tauri-plugin-clash-verge-sysinfo = { path = "crates/tauri-plugin-clash-verge-sysinfo" }
|
||||
|
||||
tauri = { version = "2.10.2" }
|
||||
tauri-plugin-clipboard-manager = "2.3.2"
|
||||
parking_lot = { version = "0.12.5", features = ["hardware-lock-elision"] }
|
||||
anyhow = "1.0.102"
|
||||
criterion = { version = "0.8.2", features = ["async_tokio"] }
|
||||
tokio = { version = "1.49.0", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
"time",
|
||||
"sync",
|
||||
] }
|
||||
flexi_logger = "0.31.8"
|
||||
log = "0.4.29"
|
||||
|
||||
smartstring = { version = "1.0.1" }
|
||||
compact_str = { version = "0.9.0", features = ["serde"] }
|
||||
|
||||
serde = { version = "1.0.228" }
|
||||
serde_json = { version = "1.0.149" }
|
||||
serde_yaml_ng = { version = "0.10.0" }
|
||||
bitflags = { version = "2.11.0" }
|
||||
|
||||
# *** For Windows platform only ***
|
||||
deelevate = "0.2.0"
|
||||
# *********************************
|
||||
|
||||
[patch.crates-io]
|
||||
# Patches until https://github.com/tauri-apps/tao/pull/1167 is merged.
|
||||
tao = { git = "https://github.com/tauri-apps/tao" }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
correctness = { level = "deny", priority = -1 }
|
||||
suspicious = { level = "deny", priority = -1 }
|
||||
unwrap_used = "warn"
|
||||
expect_used = "warn"
|
||||
panic = "deny"
|
||||
unimplemented = "deny"
|
||||
todo = "warn"
|
||||
dbg_macro = "warn"
|
||||
clone_on_ref_ptr = "warn"
|
||||
rc_clone_in_vec_init = "warn"
|
||||
large_stack_arrays = "warn"
|
||||
large_const_arrays = "warn"
|
||||
async_yields_async = "deny"
|
||||
mutex_atomic = "deny"
|
||||
mutex_integer = "deny"
|
||||
rc_mutex = "deny"
|
||||
unused_async = "deny"
|
||||
await_holding_lock = "deny"
|
||||
large_futures = "deny"
|
||||
future_not_send = "deny"
|
||||
redundant_else = "deny"
|
||||
needless_continue = "deny"
|
||||
needless_raw_string_hashes = "deny"
|
||||
or_fun_call = "deny"
|
||||
cognitive_complexity = "deny"
|
||||
useless_let_if_seq = "deny"
|
||||
use_self = "deny"
|
||||
tuple_array_conversions = "deny"
|
||||
trait_duplication_in_bounds = "deny"
|
||||
suspicious_operation_groupings = "deny"
|
||||
string_lit_as_bytes = "deny"
|
||||
significant_drop_tightening = "deny"
|
||||
significant_drop_in_scrutinee = "deny"
|
||||
redundant_clone = "deny"
|
||||
# option_if_let_else = "deny" // 过于激进,暂时不开启
|
||||
needless_pass_by_ref_mut = "deny"
|
||||
needless_collect = "deny"
|
||||
missing_const_for_fn = "deny"
|
||||
iter_with_drain = "deny"
|
||||
iter_on_single_items = "deny"
|
||||
iter_on_empty_collections = "deny"
|
||||
# fallible_impl_from = "deny" // 过于激进,暂时不开启
|
||||
equatable_if_let = "deny"
|
||||
collection_is_never_read = "deny"
|
||||
branches_sharing_code = "deny"
|
||||
pathbuf_init_then_push = "deny"
|
||||
option_as_ref_cloned = "deny"
|
||||
large_types_passed_by_value = "deny"
|
||||
# implicit_clone = "deny" // 可能会造成额外开销,暂时不开启
|
||||
expl_impl_clone_on_copy = "deny"
|
||||
copy_iterator = "deny"
|
||||
cloned_instead_of_copied = "deny"
|
||||
# self_only_used_in_recursion = "deny" // Since 1.92.0
|
||||
unnecessary_self_imports = "deny"
|
||||
unused_trait_names = "deny"
|
||||
wildcard_imports = "deny"
|
||||
unnecessary_wraps = "deny"
|
||||
@@ -0,0 +1,19 @@
|
||||
## v2.4.7
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
- 修复 Windows 管理员身份运行时开关 TUN 模式异常
|
||||
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong> 🚀 优化改进 </strong></summary>
|
||||
|
||||
- 优化订阅错误通知,仅在手动触发时
|
||||
- 隐藏日志中的订阅信息
|
||||
- 优化部分界面文案文本
|
||||
|
||||
</details>
|
||||
@@ -0,0 +1,86 @@
|
||||
[config]
|
||||
skip_core_tasks = true
|
||||
skip_git_env_info = true
|
||||
skip_rust_env_info = true
|
||||
skip_crate_env_info = true
|
||||
|
||||
# --- Backend ---
|
||||
|
||||
[tasks.rust-format]
|
||||
install_crate = "rustfmt"
|
||||
command = "cargo"
|
||||
args = ["fmt", "--", "--emit=files"]
|
||||
|
||||
[tasks.rust-clippy]
|
||||
description = "Run cargo clippy to lint the code"
|
||||
command = "cargo"
|
||||
args = ["clippy", "--all-targets", "--all-features", "--", "-D", "warnings"]
|
||||
|
||||
# --- Frontend ---
|
||||
|
||||
[tasks.eslint]
|
||||
description = "Run ESLint to lint the code"
|
||||
command = "pnpm"
|
||||
args = ["lint"]
|
||||
[tasks.eslint.windows]
|
||||
command = "pnpm.cmd"
|
||||
|
||||
[tasks.typecheck]
|
||||
description = "Run type checks"
|
||||
command = "pnpm"
|
||||
args = ["typecheck"]
|
||||
[tasks.typecheck.windows]
|
||||
command = "pnpm.cmd"
|
||||
|
||||
[tasks.lint-staged]
|
||||
description = "Run lint-staged for staged files"
|
||||
command = "pnpm"
|
||||
args = ["exec", "lint-staged"]
|
||||
[tasks.lint-staged.windows]
|
||||
command = "pnpm.cmd"
|
||||
|
||||
# --- Jobs ---
|
||||
|
||||
# Rust format (for pre-commit)
|
||||
[tasks.rust-format-check]
|
||||
description = "Check Rust code formatting"
|
||||
dependencies = ["rust-format"]
|
||||
[tasks.rust-format-check.condition]
|
||||
files_modified.input = [
|
||||
"./src-tauri/**/*.rs",
|
||||
"./crates/**/*.rs",
|
||||
"**/Cargo.toml",
|
||||
]
|
||||
files_modified.output = ["./target/debug/*", "./target/release/*"]
|
||||
|
||||
# Rust lint (for pre-push)
|
||||
[tasks.rust-lint]
|
||||
description = "Run Rust linting"
|
||||
dependencies = ["rust-clippy"]
|
||||
[tasks.rust-lint.condition]
|
||||
files_modified.input = [
|
||||
"./src-tauri/**/*.rs",
|
||||
"./crates/**/*.rs",
|
||||
"**/Cargo.toml",
|
||||
]
|
||||
files_modified.output = ["./target/debug/*", "./target/release/*"]
|
||||
|
||||
# Frontend format (for pre-commit)
|
||||
[tasks.frontend-format]
|
||||
description = "Frontend format checks"
|
||||
dependencies = ["lint-staged"]
|
||||
|
||||
# Frontend lint (for pre-push)
|
||||
[tasks.frontend-lint]
|
||||
description = "Frontend linting and type checking"
|
||||
dependencies = ["eslint", "typecheck"]
|
||||
|
||||
# --- Git Hooks ---
|
||||
|
||||
[tasks.pre-commit]
|
||||
description = "Pre-commit checks: format only"
|
||||
dependencies = ["rust-format-check", "frontend-format"]
|
||||
|
||||
[tasks.pre-push]
|
||||
description = "Pre-push checks: lint and typecheck"
|
||||
dependencies = ["rust-lint", "frontend-lint"]
|
||||
@@ -16,7 +16,8 @@ A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a
|
||||
<a href="./docs/README_es.md">Español</a> ·
|
||||
<a href="./docs/README_ru.md">Русский</a> ·
|
||||
<a href="./docs/README_ja.md">日本語</a> ·
|
||||
<a href="./docs/README_ko.md">한국어</a>
|
||||
<a href="./docs/README_ko.md">한국어</a> ·
|
||||
<a href="./docs/README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## Preview
|
||||
@@ -47,30 +48,23 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||
|
||||
## Promotion
|
||||
|
||||
#### [狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
### ✈️ [狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
- 高性能海外机场,免费试用,优惠套餐,解锁流媒体,全球首家支持 Hysteria 协议。
|
||||
- 使用 Clash Verge 专属邀请链接注册送 3 天,每天 1G 流量免费试用:[点此注册](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
- Clash Verge 专属 8 折优惠码: verge20 (仅有 500 份)
|
||||
- 优惠套餐每月仅需 15.8 元,160G 流量,年付 8 折
|
||||
- 海外团队,无跑路风险,高达 50% 返佣
|
||||
- 集群负载均衡设计,高速专线(兼容老客户端),极低延迟,无视晚高峰,4K 秒开
|
||||
- 全球首家 Hysteria 协议机场,现已上线更快的 `Hysteria2` 协议(Clash Verge 客户端最佳搭配)
|
||||
- 解锁流媒体及 ChatGPT
|
||||
- 官网:[https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
🚀 高性能海外技术流机场,支持免费试用与优惠套餐,全面解锁流媒体及 AI 服务,全球首家采用 **QUIC 协议**。
|
||||
|
||||
#### 本项目的构建与发布环境由 [YXVM](https://yxvm.com/aff.php?aff=827) 独立服务器全力支持,
|
||||
🎁 使用 **Clash Verge 专属邀请链接** 注册即送 **3 天免费试用**,每日 **1GB 流量**:👉 [点此注册](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
感谢提供 独享资源、高性能、高速网络 的强大后端环境。如果你觉得下载够快、使用够爽,那是因为我们用了好服务器!
|
||||
#### **核心优势:**
|
||||
|
||||
🧩 YXVM 独立服务器优势:
|
||||
- 📱 自研 iOS 客户端(业内"唯一")技术经得起考验,极大**持续研发**投入
|
||||
- 🧑💻 **12小时真人客服**(顺带解决 Clash Verge 使用问题)
|
||||
- 💰 优惠套餐每月**仅需 21 元,160G 流量,年付 8 折**
|
||||
- 🌍 海外团队,无跑路风险,高达 50% 返佣
|
||||
- ⚙️ **集群负载均衡**设计,**负载监控和随时扩容**,高速专线(兼容老客户端),极低延迟,无视晚高峰,4K 秒开
|
||||
- ⚡ 全球首家**Quic 协议机场**,现已上线更快的 Tuic 协议(Clash Verge 客户端最佳搭配)
|
||||
- 🎬 解锁**流媒体及 主流 AI**
|
||||
|
||||
- 🌎 优质网络,回程优化,下载快到飞起
|
||||
- 🔧 物理机独享资源,非VPS可比,性能拉满
|
||||
- 🧠 适合跑代理、搭建 WEB 站 CDN 站 、搞 CI/CD 或任何高负载应用
|
||||
- 💡 支持即开即用,多机房选择,CN2 / IEPL 可选
|
||||
- 📦 本项目使用配置已在售,欢迎同款入手!
|
||||
- 🎯 想要同款构建体验?[立即下单 YXVM 独立服务器!](https://yxvm.com/aff.php?aff=827)
|
||||
🌐 官网:👉 [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "clash-verge-draft"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bench]]
|
||||
name = "draft_bench"
|
||||
path = "bench/benche_me.rs"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
parking_lot = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
@@ -3,17 +3,20 @@ use std::hint::black_box;
|
||||
use std::process;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use app_lib::config::IVerge;
|
||||
use app_lib::utils::Draft as DraftNew;
|
||||
use clash_verge_draft::Draft;
|
||||
|
||||
/// 创建测试数据
|
||||
fn make_draft() -> DraftNew<IVerge> {
|
||||
#[derive(Default, Clone, Debug)]
|
||||
struct IVerge {
|
||||
enable_auto_launch: Option<bool>,
|
||||
enable_tun_mode: Option<bool>,
|
||||
}
|
||||
|
||||
fn make_draft() -> Draft<IVerge> {
|
||||
let verge = IVerge {
|
||||
enable_auto_launch: Some(true),
|
||||
enable_tun_mode: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
DraftNew::new(verge)
|
||||
Draft::new(verge)
|
||||
}
|
||||
|
||||
pub fn bench_draft(c: &mut Criterion) {
|
||||
@@ -101,8 +104,7 @@ pub fn bench_draft(c: &mut Criterion) {
|
||||
let draft = black_box(make_draft());
|
||||
let _: Result<(), anyhow::Error> = draft
|
||||
.with_data_modify::<_, _, _>(|mut box_data| async move {
|
||||
box_data.enable_auto_launch =
|
||||
Some(!box_data.enable_auto_launch.unwrap_or(false));
|
||||
box_data.enable_auto_launch = Some(!box_data.enable_auto_launch.unwrap_or(false));
|
||||
Ok((box_data, ()))
|
||||
})
|
||||
.await;
|
||||
@@ -0,0 +1,100 @@
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type SharedDraft<T> = Arc<T>;
|
||||
type DraftInner<T> = (SharedDraft<T>, Option<SharedDraft<T>>);
|
||||
|
||||
/// Draft 管理:committed 与 optional draft 都以 Arc<Box<T>> 存储,
|
||||
// (committed_snapshot, optional_draft_snapshot)
|
||||
#[derive(Debug)]
|
||||
pub struct Draft<T> {
|
||||
inner: Arc<RwLock<DraftInner<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Clone> Draft<T> {
|
||||
#[inline]
|
||||
pub fn new(data: T) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new((Arc::new(data), None))),
|
||||
}
|
||||
}
|
||||
/// 以 Arc<Box<T>> 的形式获取当前“已提交(正式)”数据的快照(零拷贝,仅 clone Arc)
|
||||
#[inline]
|
||||
pub fn data_arc(&self) -> SharedDraft<T> {
|
||||
let guard = self.inner.read();
|
||||
Arc::clone(&guard.0)
|
||||
}
|
||||
|
||||
/// 获取当前(草稿若存在则返回草稿,否则返回已提交)的快照
|
||||
/// 这也是零拷贝:只 clone Arc,不 clone T
|
||||
#[inline]
|
||||
pub fn latest_arc(&self) -> SharedDraft<T> {
|
||||
let guard = self.inner.read();
|
||||
guard.1.clone().unwrap_or_else(|| Arc::clone(&guard.0))
|
||||
}
|
||||
|
||||
/// 通过闭包以可变方式编辑草稿(在闭包中我们给出 &mut T)
|
||||
/// - 延迟拷贝:如果只有这一个 Arc 引用,则直接修改,不会克隆 T;
|
||||
/// - 若草稿被其他读者共享,Arc::make_mut 会做一次 T.clone(最小必要拷贝)。
|
||||
#[inline]
|
||||
pub fn edit_draft<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut T) -> R,
|
||||
{
|
||||
let mut guard = self.inner.write();
|
||||
let mut draft_arc = guard.1.take().unwrap_or_else(|| Arc::clone(&guard.0));
|
||||
let data_mut = Arc::make_mut(&mut draft_arc);
|
||||
let result = f(data_mut);
|
||||
guard.1 = Some(draft_arc);
|
||||
result
|
||||
}
|
||||
|
||||
/// 将草稿提交到已提交位置(替换),并清除草稿
|
||||
#[inline]
|
||||
pub fn apply(&self) {
|
||||
let mut guard = self.inner.write();
|
||||
if let Some(d) = guard.1.take() {
|
||||
guard.0 = d;
|
||||
}
|
||||
}
|
||||
|
||||
/// 丢弃草稿(如果存在)
|
||||
#[inline]
|
||||
pub fn discard(&self) {
|
||||
let mut guard = self.inner.write();
|
||||
guard.1 = None;
|
||||
}
|
||||
|
||||
/// 异步地以拥有 Box<T> 的方式修改已提交数据:将克隆一次已提交数据到本地,
|
||||
/// 异步闭包返回新的 Box<T>(替换已提交数据)和业务返回值 R。
|
||||
#[inline]
|
||||
pub async fn with_data_modify<F, Fut, R>(&self, f: F) -> Result<R, anyhow::Error>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
F: FnOnce(T) -> Fut + Send,
|
||||
Fut: std::future::Future<Output = Result<(T, R), anyhow::Error>> + Send,
|
||||
{
|
||||
let (local, original_arc) = {
|
||||
let guard = self.inner.read();
|
||||
let arc = Arc::clone(&guard.0);
|
||||
((*arc).clone(), arc)
|
||||
};
|
||||
let (new_local, res) = f(local).await?;
|
||||
let mut guard = self.inner.write();
|
||||
if !Arc::ptr_eq(&guard.0, &original_arc) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Optimistic lock failed: Committed data has changed during async operation"
|
||||
));
|
||||
}
|
||||
guard.0 = Arc::from(new_local);
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for Draft<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: Arc::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +1,7 @@
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type SharedBox<T> = Arc<Box<T>>;
|
||||
type DraftInner<T> = (SharedBox<T>, Option<SharedBox<T>>);
|
||||
|
||||
/// Draft 管理:committed 与 optional draft 都以 Arc<Box<T>> 存储,
|
||||
// (committed_snapshot, optional_draft_snapshot)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Draft<T: Clone> {
|
||||
inner: Arc<RwLock<DraftInner<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Clone> Draft<T> {
|
||||
pub fn new(data: T) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new((Arc::new(Box::new(data)), None))),
|
||||
}
|
||||
}
|
||||
/// 以 Arc<Box<T>> 的形式获取当前“已提交(正式)”数据的快照(零拷贝,仅 clone Arc)
|
||||
pub fn data_arc(&self) -> SharedBox<T> {
|
||||
let guard = self.inner.read();
|
||||
Arc::clone(&guard.0)
|
||||
}
|
||||
|
||||
/// 获取当前(草稿若存在则返回草稿,否则返回已提交)的快照
|
||||
/// 这也是零拷贝:只 clone Arc,不 clone T
|
||||
pub fn latest_arc(&self) -> SharedBox<T> {
|
||||
let guard = self.inner.read();
|
||||
guard
|
||||
.1
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Arc::clone(&guard.0))
|
||||
}
|
||||
|
||||
/// 通过闭包以可变方式编辑草稿(在闭包中我们给出 &mut T)
|
||||
/// - 延迟拷贝:如果只有这一个 Arc 引用,则直接修改,不会克隆 T;
|
||||
/// - 若草稿被其他读者共享,Arc::make_mut 会做一次 T.clone(最小必要拷贝)。
|
||||
pub fn edit_draft<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut T) -> R,
|
||||
{
|
||||
// 先获得写锁以创建或取出草稿 Arc 的可变引用位置
|
||||
let mut guard = self.inner.write();
|
||||
let mut draft_arc = if guard.1.is_none() {
|
||||
Arc::clone(&guard.0)
|
||||
} else {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
guard.1.take().unwrap()
|
||||
};
|
||||
drop(guard);
|
||||
// Arc::make_mut: 如果只有一个引用则返回可变引用;否则会克隆底层 Box<T>(要求 T: Clone)
|
||||
let boxed = Arc::make_mut(&mut draft_arc); // &mut Box<T>
|
||||
// 对 Box<T> 解引用得到 &mut T
|
||||
let result = f(&mut **boxed);
|
||||
// 恢复修改后的草稿 Arc
|
||||
self.inner.write().1 = Some(draft_arc);
|
||||
result
|
||||
}
|
||||
|
||||
/// 将草稿提交到已提交位置(替换),并清除草稿
|
||||
pub fn apply(&self) {
|
||||
let mut guard = self.inner.write();
|
||||
if let Some(d) = guard.1.take() {
|
||||
guard.0 = d;
|
||||
}
|
||||
}
|
||||
|
||||
/// 丢弃草稿(如果存在)
|
||||
pub fn discard(&self) {
|
||||
let mut guard = self.inner.write();
|
||||
guard.1 = None;
|
||||
}
|
||||
|
||||
/// 异步地以拥有 Box<T> 的方式修改已提交数据:将克隆一次已提交数据到本地,
|
||||
/// 异步闭包返回新的 Box<T>(替换已提交数据)和业务返回值 R。
|
||||
pub async fn with_data_modify<F, Fut, R>(&self, f: F) -> Result<R, anyhow::Error>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
F: FnOnce(Box<T>) -> Fut + Send,
|
||||
Fut: std::future::Future<Output = Result<(Box<T>, R), anyhow::Error>> + Send,
|
||||
{
|
||||
// 读取已提交快照(cheap Arc clone, 然后得到 Box<T> 所有权 via clone)
|
||||
// 注意:为了让闭包接收 Box<T> 所有权,我们需要 clone 底层 T(不可避免)
|
||||
let local: Box<T> = {
|
||||
let guard = self.inner.read();
|
||||
// 将 Arc<Box<T>> 的 Box<T> clone 出来(会调用 T: Clone)
|
||||
(*guard.0).clone()
|
||||
};
|
||||
|
||||
let (new_local, res) = f(local).await?;
|
||||
|
||||
// 将新的 Box<T> 放到已提交位置(包进 Arc)
|
||||
self.inner.write().0 = Arc::new(new_local);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::anyhow;
|
||||
use clash_verge_draft::Draft;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
@@ -233,10 +133,7 @@ mod tests {
|
||||
let prev_draft_ptr = std::sync::Arc::as_ptr(&draft_after_first_edit);
|
||||
draft.apply();
|
||||
let committed_after_apply = draft.data_arc();
|
||||
assert_eq!(
|
||||
std::sync::Arc::as_ptr(&committed_after_apply),
|
||||
prev_draft_ptr
|
||||
);
|
||||
assert_eq!(std::sync::Arc::as_ptr(&committed_after_apply), prev_draft_ptr);
|
||||
|
||||
// 第二次编辑:此时草稿唯一持有(无其它引用),不应再克隆
|
||||
// 获取草稿 Arc 的指针并立即丢弃本地引用,避免增加 strong_count
|
||||
@@ -298,7 +195,7 @@ mod tests {
|
||||
// 使用 with_data_modify 异步(立即就绪)地更新 committed
|
||||
let res = block_on_ready(draft.with_data_modify(|mut v| async move {
|
||||
v.enable_auto_launch = Some(true);
|
||||
Ok((Box::new(*v), "done")) // Dereference v to get Box<T>
|
||||
Ok((v, "done"))
|
||||
}));
|
||||
assert_eq!(
|
||||
{
|
||||
@@ -318,11 +215,8 @@ mod tests {
|
||||
let draft = Draft::new(IVerge::default());
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let err = block_on_ready(draft.with_data_modify(|v| async move {
|
||||
drop(v);
|
||||
Err::<(Box<IVerge>, ()), _>(anyhow!("boom"))
|
||||
}))
|
||||
.unwrap_err();
|
||||
let err = block_on_ready(draft.with_data_modify(|_v| async move { Err::<(IVerge, ()), _>(anyhow!("boom")) }))
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(format!("{err}"), "boom");
|
||||
}
|
||||
@@ -346,7 +240,7 @@ mod tests {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
block_on_ready(draft.with_data_modify(|mut v| async move {
|
||||
v.enable_auto_launch = Some(false); // 与草稿不同
|
||||
Ok((Box::new(*v), ())) // Dereference v to get Box<T>
|
||||
Ok((v, ()))
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "clash-verge-i18n"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rust-i18n = "3.1.5"
|
||||
sys-locale = "0.3.2"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: لوحة التحكم
|
||||
body: تم تحديث حالة عرض لوحة التحكم.
|
||||
clashModeChanged:
|
||||
title: تبديل الوضع
|
||||
body: تم التبديل إلى {mode}.
|
||||
systemProxyToggled:
|
||||
title: وكيل النظام
|
||||
body: تم تحديث حالة وكيل النظام.
|
||||
tunModeToggled:
|
||||
title: وضع TUN
|
||||
body: تم تحديث حالة وضع TUN.
|
||||
lightweightModeEntered:
|
||||
title: الوضع الخفيف
|
||||
body: تم الدخول إلى الوضع الخفيف.
|
||||
profilesReactivated:
|
||||
title: الملفات التعريفية
|
||||
body: تمت إعادة تفعيل الملف التعريفي.
|
||||
appQuit:
|
||||
title: على وشك الخروج
|
||||
body: Clash Verge على وشك الخروج.
|
||||
appHidden:
|
||||
title: تم إخفاء التطبيق
|
||||
body: Clash Verge يعمل في الخلفية.
|
||||
service:
|
||||
adminInstallPrompt: يتطلب تثبيت خدمة Clash Verge صلاحيات المسؤول.
|
||||
adminUninstallPrompt: يتطلب إلغاء تثبيت خدمة Clash Verge صلاحيات المسؤول.
|
||||
tray:
|
||||
dashboard: لوحة التحكم
|
||||
ruleMode: وضع القواعد
|
||||
globalMode: الوضع العام
|
||||
directMode: الوضع المباشر
|
||||
outboundModes: أوضاع الخروج
|
||||
rule: قاعدة
|
||||
direct: مباشر
|
||||
global: عام
|
||||
profiles: الملفات التعريفية
|
||||
proxies: وكلاء
|
||||
systemProxy: وكيل النظام
|
||||
tunMode: وضع TUN
|
||||
closeAllConnections: إغلاق كل الاتصالات
|
||||
lightweightMode: الوضع الخفيف
|
||||
copyEnv: نسخ متغيرات البيئة
|
||||
confDir: دليل الإعدادات
|
||||
coreDir: دليل النواة
|
||||
logsDir: دليل السجلات
|
||||
openDir: فتح الدليل
|
||||
appLog: سجل التطبيق
|
||||
coreLog: سجل النواة
|
||||
restartClash: إعادة تشغيل نواة Clash
|
||||
restartApp: إعادة تشغيل التطبيق
|
||||
vergeVersion: إصدار Verge
|
||||
more: المزيد
|
||||
exit: خروج
|
||||
tooltip:
|
||||
systemProxy: وكيل النظام
|
||||
tun: TUN
|
||||
profile: ملف تعريفي
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: Übersicht
|
||||
body: Die Sichtbarkeit der Übersicht wurde aktualisiert.
|
||||
clashModeChanged:
|
||||
title: Moduswechsel
|
||||
body: Auf {mode} umgeschaltet.
|
||||
systemProxyToggled:
|
||||
title: Systemproxy
|
||||
body: Der Status des Systemproxys wurde aktualisiert.
|
||||
tunModeToggled:
|
||||
title: TUN-Modus
|
||||
body: Der Status des TUN-Modus wurde aktualisiert.
|
||||
lightweightModeEntered:
|
||||
title: Leichtmodus
|
||||
body: Leichtmodus aktiviert.
|
||||
profilesReactivated:
|
||||
title: Profile
|
||||
body: Profil reaktiviert.
|
||||
appQuit:
|
||||
title: Beenden steht bevor
|
||||
body: Clash Verge wird gleich beendet.
|
||||
appHidden:
|
||||
title: Anwendung ausgeblendet
|
||||
body: Clash Verge läuft im Hintergrund.
|
||||
service:
|
||||
adminInstallPrompt: Für die Installation des Clash-Verge-Dienstes sind Administratorrechte erforderlich.
|
||||
adminUninstallPrompt: Für die Deinstallation des Clash-Verge-Dienstes sind Administratorrechte erforderlich.
|
||||
tray:
|
||||
dashboard: Übersicht
|
||||
ruleMode: Regelmodus
|
||||
globalMode: Globaler Modus
|
||||
directMode: Direktmodus
|
||||
outboundModes: Ausgangsmodi
|
||||
rule: Regel
|
||||
direct: Direkt
|
||||
global: Global
|
||||
profiles: Profile
|
||||
proxies: Proxy
|
||||
systemProxy: Systemproxy
|
||||
tunMode: TUN-Modus
|
||||
closeAllConnections: Alle Verbindungen schließen
|
||||
lightweightMode: Leichtmodus
|
||||
copyEnv: Umgebungsvariablen kopieren
|
||||
confDir: Konfigurationsverzeichnis
|
||||
coreDir: Core-Verzeichnis
|
||||
logsDir: Log-Verzeichnis
|
||||
openDir: Verzeichnis öffnen
|
||||
appLog: Anwendungslog
|
||||
coreLog: Core-Log
|
||||
restartClash: Clash-Core neu starten
|
||||
restartApp: Anwendung neu starten
|
||||
vergeVersion: Verge-Version
|
||||
more: Mehr
|
||||
exit: Beenden
|
||||
tooltip:
|
||||
systemProxy: Systemproxy
|
||||
tun: TUN
|
||||
profile: Profil
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: Dashboard
|
||||
body: Dashboard visibility has been updated.
|
||||
clashModeChanged:
|
||||
title: Mode Switch
|
||||
body: Switched to {mode}.
|
||||
systemProxyToggled:
|
||||
title: System Proxy
|
||||
body: System proxy status has been updated.
|
||||
tunModeToggled:
|
||||
title: TUN Mode
|
||||
body: TUN mode status has been updated.
|
||||
lightweightModeEntered:
|
||||
title: Lightweight Mode
|
||||
body: Entered lightweight mode.
|
||||
profilesReactivated:
|
||||
title: Profiles
|
||||
body: Profile Reactivated.
|
||||
appQuit:
|
||||
title: About to Exit
|
||||
body: Clash Verge is about to exit.
|
||||
appHidden:
|
||||
title: Application Hidden
|
||||
body: Clash Verge is running in the background.
|
||||
service:
|
||||
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
|
||||
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
|
||||
tray:
|
||||
dashboard: Dashboard
|
||||
ruleMode: Rule Mode
|
||||
globalMode: Global Mode
|
||||
directMode: Direct Mode
|
||||
outboundModes: Outbound Modes
|
||||
rule: Rule
|
||||
direct: Direct
|
||||
global: Global
|
||||
profiles: Profiles
|
||||
proxies: Proxies
|
||||
systemProxy: System Proxy
|
||||
tunMode: TUN Mode
|
||||
closeAllConnections: Close All Connections
|
||||
lightweightMode: Lightweight Mode
|
||||
copyEnv: Copy Environment Variables
|
||||
confDir: Configuration Directory
|
||||
coreDir: Core Directory
|
||||
logsDir: Log Directory
|
||||
openDir: Open Directory
|
||||
appLog: Application Log
|
||||
coreLog: Core Log
|
||||
restartClash: Restart Clash Core
|
||||
restartApp: Restart Application
|
||||
vergeVersion: Verge Version
|
||||
more: More
|
||||
exit: Exit
|
||||
tooltip:
|
||||
systemProxy: System Proxy
|
||||
tun: TUN
|
||||
profile: Profile
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: Panel
|
||||
body: La visibilidad del panel se ha actualizado.
|
||||
clashModeChanged:
|
||||
title: Cambio de modo
|
||||
body: Cambiado a {mode}.
|
||||
systemProxyToggled:
|
||||
title: Proxy del sistema
|
||||
body: El estado del proxy del sistema se ha actualizado.
|
||||
tunModeToggled:
|
||||
title: Modo TUN
|
||||
body: El estado del modo TUN se ha actualizado.
|
||||
lightweightModeEntered:
|
||||
title: Modo ligero
|
||||
body: Se ha entrado en el modo ligero.
|
||||
profilesReactivated:
|
||||
title: Perfiles
|
||||
body: Perfil reactivado.
|
||||
appQuit:
|
||||
title: A punto de salir
|
||||
body: Clash Verge está a punto de salir.
|
||||
appHidden:
|
||||
title: Aplicación oculta
|
||||
body: Clash Verge se está ejecutando en segundo plano.
|
||||
service:
|
||||
adminInstallPrompt: Instalar el servicio de Clash Verge requiere privilegios de administrador.
|
||||
adminUninstallPrompt: Desinstalar el servicio de Clash Verge requiere privilegios de administrador.
|
||||
tray:
|
||||
dashboard: Panel
|
||||
ruleMode: Modo de reglas
|
||||
globalMode: Modo global
|
||||
directMode: Modo directo
|
||||
outboundModes: Modos de salida
|
||||
rule: Regla
|
||||
direct: Directo
|
||||
global: Global
|
||||
profiles: Perfiles
|
||||
proxies: Proxies
|
||||
systemProxy: Proxy del sistema
|
||||
tunMode: Modo TUN
|
||||
closeAllConnections: Cerrar todas las conexiones
|
||||
lightweightMode: Modo ligero
|
||||
copyEnv: Copiar variables de entorno
|
||||
confDir: Directorio de configuración
|
||||
coreDir: Directorio del núcleo
|
||||
logsDir: Directorio de registros
|
||||
openDir: Abrir directorio
|
||||
appLog: Registro de la aplicación
|
||||
coreLog: Registro del núcleo
|
||||
restartClash: Reiniciar el núcleo de Clash
|
||||
restartApp: Reiniciar aplicación
|
||||
vergeVersion: Versión de Verge
|
||||
more: Más
|
||||
exit: Salir
|
||||
tooltip:
|
||||
systemProxy: Proxy del sistema
|
||||
tun: TUN
|
||||
profile: Perfil
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: داشبورد
|
||||
body: وضعیت نمایش داشبورد بهروزرسانی شد.
|
||||
clashModeChanged:
|
||||
title: تغییر حالت
|
||||
body: به {mode} تغییر کرد.
|
||||
systemProxyToggled:
|
||||
title: پروکسی سیستم
|
||||
body: وضعیت پروکسی سیستم بهروزرسانی شد.
|
||||
tunModeToggled:
|
||||
title: حالت TUN
|
||||
body: وضعیت حالت TUN بهروزرسانی شد.
|
||||
lightweightModeEntered:
|
||||
title: حالت سبک
|
||||
body: به حالت سبک وارد شد.
|
||||
profilesReactivated:
|
||||
title: پروفایلها
|
||||
body: پروفایل دوباره فعال شد.
|
||||
appQuit:
|
||||
title: در آستانه خروج
|
||||
body: Clash Verge در آستانه خروج است.
|
||||
appHidden:
|
||||
title: برنامه پنهان شد
|
||||
body: Clash Verge در پسزمینه در حال اجراست.
|
||||
service:
|
||||
adminInstallPrompt: نصب سرویس Clash Verge به دسترسی مدیر نیاز دارد.
|
||||
adminUninstallPrompt: حذف سرویس Clash Verge به دسترسی مدیر نیاز دارد.
|
||||
tray:
|
||||
dashboard: داشبورد
|
||||
ruleMode: حالت قوانین
|
||||
globalMode: حالت سراسری
|
||||
directMode: حالت مستقیم
|
||||
outboundModes: حالتهای خروجی
|
||||
rule: قانون
|
||||
direct: مستقیم
|
||||
global: سراسری
|
||||
profiles: پروفایلها
|
||||
proxies: پروکسیها
|
||||
systemProxy: پروکسی سیستم
|
||||
tunMode: حالت TUN
|
||||
closeAllConnections: بستن همه اتصالها
|
||||
lightweightMode: حالت سبک
|
||||
copyEnv: کپی متغیرهای محیطی
|
||||
confDir: پوشه پیکربندی
|
||||
coreDir: پوشه هسته
|
||||
logsDir: پوشه گزارشها
|
||||
openDir: باز کردن پوشه
|
||||
appLog: گزارش برنامه
|
||||
coreLog: گزارش هسته
|
||||
restartClash: راهاندازی مجدد هسته Clash
|
||||
restartApp: راهاندازی مجدد برنامه
|
||||
vergeVersion: نسخه Verge
|
||||
more: بیشتر
|
||||
exit: خروج
|
||||
tooltip:
|
||||
systemProxy: پروکسی سیستم
|
||||
tun: TUN
|
||||
profile: پروفایل
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: Dasbor
|
||||
body: Visibilitas dasbor telah diperbarui.
|
||||
clashModeChanged:
|
||||
title: Peralihan Mode
|
||||
body: Beralih ke {mode}.
|
||||
systemProxyToggled:
|
||||
title: Proksi Sistem
|
||||
body: Status proksi sistem telah diperbarui.
|
||||
tunModeToggled:
|
||||
title: Mode TUN
|
||||
body: Status mode TUN telah diperbarui.
|
||||
lightweightModeEntered:
|
||||
title: Mode Ringan
|
||||
body: Masuk ke mode ringan.
|
||||
profilesReactivated:
|
||||
title: Profil
|
||||
body: Profil diaktifkan kembali.
|
||||
appQuit:
|
||||
title: Akan Keluar
|
||||
body: Clash Verge akan keluar.
|
||||
appHidden:
|
||||
title: Aplikasi Disembunyikan
|
||||
body: Clash Verge berjalan di latar belakang.
|
||||
service:
|
||||
adminInstallPrompt: Menginstal layanan Clash Verge memerlukan hak administrator.
|
||||
adminUninstallPrompt: Menghapus instalasi layanan Clash Verge memerlukan hak administrator.
|
||||
tray:
|
||||
dashboard: Dasbor
|
||||
ruleMode: Mode Aturan
|
||||
globalMode: Mode Global
|
||||
directMode: Mode Langsung
|
||||
outboundModes: Mode Keluar
|
||||
rule: Aturan
|
||||
direct: Langsung
|
||||
global: Global
|
||||
profiles: Profil
|
||||
proxies: Proksi
|
||||
systemProxy: Proksi Sistem
|
||||
tunMode: Mode TUN
|
||||
closeAllConnections: Tutup Semua Koneksi
|
||||
lightweightMode: Mode Ringan
|
||||
copyEnv: Salin Variabel Lingkungan
|
||||
confDir: Direktori Konfigurasi
|
||||
coreDir: Direktori Core
|
||||
logsDir: Direktori Log
|
||||
openDir: Buka Direktori
|
||||
appLog: Log Aplikasi
|
||||
coreLog: Log Core
|
||||
restartClash: Mulai Ulang Core Clash
|
||||
restartApp: Mulai Ulang Aplikasi
|
||||
vergeVersion: Versi Verge
|
||||
more: Lainnya
|
||||
exit: Keluar
|
||||
tooltip:
|
||||
systemProxy: Proksi Sistem
|
||||
tun: TUN
|
||||
profile: Profil
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: ダッシュボード
|
||||
body: ダッシュボードの表示状態が更新されました。
|
||||
clashModeChanged:
|
||||
title: モード切り替え
|
||||
body: "{mode} に切り替えました。"
|
||||
systemProxyToggled:
|
||||
title: システムプロキシ
|
||||
body: システムプロキシの状態が更新されました。
|
||||
tunModeToggled:
|
||||
title: TUN モード
|
||||
body: TUN モードの状態が更新されました。
|
||||
lightweightModeEntered:
|
||||
title: 軽量モード
|
||||
body: 軽量モードに入りました。
|
||||
profilesReactivated:
|
||||
title: プロファイル
|
||||
body: プロファイルが再有効化されました。
|
||||
appQuit:
|
||||
title: 終了間近
|
||||
body: Clash Verge はまもなく終了します。
|
||||
appHidden:
|
||||
title: アプリが非表示
|
||||
body: Clash Verge はバックグラウンドで実行中です。
|
||||
service:
|
||||
adminInstallPrompt: Clash Verge サービスのインストールには管理者権限が必要です。
|
||||
adminUninstallPrompt: Clash Verge サービスのアンインストールには管理者権限が必要です。
|
||||
tray:
|
||||
dashboard: ダッシュボード
|
||||
ruleMode: ルールモード
|
||||
globalMode: グローバルモード
|
||||
directMode: ダイレクトモード
|
||||
outboundModes: アウトバウンドモード
|
||||
rule: ルール
|
||||
direct: ダイレクト
|
||||
global: グローバル
|
||||
profiles: プロファイル
|
||||
proxies: プロキシ
|
||||
systemProxy: システムプロキシ
|
||||
tunMode: TUN モード
|
||||
closeAllConnections: すべての接続を閉じる
|
||||
lightweightMode: 軽量モード
|
||||
copyEnv: 環境変数をコピー
|
||||
confDir: 設定ディレクトリ
|
||||
coreDir: コアディレクトリ
|
||||
logsDir: ログディレクトリ
|
||||
openDir: ディレクトリを開く
|
||||
appLog: アプリケーションログ
|
||||
coreLog: コアログ
|
||||
restartClash: Clash コアを再起動
|
||||
restartApp: アプリケーションを再起動
|
||||
vergeVersion: Verge バージョン
|
||||
more: その他
|
||||
exit: 終了
|
||||
tooltip:
|
||||
systemProxy: システムプロキシ
|
||||
tun: TUN
|
||||
profile: プロファイル
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: 대시보드
|
||||
body: 대시보드 표시 상태가 업데이트되었습니다.
|
||||
clashModeChanged:
|
||||
title: 모드 전환
|
||||
body: "{mode}(으)로 전환되었습니다."
|
||||
systemProxyToggled:
|
||||
title: 시스템 프록시
|
||||
body: 시스템 프록시 상태가 업데이트되었습니다.
|
||||
tunModeToggled:
|
||||
title: TUN 모드
|
||||
body: TUN 모드 상태가 업데이트되었습니다.
|
||||
lightweightModeEntered:
|
||||
title: 경량 모드
|
||||
body: 경량 모드에 진입했습니다.
|
||||
profilesReactivated:
|
||||
title: 프로필
|
||||
body: 프로필이 다시 활성화되었습니다.
|
||||
appQuit:
|
||||
title: 곧 종료
|
||||
body: Clash Verge가 곧 종료됩니다.
|
||||
appHidden:
|
||||
title: 앱이 숨겨짐
|
||||
body: Clash Verge가 백그라운드에서 실행 중입니다.
|
||||
service:
|
||||
adminInstallPrompt: Clash Verge 서비스 설치에는 관리자 권한이 필요합니다.
|
||||
adminUninstallPrompt: Clash Verge 서비스 제거에는 관리자 권한이 필요합니다.
|
||||
tray:
|
||||
dashboard: 대시보드
|
||||
ruleMode: 규칙 모드
|
||||
globalMode: 전역 모드
|
||||
directMode: 직접 모드
|
||||
outboundModes: 아웃바운드 모드
|
||||
rule: 규칙
|
||||
direct: 직접
|
||||
global: 글로벌
|
||||
profiles: 프로필
|
||||
proxies: 프록시
|
||||
systemProxy: 시스템 프록시
|
||||
tunMode: TUN 모드
|
||||
closeAllConnections: 모든 연결 닫기
|
||||
lightweightMode: 경량 모드
|
||||
copyEnv: 환경 변수 복사
|
||||
confDir: 구성 디렉터리
|
||||
coreDir: 코어 디렉터리
|
||||
logsDir: 로그 디렉터리
|
||||
openDir: 디렉터리 열기
|
||||
appLog: 애플리케이션 로그
|
||||
coreLog: 코어 로그
|
||||
restartClash: Clash 코어 재시작
|
||||
restartApp: 애플리케이션 재시작
|
||||
vergeVersion: Verge 버전
|
||||
more: 더 보기
|
||||
exit: 종료
|
||||
tooltip:
|
||||
systemProxy: 시스템 프록시
|
||||
tun: TUN
|
||||
profile: 프로필
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: Панель
|
||||
body: Видимость панели обновлена.
|
||||
clashModeChanged:
|
||||
title: Смена режима
|
||||
body: Переключено на {mode}.
|
||||
systemProxyToggled:
|
||||
title: Системный прокси
|
||||
body: Статус системного прокси обновлен.
|
||||
tunModeToggled:
|
||||
title: Режим TUN
|
||||
body: Статус режима TUN обновлен.
|
||||
lightweightModeEntered:
|
||||
title: Легкий режим
|
||||
body: Включен легкий режим.
|
||||
profilesReactivated:
|
||||
title: Профили
|
||||
body: Профиль повторно активирован.
|
||||
appQuit:
|
||||
title: Скорый выход
|
||||
body: Clash Verge скоро завершит работу.
|
||||
appHidden:
|
||||
title: Приложение скрыто
|
||||
body: Clash Verge работает в фоновом режиме.
|
||||
service:
|
||||
adminInstallPrompt: Для установки службы Clash Verge требуются права администратора.
|
||||
adminUninstallPrompt: Для удаления службы Clash Verge требуются права администратора.
|
||||
tray:
|
||||
dashboard: Панель
|
||||
ruleMode: Режим правил
|
||||
globalMode: Глобальный режим
|
||||
directMode: Прямой режим
|
||||
outboundModes: Исходящие режимы
|
||||
rule: Правило
|
||||
direct: Прямой
|
||||
global: Глобальный
|
||||
profiles: Профили
|
||||
proxies: Прокси
|
||||
systemProxy: Системный прокси
|
||||
tunMode: Режим TUN
|
||||
closeAllConnections: Закрыть все соединения
|
||||
lightweightMode: Легкий режим
|
||||
copyEnv: Копировать переменные среды
|
||||
confDir: Каталог конфигурации
|
||||
coreDir: Каталог ядра
|
||||
logsDir: Каталог журналов
|
||||
openDir: Открыть каталог
|
||||
appLog: Журнал приложения
|
||||
coreLog: Журнал ядра
|
||||
restartClash: Перезапустить ядро Clash
|
||||
restartApp: Перезапустить приложение
|
||||
vergeVersion: Версия Verge
|
||||
more: Еще
|
||||
exit: Выход
|
||||
tooltip:
|
||||
systemProxy: Системный прокси
|
||||
tun: TUN
|
||||
profile: Профиль
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: Gösterge Paneli
|
||||
body: Gösterge panelinin görünürlüğü güncellendi.
|
||||
clashModeChanged:
|
||||
title: Mod Değişimi
|
||||
body: "{mode} moduna geçildi."
|
||||
systemProxyToggled:
|
||||
title: Sistem Vekil'i
|
||||
body: Sistem vekil'i durumu güncellendi.
|
||||
tunModeToggled:
|
||||
title: TUN Modu
|
||||
body: TUN modu durumu güncellendi.
|
||||
lightweightModeEntered:
|
||||
title: Hafif Mod
|
||||
body: Hafif moda geçildi.
|
||||
profilesReactivated:
|
||||
title: Profiller
|
||||
body: Profil yeniden etkinleştirildi.
|
||||
appQuit:
|
||||
title: Çıkış Yapılmak Üzere
|
||||
body: Clash Verge kapanmak üzere.
|
||||
appHidden:
|
||||
title: Uygulama Gizlendi
|
||||
body: Clash Verge arka planda çalışıyor.
|
||||
service:
|
||||
adminInstallPrompt: Clash Verge hizmetini kurmak için yönetici ayrıcalıkları gerekir.
|
||||
adminUninstallPrompt: Clash Verge hizmetini kaldırmak için yönetici ayrıcalıkları gerekir.
|
||||
tray:
|
||||
dashboard: Gösterge Paneli
|
||||
ruleMode: Kural Modu
|
||||
globalMode: Küresel Mod
|
||||
directMode: Doğrudan Mod
|
||||
outboundModes: Giden Modlar
|
||||
rule: Kural
|
||||
direct: Doğrudan
|
||||
global: Küresel
|
||||
profiles: Profiller
|
||||
proxies: Vekil'ler
|
||||
systemProxy: Sistem Vekil'i
|
||||
tunMode: TUN Modu
|
||||
closeAllConnections: Tüm Bağlantıları Kapat
|
||||
lightweightMode: Hafif Mod
|
||||
copyEnv: Ortam Değişkenlerini Kopyala
|
||||
confDir: Yapılandırma Dizini
|
||||
coreDir: Çekirdek Dizini
|
||||
logsDir: Günlük Dizini
|
||||
openDir: Dizini Aç
|
||||
appLog: Uygulama Günlüğü
|
||||
coreLog: Çekirdek Günlüğü
|
||||
restartClash: Clash Çekirdeğini Yeniden Başlat
|
||||
restartApp: Uygulamayı Yeniden Başlat
|
||||
vergeVersion: Verge Sürümü
|
||||
more: Daha Fazla
|
||||
exit: Çıkış
|
||||
tooltip:
|
||||
systemProxy: Sistem Vekil'i
|
||||
tun: TUN
|
||||
profile: Profil
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: Идарә панеле
|
||||
body: Идарә панеленең күренеше яңартылды.
|
||||
clashModeChanged:
|
||||
title: Режим алыштыру
|
||||
body: "{mode} режимына күчтел."
|
||||
systemProxyToggled:
|
||||
title: Системалы прокси
|
||||
body: Системалы прокси хәле яңартылды.
|
||||
tunModeToggled:
|
||||
title: TUN режимы
|
||||
body: TUN режимы хәле яңартылды.
|
||||
lightweightModeEntered:
|
||||
title: Җиңел режим
|
||||
body: Җиңел режимга күчелде.
|
||||
profilesReactivated:
|
||||
title: Профильләр
|
||||
body: Профиль яңадан активлаштырылды.
|
||||
appQuit:
|
||||
title: Чыгар алдыннан
|
||||
body: Clash Verge чыгарга җыена.
|
||||
appHidden:
|
||||
title: Кушымта яшерелде
|
||||
body: Clash Verge фон режимында эшли.
|
||||
service:
|
||||
adminInstallPrompt: Clash Verge хезмәтен урнаштыру өчен администратор хокуклары кирәк.
|
||||
adminUninstallPrompt: Clash Verge хезмәтен бетерү өчен администратор хокуклары кирәк.
|
||||
tray:
|
||||
dashboard: Идарә панеле
|
||||
ruleMode: Кагыйдә режимы
|
||||
globalMode: Глобаль режим
|
||||
directMode: Турыдан-туры режим
|
||||
outboundModes: Чыгыш режимнары
|
||||
rule: Кагыйдә
|
||||
direct: Турыдан-туры
|
||||
global: Глобаль
|
||||
profiles: Профильләр
|
||||
proxies: Проксилар
|
||||
systemProxy: Системалы прокси
|
||||
tunMode: TUN режимы
|
||||
closeAllConnections: Барлык тоташуларны ябу
|
||||
lightweightMode: Җиңел режим
|
||||
copyEnv: Мохит үзгәрүчәннәрен күчерү
|
||||
confDir: Конфигурация каталогы
|
||||
coreDir: Ядро каталогы
|
||||
logsDir: Журнал каталогы
|
||||
openDir: Каталогны ачу
|
||||
appLog: Кушымта журналы
|
||||
coreLog: Ядро журналы
|
||||
restartClash: Clash ядрәсен кабат җибәрү
|
||||
restartApp: Кушымтаны кабат җибәрү
|
||||
vergeVersion: Verge версиясе
|
||||
more: Күбрәк
|
||||
exit: Чыгу
|
||||
tooltip:
|
||||
systemProxy: Системалы прокси
|
||||
tun: TUN
|
||||
profile: Профиль
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: 仪表板
|
||||
body: 仪表板显示状态已更新。
|
||||
clashModeChanged:
|
||||
title: 模式切换
|
||||
body: 已切换至 {mode}。
|
||||
systemProxyToggled:
|
||||
title: 系统代理
|
||||
body: 系统代理状态已更新。
|
||||
tunModeToggled:
|
||||
title: TUN 模式
|
||||
body: TUN 模式状态已更新。
|
||||
lightweightModeEntered:
|
||||
title: 轻量模式
|
||||
body: 已进入轻量模式。
|
||||
profilesReactivated:
|
||||
title: 订阅
|
||||
body: 订阅已激活。
|
||||
appQuit:
|
||||
title: 即将退出
|
||||
body: Clash Verge 即将退出。
|
||||
appHidden:
|
||||
title: 应用已隐藏
|
||||
body: Clash Verge 正在后台运行。
|
||||
service:
|
||||
adminInstallPrompt: 安装 Clash Verge 服务需要管理员权限
|
||||
adminUninstallPrompt: 卸载 Clash Verge 服务需要管理员权限
|
||||
tray:
|
||||
dashboard: 仪表板
|
||||
ruleMode: 规则模式
|
||||
globalMode: 全局模式
|
||||
directMode: 直连模式
|
||||
outboundModes: 出站模式
|
||||
rule: 规则
|
||||
direct: 直连
|
||||
global: 全局
|
||||
profiles: 订阅
|
||||
proxies: 代理
|
||||
systemProxy: 系统代理
|
||||
tunMode: TUN 模式
|
||||
closeAllConnections: 关闭所有连接
|
||||
lightweightMode: 轻量模式
|
||||
copyEnv: 复制环境变量
|
||||
confDir: 配置目录
|
||||
coreDir: 内核目录
|
||||
logsDir: 日志目录
|
||||
openDir: 打开目录
|
||||
appLog: 应用日志
|
||||
coreLog: 内核日志
|
||||
restartClash: 重启 Clash 内核
|
||||
restartApp: 重启应用
|
||||
vergeVersion: Verge 版本
|
||||
more: 更多
|
||||
exit: 退出
|
||||
tooltip:
|
||||
systemProxy: 系统代理
|
||||
tun: TUN
|
||||
profile: 订阅
|
||||
@@ -0,0 +1,60 @@
|
||||
_version: 1
|
||||
notifications:
|
||||
dashboardToggled:
|
||||
title: 儀表板
|
||||
body: 儀表板顯示狀態已更新。
|
||||
clashModeChanged:
|
||||
title: 模式切換
|
||||
body: 已切換至 {mode}。
|
||||
systemProxyToggled:
|
||||
title: 系統代理
|
||||
body: 系統代理狀態已更新。
|
||||
tunModeToggled:
|
||||
title: 虛擬網路介面卡模式
|
||||
body: 已更新虛擬網路介面卡模式狀態。
|
||||
lightweightModeEntered:
|
||||
title: 輕量模式
|
||||
body: 已進入輕量模式。
|
||||
profilesReactivated:
|
||||
title: 訂閱
|
||||
body: 訂閱已啟用。
|
||||
appQuit:
|
||||
title: 即將退出
|
||||
body: Clash Verge 即將退出。
|
||||
appHidden:
|
||||
title: 應用已隱藏
|
||||
body: Clash Verge 正在背景執行。
|
||||
service:
|
||||
adminInstallPrompt: 安裝 Clash Verge 服務需要管理員權限
|
||||
adminUninstallPrompt: 卸载 Clash Verge 服務需要管理員權限
|
||||
tray:
|
||||
dashboard: 儀表板
|
||||
ruleMode: 規則模式
|
||||
globalMode: 全域模式
|
||||
directMode: 直連模式
|
||||
outboundModes: 出站模式
|
||||
rule: 規則
|
||||
direct: 直連
|
||||
global: 全域
|
||||
profiles: 訂閱
|
||||
proxies: 代理
|
||||
systemProxy: 系統代理
|
||||
tunMode: 虛擬網路介面卡模式
|
||||
closeAllConnections: 關閉所有連線
|
||||
lightweightMode: 輕量模式
|
||||
copyEnv: 複製環境變數
|
||||
confDir: 設定目錄
|
||||
coreDir: 核心目錄
|
||||
logsDir: 日誌目錄
|
||||
openDir: 開啟目錄
|
||||
appLog: 應用程式日誌
|
||||
coreLog: 核心日誌
|
||||
restartClash: 重新啟動 Clash 核心
|
||||
restartApp: 重新啟動應用程式
|
||||
vergeVersion: Verge 版本
|
||||
more: 更多
|
||||
exit: 離開
|
||||
tooltip:
|
||||
systemProxy: 系統代理
|
||||
tun: 虛擬網路介面卡
|
||||
profile: 訂閱
|
||||
@@ -0,0 +1,103 @@
|
||||
use rust_i18n::i18n;
|
||||
|
||||
const DEFAULT_LANGUAGE: &str = "zh";
|
||||
i18n!("locales", fallback = "zh");
|
||||
|
||||
#[inline]
|
||||
fn locale_alias(locale: &str) -> Option<&'static str> {
|
||||
match locale {
|
||||
"ja" | "ja-jp" | "jp" => Some("jp"),
|
||||
"zh" | "zh-cn" | "zh-hans" | "zh-sg" | "zh-my" | "zh-chs" => Some("zh"),
|
||||
"zh-tw" | "zh-hk" | "zh-hant" | "zh-mo" | "zh-cht" => Some("zhtw"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn resolve_supported_language(language: &str) -> Option<&'static str> {
|
||||
if language.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let normalized = language.to_lowercase().replace('_', "-");
|
||||
let segments: Vec<&str> = normalized.split('-').collect();
|
||||
let supported = rust_i18n::available_locales!();
|
||||
for i in (1..=segments.len()).rev() {
|
||||
let prefix = segments[..i].join("-");
|
||||
if let Some(alias) = locale_alias(&prefix)
|
||||
&& let Some(&found) = supported.iter().find(|&&l| l.eq_ignore_ascii_case(alias))
|
||||
{
|
||||
return Some(found);
|
||||
}
|
||||
if let Some(&found) = supported.iter().find(|&&l| l.eq_ignore_ascii_case(&prefix)) {
|
||||
return Some(found);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn current_language(language: Option<&str>) -> &str {
|
||||
language
|
||||
.as_ref()
|
||||
.filter(|lang| !lang.is_empty())
|
||||
.and_then(|lang| resolve_supported_language(lang))
|
||||
.unwrap_or_else(system_language)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn system_language() -> &'static str {
|
||||
sys_locale::get_locale()
|
||||
.as_deref()
|
||||
.and_then(resolve_supported_language)
|
||||
.unwrap_or(DEFAULT_LANGUAGE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sync_locale(language: Option<&str>) {
|
||||
let language = current_language(language);
|
||||
set_locale(language);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_locale(language: &str) {
|
||||
let lang = resolve_supported_language(language).unwrap_or(DEFAULT_LANGUAGE);
|
||||
rust_i18n::set_locale(lang);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn translate(key: &str) -> Cow<'_, str> {
|
||||
rust_i18n::t!(key)
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! t {
|
||||
($key:expr) => {
|
||||
$crate::translate(&$key)
|
||||
};
|
||||
($key:expr, $($arg_name:ident = $arg_value:expr),*) => {
|
||||
{
|
||||
let mut _text = $crate::translate(&$key);
|
||||
$(
|
||||
_text = _text.replace(&format!("{{{}}}", stringify!($arg_name)), &$arg_value);
|
||||
)*
|
||||
_text
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::resolve_supported_language;
|
||||
|
||||
#[test]
|
||||
fn test_resolve_supported_language() {
|
||||
assert_eq!(resolve_supported_language("en"), Some("en"));
|
||||
assert_eq!(resolve_supported_language("en-US"), Some("en"));
|
||||
assert_eq!(resolve_supported_language("zh"), Some("zh"));
|
||||
assert_eq!(resolve_supported_language("zh-CN"), Some("zh"));
|
||||
assert_eq!(resolve_supported_language("zh-Hant"), Some("zhtw"));
|
||||
assert_eq!(resolve_supported_language("jp"), Some("jp"));
|
||||
assert_eq!(resolve_supported_language("ja-JP"), Some("jp"));
|
||||
assert_eq!(resolve_supported_language("fr"), None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "clash-verge-limiter"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,165 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub type SystemLimiter = Limiter<SystemClock>;
|
||||
|
||||
pub trait Clock: Send + Sync {
|
||||
fn now_ms(&self) -> u64;
|
||||
}
|
||||
|
||||
impl<T: Clock + ?Sized> Clock for &T {
|
||||
fn now_ms(&self) -> u64 {
|
||||
(**self).now_ms()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clock + ?Sized> Clock for Arc<T> {
|
||||
fn now_ms(&self) -> u64 {
|
||||
(**self).now_ms()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SystemClock;
|
||||
|
||||
impl Clock for SystemClock {
|
||||
fn now_ms(&self) -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as u64
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Limiter<C: Clock = SystemClock> {
|
||||
last_run_ms: AtomicU64,
|
||||
period_ms: u64,
|
||||
clock: C,
|
||||
}
|
||||
|
||||
impl<C: Clock> Limiter<C> {
|
||||
pub const fn new(period: Duration, clock: C) -> Self {
|
||||
Self {
|
||||
last_run_ms: AtomicU64::new(0),
|
||||
period_ms: period.as_millis() as u64,
|
||||
clock,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(&self) -> bool {
|
||||
let now = self.clock.now_ms();
|
||||
let last = self.last_run_ms.load(Ordering::Relaxed);
|
||||
|
||||
if now < last + self.period_ms && now >= last {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.last_run_ms
|
||||
.compare_exchange(last, now, Ordering::SeqCst, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod extra_tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
struct MockClock(AtomicU64);
|
||||
impl Clock for MockClock {
|
||||
fn now_ms(&self) -> u64 {
|
||||
self.0.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_period_always_passes() {
|
||||
let mock = MockClock(AtomicU64::new(100));
|
||||
let limiter = Limiter::new(Duration::from_millis(0), &mock);
|
||||
|
||||
assert!(limiter.check());
|
||||
assert!(limiter.check());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boundary_condition() {
|
||||
let period_ms = 100;
|
||||
let mock = MockClock(AtomicU64::new(1000));
|
||||
let limiter = Limiter::new(Duration::from_millis(period_ms), &mock);
|
||||
|
||||
assert!(limiter.check());
|
||||
|
||||
mock.0.store(1099, Ordering::SeqCst);
|
||||
assert!(!limiter.check());
|
||||
|
||||
mock.0.store(1100, Ordering::SeqCst);
|
||||
assert!(limiter.check(), "Should pass exactly at period boundary");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_high_concurrency_consistency() {
|
||||
let period = Duration::from_millis(1000);
|
||||
let mock = Arc::new(MockClock(AtomicU64::new(1000)));
|
||||
let limiter = Arc::new(Limiter::new(period, Arc::clone(&mock)));
|
||||
|
||||
assert!(limiter.check());
|
||||
|
||||
mock.0.store(2500, Ordering::SeqCst);
|
||||
|
||||
let mut handles = vec![];
|
||||
for _ in 0..20 {
|
||||
let l = Arc::clone(&limiter);
|
||||
handles.push(thread::spawn(move || l.check()));
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let results: Vec<bool> = handles.into_iter().map(|h| h.join().unwrap()).collect();
|
||||
|
||||
let success_count = results.iter().filter(|&&x| x).count();
|
||||
assert_eq!(success_count, 1);
|
||||
|
||||
assert_eq!(limiter.last_run_ms.load(Ordering::SeqCst), 2500);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extreme_time_jump() {
|
||||
let mock = MockClock(AtomicU64::new(100));
|
||||
let limiter = Limiter::new(Duration::from_millis(100), &mock);
|
||||
|
||||
assert!(limiter.check());
|
||||
|
||||
mock.0.store(u64::MAX - 10, Ordering::SeqCst);
|
||||
assert!(limiter.check());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_clock_real_path() {
|
||||
let clock = SystemClock;
|
||||
let start = clock.now_ms();
|
||||
assert!(start > 0);
|
||||
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
assert!(clock.now_ms() >= start);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_limiter_with_system_clock_default() {
|
||||
let limiter = Limiter::new(Duration::from_millis(100), SystemClock);
|
||||
assert!(limiter.check());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_coverage_time_backward() {
|
||||
let mock = MockClock(AtomicU64::new(5000));
|
||||
let limiter = Limiter::new(Duration::from_millis(100), &mock);
|
||||
|
||||
assert!(limiter.check());
|
||||
|
||||
mock.0.store(4000, Ordering::SeqCst);
|
||||
|
||||
assert!(limiter.check(), "Should pass and reset when time moves backward");
|
||||
|
||||
assert_eq!(limiter.last_run_ms.load(Ordering::SeqCst), 4000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "clash-verge-logging"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
log = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
compact_str = { workspace = true }
|
||||
flexi_logger = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@@ -1,9 +1,8 @@
|
||||
use compact_str::CompactString;
|
||||
use flexi_logger::DeferredNow;
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
use flexi_logger::filter::LogLineFilter;
|
||||
use flexi_logger::writers::FileLogWriter;
|
||||
use flexi_logger::writers::LogWriter;
|
||||
use flexi_logger::writers::LogWriter as _;
|
||||
use log::Level;
|
||||
use log::Record;
|
||||
use std::{fmt, sync::Arc};
|
||||
@@ -18,6 +17,7 @@ pub enum Type {
|
||||
Config,
|
||||
Setup,
|
||||
System,
|
||||
SystemSignal,
|
||||
Service,
|
||||
Hotkey,
|
||||
Window,
|
||||
@@ -34,6 +34,7 @@ pub enum Type {
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Cmd => write!(f, "[Cmd]"),
|
||||
@@ -41,6 +42,7 @@ impl fmt::Display for Type {
|
||||
Self::Config => write!(f, "[Config]"),
|
||||
Self::Setup => write!(f, "[Setup]"),
|
||||
Self::System => write!(f, "[System]"),
|
||||
Self::SystemSignal => write!(f, "[SysSignal]"),
|
||||
Self::Service => write!(f, "[Service]"),
|
||||
Self::Hotkey => write!(f, "[Hotkey]"),
|
||||
Self::Window => write!(f, "[Window]"),
|
||||
@@ -58,44 +60,6 @@ impl fmt::Display for Type {
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($result: expr) => {
|
||||
log::error!(target: "app", "{}", $result);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_err {
|
||||
($result: expr) => {
|
||||
if let Err(err) = $result {
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
};
|
||||
|
||||
($result: expr, $err_str: expr) => {
|
||||
if let Err(_) = $result {
|
||||
log::error!(target: "app", "{}", $err_str);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// wrap the anyhow error
|
||||
/// transform the error to String
|
||||
#[macro_export]
|
||||
macro_rules! wrap_err {
|
||||
// Case 1: Future<Result<T, E>>
|
||||
($stat:expr, async) => {{
|
||||
match $stat.await {
|
||||
Ok(a) => Ok::<_, ::anyhow::Error>(a),
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "{}", err);
|
||||
Err(::anyhow::Error::msg(err.to_string()))
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! logging {
|
||||
// 不带 print 参数的版本(默认不打印)
|
||||
@@ -119,6 +83,7 @@ macro_rules! logging_error {
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_sidecar_log(
|
||||
writer: MutexGuard<'_, FileLogWriter>,
|
||||
now: &mut DeferredNow,
|
||||
@@ -127,27 +92,19 @@ pub fn write_sidecar_log(
|
||||
) {
|
||||
let args = format_args!("{}", message);
|
||||
|
||||
let record = Record::builder()
|
||||
.args(args)
|
||||
.level(level)
|
||||
.target("sidecar")
|
||||
.build();
|
||||
let record = Record::builder().args(args).level(level).target("sidecar").build();
|
||||
|
||||
let _ = writer.write(now, &record);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
pub struct NoModuleFilter<'a>(pub &'a [&'a str]);
|
||||
pub struct NoModuleFilter<'a>(pub Vec<&'a str>);
|
||||
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
impl<'a> NoModuleFilter<'a> {
|
||||
#[inline]
|
||||
pub fn filter(&self, record: &Record) -> bool {
|
||||
if let Some(module) = record.module_path() {
|
||||
for blocked in self.0 {
|
||||
if module.len() >= blocked.len()
|
||||
&& module.as_bytes()[..blocked.len()] == blocked.as_bytes()[..]
|
||||
{
|
||||
for blocked in self.0.iter() {
|
||||
if module.len() >= blocked.len() && module.as_bytes()[..blocked.len()] == blocked.as_bytes()[..] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -156,8 +113,8 @@ impl<'a> NoModuleFilter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
impl<'a> LogLineFilter for NoModuleFilter<'a> {
|
||||
#[inline]
|
||||
fn write(
|
||||
&self,
|
||||
now: &mut DeferredNow,
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "clash-verge-signal"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[dependencies]
|
||||
clash-verge-logging = { workspace = true }
|
||||
log = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,37 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use clash_verge_logging::{Type, logging};
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
pub(crate) static RUNTIME: OnceLock<Option<tokio::runtime::Runtime>> = OnceLock::new();
|
||||
|
||||
pub fn register<F, Fut>(f: F)
|
||||
where
|
||||
F: Fn() -> Fut + Send + Sync + 'static,
|
||||
Fut: Future + Send + 'static,
|
||||
{
|
||||
RUNTIME.get_or_init(
|
||||
|| match tokio::runtime::Builder::new_current_thread().enable_all().build() {
|
||||
Ok(rt) => Some(rt),
|
||||
Err(e) => {
|
||||
logging!(
|
||||
info,
|
||||
Type::SystemSignal,
|
||||
"register shutdown signal failed, create tokio runtime error: {}",
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
unix::register(f);
|
||||
|
||||
#[cfg(windows)]
|
||||
windows::register(f);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
|
||||
use crate::RUNTIME;
|
||||
|
||||
static IS_CLEANING_UP: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn register<F, Fut>(f: F)
|
||||
where
|
||||
F: Fn() -> Fut + Send + Sync + 'static,
|
||||
Fut: Future + Send + 'static,
|
||||
{
|
||||
if let Some(Some(rt)) = RUNTIME.get() {
|
||||
rt.spawn(async move {
|
||||
let mut sigterm = match signal(SignalKind::terminate()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
logging!(error, Type::SystemSignal, "Failed to register SIGTERM: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut sigint = match signal(SignalKind::interrupt()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
logging!(error, Type::SystemSignal, "Failed to register SIGINT: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut sighup = match signal(SignalKind::hangup()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
logging!(error, Type::SystemSignal, "Failed to register SIGHUP: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
let signal_name;
|
||||
tokio::select! {
|
||||
_ = sigterm.recv() => {
|
||||
signal_name = "SIGTERM";
|
||||
}
|
||||
_ = sigint.recv() => {
|
||||
signal_name = "SIGINT";
|
||||
}
|
||||
_ = sighup.recv() => {
|
||||
signal_name = "SIGHUP";
|
||||
}
|
||||
else => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if IS_CLEANING_UP.load(Ordering::SeqCst) {
|
||||
logging!(
|
||||
info,
|
||||
Type::SystemSignal,
|
||||
"Already shutting down, ignoring repeated signal: {}",
|
||||
signal_name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
IS_CLEANING_UP.store(true, Ordering::SeqCst);
|
||||
|
||||
logging!(info, Type::SystemSignal, "Caught signal {}", signal_name);
|
||||
|
||||
f().await;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logging!(
|
||||
error,
|
||||
Type::SystemSignal,
|
||||
"register shutdown signal failed, RUNTIME is not available"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use tokio::signal::windows;
|
||||
|
||||
use crate::RUNTIME;
|
||||
|
||||
static IS_CLEANING_UP: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn register<F, Fut>(f: F)
|
||||
where
|
||||
F: Fn() -> Fut + Send + Sync + 'static,
|
||||
Fut: Future + Send + 'static,
|
||||
{
|
||||
if let Some(Some(rt)) = RUNTIME.get() {
|
||||
rt.spawn(async move {
|
||||
let mut ctrl_c = match windows::ctrl_c() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+C: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut ctrl_close = match windows::ctrl_close() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Close: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut ctrl_shutdown = match windows::ctrl_shutdown() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Shutdown: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut ctrl_logoff = match windows::ctrl_logoff() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Logoff: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
let signal_name;
|
||||
tokio::select! {
|
||||
_ = ctrl_c.recv() => {
|
||||
signal_name = "Ctrl+C";
|
||||
}
|
||||
_ = ctrl_close.recv() => {
|
||||
signal_name = "Ctrl+Close";
|
||||
}
|
||||
_ = ctrl_shutdown.recv() => {
|
||||
signal_name = "Ctrl+Shutdown";
|
||||
}
|
||||
_ = ctrl_logoff.recv() => {
|
||||
signal_name = "Ctrl+Logoff";
|
||||
}
|
||||
}
|
||||
|
||||
if IS_CLEANING_UP.load(Ordering::SeqCst) {
|
||||
logging!(
|
||||
info,
|
||||
Type::SystemSignal,
|
||||
"Already shutting down, ignoring repeated signal: {}",
|
||||
signal_name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
IS_CLEANING_UP.store(true, Ordering::SeqCst);
|
||||
|
||||
logging!(info, Type::SystemSignal, "Caught Windows signal: {}", signal_name);
|
||||
|
||||
f().await;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logging!(
|
||||
error,
|
||||
Type::SystemSignal,
|
||||
"register shutdown signal failed, RUNTIME is not available"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "tauri-plugin-clash-verge-sysinfo"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[dependencies]
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-clipboard-manager = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
# sysinfo 0.38.2 conflicts with dark-light
|
||||
# see https://github.com/GuillaumeGomez/sysinfo/issues/1623
|
||||
sysinfo = { version = "0.38", features = ["network", "system"] }
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
libc = "0.2.182"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
deelevate = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,33 @@
|
||||
use parking_lot::RwLock;
|
||||
use tauri::{AppHandle, Runtime, State, command};
|
||||
use tauri_plugin_clipboard_manager::{ClipboardExt as _, Error};
|
||||
|
||||
use crate::Platform;
|
||||
|
||||
// TODO 迁移,让新的结构体允许通过 tauri command 正确使用 structure.field 方式获取信息
|
||||
#[command]
|
||||
pub fn get_system_info(state: State<'_, RwLock<Platform>>) -> Result<String, Error> {
|
||||
Ok(state.inner().read().to_string())
|
||||
}
|
||||
|
||||
/// 获取应用的运行时间(毫秒)
|
||||
#[command]
|
||||
pub fn get_app_uptime(state: State<'_, RwLock<Platform>>) -> Result<u128, Error> {
|
||||
Ok(state.inner().read().appinfo.app_startup_time.elapsed().as_millis())
|
||||
}
|
||||
|
||||
/// 检查应用是否以管理员身份运行
|
||||
#[command]
|
||||
pub fn app_is_admin(state: State<'_, RwLock<Platform>>) -> Result<bool, Error> {
|
||||
Ok(state.inner().read().appinfo.app_is_admin)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn export_diagnostic_info<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
state: State<'_, RwLock<Platform>>,
|
||||
) -> Result<(), Error> {
|
||||
let info = state.inner().read().to_string();
|
||||
let clipboard = app_handle.clipboard();
|
||||
clipboard.write_text(info)
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
pub mod commands;
|
||||
|
||||
#[cfg(windows)]
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
#[cfg(unix)]
|
||||
pub use libc;
|
||||
use parking_lot::RwLock;
|
||||
use sysinfo::{Networks, System};
|
||||
use tauri::{
|
||||
Manager as _, Runtime,
|
||||
plugin::{Builder, TauriPlugin},
|
||||
};
|
||||
|
||||
pub struct SysInfo {
|
||||
system_name: String,
|
||||
system_version: String,
|
||||
system_kernel_version: String,
|
||||
system_arch: String,
|
||||
}
|
||||
|
||||
impl Default for SysInfo {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
let system_name = System::name().unwrap_or_else(|| "Null".into());
|
||||
let system_version = System::long_os_version().unwrap_or_else(|| "Null".into());
|
||||
let system_kernel_version = System::kernel_version().unwrap_or_else(|| "Null".into());
|
||||
let system_arch = System::cpu_arch();
|
||||
Self {
|
||||
system_name,
|
||||
system_version,
|
||||
system_kernel_version,
|
||||
system_arch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppInfo {
|
||||
app_version: String,
|
||||
app_core_mode: String,
|
||||
pub app_startup_time: Instant,
|
||||
pub app_is_admin: bool,
|
||||
}
|
||||
|
||||
impl Default for AppInfo {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
let app_version = "0.0.0".into();
|
||||
let app_core_mode = "NotRunning".into();
|
||||
let app_is_admin = false;
|
||||
let app_startup_time = Instant::now();
|
||||
Self {
|
||||
app_version,
|
||||
app_core_mode,
|
||||
app_startup_time,
|
||||
app_is_admin,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Platform {
|
||||
pub sysinfo: SysInfo,
|
||||
pub appinfo: AppInfo,
|
||||
}
|
||||
|
||||
impl Debug for Platform {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Platform")
|
||||
.field("system_name", &self.sysinfo.system_name)
|
||||
.field("system_version", &self.sysinfo.system_version)
|
||||
.field("system_kernel_version", &self.sysinfo.system_kernel_version)
|
||||
.field("system_arch", &self.sysinfo.system_arch)
|
||||
.field("app_version", &self.appinfo.app_version)
|
||||
.field("app_core_mode", &self.appinfo.app_core_mode)
|
||||
.field("app_is_admin", &self.appinfo.app_is_admin)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Platform {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"System Name: {}\nSystem Version: {}\nSystem kernel Version: {}\nSystem Arch: {}\nVerge Version: {}\nRunning Mode: {}\nIs Admin: {}",
|
||||
self.sysinfo.system_name,
|
||||
self.sysinfo.system_version,
|
||||
self.sysinfo.system_kernel_version,
|
||||
self.sysinfo.system_arch,
|
||||
self.appinfo.app_version,
|
||||
self.appinfo.app_core_mode,
|
||||
self.appinfo.app_is_admin
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
#[inline]
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_binary_admin() -> bool {
|
||||
#[cfg(not(windows))]
|
||||
unsafe {
|
||||
libc::geteuid() == 0
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Token::with_current_process()
|
||||
.and_then(|token| token.privilege_level())
|
||||
.map(|level| level != PrivilegeLevel::NotPrivileged)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(unix)]
|
||||
pub fn current_gid() -> u32 {
|
||||
unsafe { libc::getgid() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn list_network_interfaces() -> Vec<String> {
|
||||
let mut networks = Networks::new();
|
||||
networks.refresh(false);
|
||||
networks.keys().map(|name| name.to_owned()).collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_app_core_mode<R: Runtime>(app: &tauri::AppHandle<R>, mode: impl Into<String>) {
|
||||
let platform_spec = app.state::<RwLock<Platform>>();
|
||||
let mut spec = platform_spec.write();
|
||||
spec.appinfo.app_core_mode = mode.into();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_app_uptime<R: Runtime>(app: &tauri::AppHandle<R>) -> Instant {
|
||||
let platform_spec = app.state::<RwLock<Platform>>();
|
||||
let spec = platform_spec.read();
|
||||
spec.appinfo.app_startup_time
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_current_app_handle_admin<R: Runtime>(app: &tauri::AppHandle<R>) -> bool {
|
||||
let platform_spec = app.state::<RwLock<Platform>>();
|
||||
let spec = platform_spec.read();
|
||||
spec.appinfo.app_is_admin
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::<R>::new("clash_verge_sysinfo")
|
||||
// TODO 现在 crate 还不是真正的 tauri 插件,必须由主 lib 自行注册
|
||||
// TODO 从 clash-verge 中迁移获取系统信息的 commnand 并实现优雅 structure.field 访问
|
||||
// .invoke_handler(tauri::generate_handler![
|
||||
// commands::get_system_info,
|
||||
// commands::get_app_uptime,
|
||||
// commands::app_is_admin,
|
||||
// commands::export_diagnostic_info,
|
||||
// ])
|
||||
.setup(move |app, _api| {
|
||||
let app_version = app.package_info().version.to_string();
|
||||
let is_admin = is_binary_admin();
|
||||
|
||||
let mut platform_spec = Platform::new();
|
||||
platform_spec.appinfo.app_version = app_version;
|
||||
platform_spec.appinfo.app_is_admin = is_admin;
|
||||
|
||||
app.manage(RwLock::new(platform_spec));
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
files:
|
||||
- source: /src/locales/en.json
|
||||
translation: /src/locales
|
||||
multilingual: 1
|
||||
+57
-59
@@ -1,81 +1,79 @@
|
||||
# CONTRIBUTING — i18n
|
||||
|
||||
Thank you for considering contributing to our localization work — your help is appreciated.
|
||||
Thanks for helping localize Clash Verge Rev. This guide reflects the current architecture, where the React frontend and the Tauri backend keep their translation bundles separate. Follow the steps below to keep both sides in sync without stepping on each other.
|
||||
|
||||
Quick overview
|
||||
## Quick workflow
|
||||
|
||||
- cvr-i18 is a CLI that helps manage simple top-level JSON locale files:
|
||||
- Detect duplicated top-level keys
|
||||
- Find keys missing versus a base file (default: en.json)
|
||||
- Export missing entries for translators
|
||||
- Reorder keys to match the base file for predictable diffs
|
||||
- Operate on a directory or a single file
|
||||
- Update the language folder under `src/locales/<lang>/`; use `src/locales/en/` as the canonical reference for keys and intent.
|
||||
- Run `pnpm i18n:format` to align structure (frontend JSON + backend YAML) and `pnpm i18n:types` to refresh generated typings.
|
||||
- If you touch backend copy, edit the matching YAML file in `crates/clash-verge-i18n/locales/<lang>.yml`.
|
||||
- Preview UI changes with `pnpm dev` (desktop shell) or `pnpm web:dev` (web only).
|
||||
- Keep PRs focused and add screenshots whenever layout could be affected by text length.
|
||||
|
||||
Get the CLI (No binary provided yet)
|
||||
## Frontend locale structure
|
||||
|
||||
```bash
|
||||
git clone https://github.com/clash-verge-rev/clash-verge-rev-i18n-cli
|
||||
cd clash-verge-rev-i18n-cli
|
||||
cargo install --path .
|
||||
# or
|
||||
cargo install --git https://github.com/clash-verge-rev/clash-verge-rev-i18n-cli
|
||||
Each locale folder mirrors the namespaces under `src/locales/en/`:
|
||||
|
||||
```
|
||||
src/locales/
|
||||
en/
|
||||
connections.json
|
||||
home.json
|
||||
shared.json
|
||||
...
|
||||
index.ts
|
||||
zh/
|
||||
...
|
||||
```
|
||||
|
||||
Common commands
|
||||
- JSON files map to namespaces (for example `home.json` → `home.*`). Keep keys scoped to the file they belong to.
|
||||
- `shared.json` stores reusable vocabulary (buttons, validations, etc.); feature-specific wording should live in the relevant namespace.
|
||||
- `index.ts` re-exports a `resources` object that aggregates the namespace JSON files. When adding or removing namespaces, mirror the pattern from `src/locales/en/index.ts`.
|
||||
- Frontend bundles are lazy-loaded by `src/services/i18n.ts`. Only languages listed in `supportedLanguages` are fetched at runtime, so append new codes there when you add a locale.
|
||||
|
||||
- Show help: `cvr-i18`
|
||||
- Directory (auto-detects `./locales` or `./src/locales`): `cvr-i18 -d /path/to/locales`
|
||||
- Check duplicates: `cvr-i18 -k`
|
||||
- Check missing keys: `cvr-i18 -m`
|
||||
- Export missing keys: `cvr-i18 -m -e ./exports`
|
||||
- Sort keys to base file: `cvr-i18 -s`
|
||||
- Use a base file: `cvr-i18 -b base.json`
|
||||
- Single file: `cvr-i18 -f locales/zh.json`
|
||||
Because backend translations now live in their own directory, you no longer need to run `pnpm prebuild` just to sync locales—the frontend folder is the sole source of truth for web bundles.
|
||||
|
||||
Options (short)
|
||||
## Tooling for i18n contributors
|
||||
|
||||
- `-d, --directory <DIR>`
|
||||
- `-f, --file <FILE>`
|
||||
- `-k, --duplicated-key`
|
||||
- `-m, --missing-key`
|
||||
- `-e, --export <DIR>`
|
||||
- `-s, --sort`
|
||||
- `-b, --base <FILE>`
|
||||
- `pnpm i18n:format` → `node scripts/cleanup-unused-i18n.mjs --align --apply`. It aligns key ordering, removes unused entries, and keeps all locales in lock-step with English across both JSON and YAML bundles.
|
||||
- `pnpm i18n:check` performs a dry-run audit of frontend and backend keys. It scans TS/TSX usage plus Rust `t!(...)` calls in `src-tauri/` and `crates/` to spot missing or extra entries.
|
||||
- `pnpm i18n:types` regenerates `src/types/generated/i18n-keys.ts` and `src/types/generated/i18n-resources.ts`, ensuring TypeScript catches invalid key usage.
|
||||
- For dynamic keys that the analyzer cannot statically detect, add explicit references in code or update the script whitelist to avoid false positives.
|
||||
|
||||
Exit codes
|
||||
## Backend (Tauri) locale bundles
|
||||
|
||||
- `0` — success (no issues)
|
||||
- `1` — issues found (duplicates/missing)
|
||||
- `2` — error (IO/parse/runtime)
|
||||
Native UI strings (tray menu, notifications, dialogs) use `rust-i18n` with YAML bundles stored in `crates/clash-verge-i18n/locales/<lang>.yml`. These files are completely independent from the frontend JSON modules.
|
||||
|
||||
How to contribute (recommended steps)
|
||||
- Keep `en.yml` semantically aligned with the Simplified Chinese baseline (`zh.yml`). Other locales may temporarily copy English if no translation is available yet.
|
||||
- When a backend feature introduces new strings, update every YAML file to keep the key set consistent. Missing keys fall back to the default language (`zh`), so catching gaps early avoids mixed-language output.
|
||||
- The same `pnpm i18n:check` / `pnpm i18n:format` tooling now validates backend YAML keys against Rust usage, so run it after backend i18n edits.
|
||||
- Rust code resolves the active language through the `clash-verge-i18n` crate (`crates/clash-verge-i18n/src/lib.rs`). No additional build step is required after editing YAML files; `tauri dev` and `tauri build` pick them up automatically.
|
||||
|
||||
- Start small: fix typos, improve phrasing, or refine tone and consistency.
|
||||
- Run the CLI against your locale files to detect duplicates or missing keys.
|
||||
- Export starter JSONs for translators with `-m -e <DIR>`.
|
||||
- Prefer incremental PRs or draft PRs; leave a comment on the issue if you want guidance.
|
||||
- Open an issue to report missing strings, UI context, or localization bugs.
|
||||
- Add or improve docs and tests to make future contributions easier.
|
||||
## Adding a new language
|
||||
|
||||
PR checklist
|
||||
1. Duplicate `src/locales/en/` into `src/locales/<new-lang>/` and translate the JSON files while preserving key structure.
|
||||
2. Update the locale’s `index.ts` to import every namespace. Matching the English file is the easiest way to avoid missing exports.
|
||||
3. Append the language code to `supportedLanguages` in `src/services/i18n.ts`.
|
||||
4. If the backend should expose the language, create `crates/clash-verge-i18n/<new-lang>.yml` and translate the keys used in existing YAML files.
|
||||
5. Run `pnpm i18n:format`, `pnpm i18n:types`, and (optionally) `pnpm i18n:check` in dry-run mode to confirm structure.
|
||||
|
||||
- Keep JSON files UTF-8 encoded.
|
||||
- Follow the repo’s locale file structure and naming conventions.
|
||||
- Reorder keys to match the base file (`-s`) for minimal diffs.
|
||||
- Test translations in a local dev build before opening a PR.
|
||||
- Reference related issues and explain any context for translations or changes.
|
||||
## Authoring guidelines
|
||||
|
||||
Notes
|
||||
- **Reuse shared vocabulary** before introducing new phrases—check `shared.json` for common actions, statuses, and labels.
|
||||
- **Prefer semantic keys** (`systemProxy`, `updateInterval`, `autoRefresh`) over positional ones (`item1`, `dialogTitle2`).
|
||||
- **Document placeholders** using `{{placeholder}}` and ensure components supply the required values.
|
||||
- **Group keys by UI responsibility** inside each namespace (`page`, `sections`, `forms`, `actions`, `tooltips`, `notifications`, `errors`, `tables`, `statuses`, etc.).
|
||||
- **Keep strings concise** to avoid layout issues. If a translation needs more context, leave a PR note so reviewers can verify the UI.
|
||||
|
||||
- The tool expects simple top-level JSON key/value maps.
|
||||
- Exported JSONs are starter files for translators (fill in values, keep keys).
|
||||
- Sorting keeps diffs consistent and reviewable.
|
||||
## Testing & QA
|
||||
|
||||
Repository
|
||||
https://github.com/clash-verge-rev/clash-verge-rev-i18n-cli
|
||||
- Launch the desktop shell with `pnpm dev` (or `pnpm web:dev`) and navigate through the affected views to confirm translations load and layouts behave.
|
||||
- Run `pnpm test` if you touched code that consumes translations or adjusts formatting logic.
|
||||
- For backend changes, trigger the relevant tray actions or notifications to verify the updated copy.
|
||||
- Note any remaining untranslated sections or layout concerns in your PR description so maintainers can follow up.
|
||||
|
||||
## Feedback & Contributions
|
||||
## Feedback & support
|
||||
|
||||
- For tool usage issues or feedback: please open an Issue in the [repository](https://github.com/clash-verge-rev/clash-verge-rev-i18n-cli) so it can be tracked and addressed.
|
||||
- For localization contributions (translations, fixes, context notes, etc.): submit a PR or Issue in this repository and include examples, context, and testing instructions when possible.
|
||||
- If you need help or a review, leave a comment on your submission requesting assistance.
|
||||
- File an issue for missing context, tooling bugs, or localization gaps so we can track them.
|
||||
- PRs that touch UI should include screenshots or GIFs whenever text length may affect layout.
|
||||
- Mention the commands you ran (formatting, type generation, tests) in the PR checklist. If you need extra context or review help, request it via a PR comment.
|
||||
|
||||
@@ -1,3 +1,176 @@
|
||||
## v(2.4.6)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 历经多轮磨合与修正,这是自 2.0 以来我们最满意的里程碑版本。建议所有用户立即升级。
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
- 修复首次启动时代理信息刷新缓慢
|
||||
- 修复无网络时无限请求 IP 归属查询
|
||||
- 修复 WebDAV 页面重试逻辑
|
||||
- 修复 Linux 通过 GUI 安装服务模式权限不符合预期
|
||||
- 修复 macOS 因网口顺序导致无法正确设置代理
|
||||
- 修复恢复休眠后无法操作托盘
|
||||
- 修复首页当前节点图标语义显示不一致
|
||||
- 修复使用 URL scheme 导入订阅时没有及时重载配置
|
||||
- 修复规则界面里的行号展示逻辑
|
||||
- 修复 Windows 托盘打开日志失败
|
||||
- 修复 KDE 首次启动报错
|
||||
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
|
||||
- 升级 Mihomo 内核到最新
|
||||
- 支持订阅设置自动延时监测间隔
|
||||
- 新增流量隧道管理界面,支持可视化添加/删除隧道配置
|
||||
- Masque 协议的 GUI 支持
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong> 🚀 优化改进 </strong></summary>
|
||||
|
||||
- 安装服务失败时报告更详细的错误
|
||||
- 避免脏订阅地址无法 Scheme 导入订阅
|
||||
- macOS TUN 覆盖 DNS 时使用 114.114.114.114
|
||||
- 连通性测试替换为更快的 http://1.0.0.1
|
||||
- 连接、规则、日志等页面的过滤搜索组件新增了清空输入框按钮
|
||||
- 链式代理增加明显的入口出口与数据流向标识
|
||||
- 优化 IP 信息卡
|
||||
- 美化代理组图标样式
|
||||
- 移除 Linux resources 文件夹下多余的服务二进制文件
|
||||
|
||||
</details>
|
||||
|
||||
## v2.4.5
|
||||
|
||||
- **Mihomo(Meta) 内核升级至 v1.19.19**
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
- 修复 macOS 有线网络 DNS 劫持失败
|
||||
- 修复 Monaco 编辑器内右键菜单显示异常
|
||||
- 修复设置代理端口时检查端口占用
|
||||
- 修复 Monaco 编辑器初始化卡 Loading
|
||||
- 修复恢复备份时 `config.yaml` / `profiles.yaml` 文件内字段未正确恢复
|
||||
- 修复 Windows 下系统主题同步问题
|
||||
- 修复 URL Schemes 无法正常导入
|
||||
- 修复 Linux 下无法安装 TUN 服务
|
||||
- 修复可能的端口被占用误报
|
||||
- 修复设置允许外部控制来源不能立即生效
|
||||
- 修复前端性能回归问题
|
||||
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
|
||||
- 允许代理页面允许高级过滤搜索
|
||||
- 备份设置页面新增导入备份按钮
|
||||
- 允许修改通知弹窗位置
|
||||
- 支持收起导航栏(导航栏右键菜单 / 界面设置)
|
||||
- 允许将出站模式显示在托盘一级菜单
|
||||
- 允许禁用在托盘中显示代理组
|
||||
- 支持在「编辑节点」中直接导入 AnyTLS URI 配置
|
||||
- 支持关闭「验证代理绕过格式」
|
||||
- 新增系统代理绕过和 TUN 排除自定义网段的可视化编辑器
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong> 🚀 优化改进 </strong></summary>
|
||||
|
||||
- 应用内更新日志支持解析并渲染 HTML 标签
|
||||
- 性能优化前后端在渲染流量图时的资源
|
||||
- 在 Linux NVIDIA 显卡环境下尝试禁用 WebKit DMABUF 渲染以规避潜在问题
|
||||
- Windows 下自启动改为计划任务实现
|
||||
- 改进托盘和窗口操作频率限制实现
|
||||
- 使用「编辑节点」添加节点时,自动将节点添加到第一个 `select` 类型的代理组的第一位
|
||||
- 隐藏侧边导航栏和悬浮跳转导航的滚动条
|
||||
- 完善对 AnyTLS / Mieru / Sudoku 的 GUI 支持
|
||||
- macOS 和 Linux 对服务 IPC 权限进一步限制
|
||||
- 移除 Windows 自启动计划任务中冗余的 3 秒延时
|
||||
- 右键错误通知可复制错误详情
|
||||
- 保存 TUN 设置时优化执行流程,避免界面卡顿
|
||||
- 补充 `deb` / `rpm` 依赖 `libayatana-appindicator`
|
||||
- 「连接」表格标题的排序点击区域扩展到整列宽度
|
||||
- 备份恢复时显示加载覆盖层,恢复过程无需再手动关闭对话框
|
||||
|
||||
</details>
|
||||
|
||||
## v2.4.4
|
||||
|
||||
- **Mihomo(Meta) 内核升级至 v1.19.17**
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
- Linux 无法切换 TUN 堆栈
|
||||
- macOS service 启动项显示名称(试验性修改)
|
||||
- macOS 非预期 Tproxy 端口设置
|
||||
- 流量图缩放异常
|
||||
- PAC 自动代理脚本内容无法动态调整
|
||||
- 兼容从旧版服务模式升级
|
||||
- Monaco 编辑器的行数上限
|
||||
- 已删除节点在手动分组中导致配置无法加载
|
||||
- 仪表盘与托盘状态不同步
|
||||
- 彻底修复 macOS 连接页面显示异常
|
||||
- windows 端监听关机信号失败
|
||||
- 修复代理按钮和高亮状态不同步
|
||||
- 修复侧边栏可能的未能正确跳转
|
||||
- 修复解锁测试部分地区图标编码不正确
|
||||
- 修复 IP 检测切页后强制刷新,改为仅在必要时更新
|
||||
- 修复在搜索框输入不完整正则直接崩溃
|
||||
- 修复创建窗口时在非简体中文环境或深色主题下的短暂闪烁
|
||||
- 修复更新时加载进度条异常
|
||||
- 升级内核失败导致内核不可用问题
|
||||
- 修复 macOS 在安装和卸载服务时提示与操作不匹配
|
||||
- 修复菜单排序模式拖拽异常
|
||||
- 修复托盘菜单代理组前的异常勾选状态
|
||||
- 修复 Windows 下自定义标题栏按钮在最小化 / 关闭后 hover 状态残留
|
||||
- 修复直接覆盖 `config.yaml` 使用时无法展开代理组
|
||||
- 修复 macOS 下应用启动时系统托盘图标颜色闪烁
|
||||
- 修复应用静默启动模式下非全局热键一直抢占其他应用按键问题
|
||||
- 修复首页当前节点卡片按延迟排序时,打开节点列表后,`timeout` 节点被排在正常节点前的问题
|
||||
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
|
||||
- 支持连接页面各个项目的排序
|
||||
- 实现可选的自动备份
|
||||
- 连接页面支持查看已关闭的连接(最近最多 500 个已关闭连接)
|
||||
- 日志页面支持按时间倒序
|
||||
- 增加「重新激活订阅」的全局快捷键
|
||||
- WebView2 Runtime 修复构建升级到 133.0.3065.92
|
||||
- 侧边栏右键新增「恢复默认排序」
|
||||
- Linux 下新增对 TUN 「自动重定向」(`auto-redirect` 字段)的配置支持,默认关闭
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong> 🚀 优化改进 </strong></summary>
|
||||
|
||||
- 网络请求改为使用 rustls,提升 TLS 兼容性
|
||||
- rustls 避免因服务器证书链配置问题或较新 TLS 要求导致订阅无法导入
|
||||
- 替换前端信息编辑组件,提供更好性能
|
||||
- 优化后端内存和性能表现
|
||||
- 防止退出时可能的禁用 TUN 失败
|
||||
- 全新 i18n 支持方式
|
||||
- 优化备份设置布局
|
||||
- 优化流量图性能表现,实现动态 FPS 和窗口失焦自动暂停
|
||||
- 性能优化系统状态获取
|
||||
- 优化托盘菜单当前订阅检测逻辑
|
||||
- 优化连接页面表格渲染
|
||||
- 优化链式代理 UI 反馈
|
||||
- 优化重启应用的资源清理逻辑
|
||||
- 优化前端数据刷新
|
||||
- 优化流量采样和数据处理
|
||||
- 优化应用重启/退出时的资源清理性能, 大幅缩短执行时间
|
||||
- 优化前端 WebSocket 连接机制
|
||||
- 改进旧版 Service 需要重新安装检测流程
|
||||
- 优化 macOS, Linux 和 Windows 系统信号处理
|
||||
- 链式代理仅显示 Selector 类型规则组
|
||||
- 优化 Windows 系统代理设置,不再依赖 `sysproxy.exe` 来设置代理
|
||||
|
||||
</details>
|
||||
|
||||
## v2.4.3
|
||||
|
||||
**发行代号:澜**
|
||||
+14
-20
@@ -16,7 +16,8 @@ A Clash Meta GUI built with <a href="https://github.com/tauri-apps/tauri">Tauri<
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## Preview
|
||||
@@ -50,30 +51,23 @@ Join [@clash_verge_rev](https://t.me/clash_verge_re) for update announcements.
|
||||
|
||||
## Promotion
|
||||
|
||||
#### [Doggygo VPN — Performance-oriented global accelerator](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
### ✈️ [Doggygo VPN — A Technical-Grade Proxy Service](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
- High-performance overseas network service with free trials, discounted plans, streaming unlocks, and first-class Hysteria protocol support.
|
||||
- Register through the exclusive Clash Verge link to get a 3-day trial with 1 GB of traffic per day: [Sign up](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
- Exclusive 20% off coupon for Clash Verge users: `verge20` (limited to 500 uses)
|
||||
- Discounted bundle from ¥15.8 per month for 160 GB, plus an additional 20% off for yearly billing
|
||||
- Operated by an overseas team with reliable service and up to 50% revenue share
|
||||
- Load-balanced clusters with high-speed dedicated routes (compatible with legacy clients), exceptionally low latency, smooth 4K playback
|
||||
- First global provider to support the `Hysteria2` protocol—perfect fit for the Clash Verge client
|
||||
- Supports streaming services and ChatGPT access
|
||||
- Official site: [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
🚀 A high-performance, overseas, technical-grade proxy service offering free trials and discounted plans, fully unlocking streaming platforms and AI services. The world’s first provider to adopt the **QUIC protocol**.
|
||||
|
||||
#### Build Infrastructure Sponsor — [YXVM Dedicated Servers](https://yxvm.com/aff.php?aff=827)
|
||||
🎁 Register via the **Clash Verge exclusive invitation link** to receive **3 days of free trial**, with **1GB traffic per day**: 👉 [Register here](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
Our builds and releases run on YXVM dedicated servers that deliver premium resources, strong performance, and high-speed networking. If downloads feel fast and usage feels snappy, it is thanks to robust hardware.
|
||||
#### **Core Advantages:**
|
||||
|
||||
🧩 Highlights of YXVM Dedicated Servers:
|
||||
- 📱 Self-developed iOS client (the industry’s “only one”), with technology proven in production and **significant ongoing R&D investment**
|
||||
- 🧑💻 **12-hour live customer support** (also assists with Clash Verge usage issues)
|
||||
- 💰 Discounted plans at **only CNY 21 per month, 160GB traffic, 20% off with annual billing**
|
||||
- 🌍 Overseas team, no risk of shutdown or exit scams, with up to **50% referral commission**
|
||||
- ⚙️ **Cluster-based load balancing** architecture with **real-time load monitoring and elastic scaling**, high-speed dedicated lines (compatible with legacy clients), ultra-low latency, unaffected by peak hours, **4K streaming loads instantly**
|
||||
- ⚡ The world’s first **QUIC-protocol-based proxy service**, now upgraded with the faster **Tuic protocol** (best paired with the Clash Verge client)
|
||||
- 🎬 Unlocks **streaming platforms and mainstream AI services**
|
||||
|
||||
- 🌎 Optimized global routes for dramatically faster downloads
|
||||
- 🔧 Bare-metal resources instead of shared VPS capacity for maximum performance
|
||||
- 🧠 Great for proxy workloads, hosting web/CDN services, CI/CD pipelines, or any high-load tasks
|
||||
- 💡 Ready to use instantly with multiple datacenter options, including CN2 and IEPL
|
||||
- 📦 The configuration used by this project is on sale—feel free to get the same setup
|
||||
- 🎯 Want the same build environment? [Order a YXVM server today](https://yxvm.com/aff.php?aff=827)
|
||||
🌐 Official Website: 👉 [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
+2
-14
@@ -16,7 +16,8 @@ Una interfaz gráfica para Clash Meta construida con <a href="https://github.com
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## Vista previa
|
||||
@@ -62,19 +63,6 @@ Consulta la [documentación del proyecto](https://clash-verge-rev.github.io/) pa
|
||||
- Desbloquea servicios de streaming y acceso a ChatGPT
|
||||
- Sitio oficial: [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
#### Patrocinador de la infraestructura de compilación — [Servidores dedicados YXVM](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
Las compilaciones y lanzamientos del proyecto se ejecutan en servidores dedicados de YXVM, que proporcionan recursos premium, alto rendimiento y redes de alta velocidad. Si las descargas son rápidas y el uso es fluido, es gracias a este hardware robusto.
|
||||
|
||||
🧩 Ventajas de los servidores dedicados YXVM:
|
||||
|
||||
- 🌎 Rutas globales optimizadas para descargas significativamente más rápidas
|
||||
- 🔧 Recursos bare-metal, en lugar de VPS compartidos, para obtener el máximo rendimiento
|
||||
- 🧠 Ideales para proxys, alojamiento de sitios web/CDN, pipelines de CI/CD o cualquier carga elevada
|
||||
- 💡 Listos para usar al instante, con múltiples centros de datos disponibles (incluidos CN2 e IEPL)
|
||||
- 📦 La misma configuración utilizada por este proyecto está disponible para su compra
|
||||
- 🎯 ¿Quieres el mismo entorno de compilación? [Solicita un servidor YXVM hoy](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
## Funciones
|
||||
|
||||
- Basado en Rust de alto rendimiento y en el framework Tauri 2
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<h1 align="center">
|
||||
<img src="../src-tauri/icons/icon.png" alt="Clash" width="128" />
|
||||
<br>
|
||||
Continuation of <a href="https://github.com/zzzgydi/clash-verge">Clash Verge</a>
|
||||
<br>
|
||||
</h1>
|
||||
|
||||
<h3 align="center">
|
||||
یک رابط کاربری گرافیکی Clash Meta که با <a href="https://github.com/tauri-apps/tauri">Tauri</a> ساخته شده است.
|
||||
</h3>
|
||||
|
||||
<p align="center">
|
||||
زبانها:
|
||||
<a href="../README.md">简体中文</a> ·
|
||||
<a href="./README_en.md">English</a> ·
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## پیشنمایش
|
||||
|
||||
| تاریک | روشن |
|
||||
| ----------------------------------- | ------------------------------------- |
|
||||
|  |  |
|
||||
|
||||
## نصب
|
||||
|
||||
برای دانلود فایل نصبی متناسب با پلتفرم خود، به [صفحه انتشار](https://github.com/clash-verge-rev/clash-verge-rev/releases) مراجعه کنید.<br> ما بستههایی برای ویندوز (x64/x86)، لینوکس (x64/arm64) و macOS 10.15+ (اینتل/اپل) ارائه میدهیم.
|
||||
|
||||
#### انتخاب کانال انتشار
|
||||
|
||||
| Channel | توضیحات | Link |
|
||||
| :---------- | :------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------- |
|
||||
| Stable | ساخت رسمی با قابلیت اطمینان بالا، ایدهآل برای استفاده روزانه. | [Release](https://github.com/clash-verge-rev/clash-verge-rev/releases) |
|
||||
| Alpha (EOL) | نسخههای قدیمی (Legacy builds) برای اعتبارسنجی خط لوله انتشار (publish pipeline) استفاده میشوند. | [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) |
|
||||
| AutoBuild | نسخههای آزمایشی برای آزمایش و دریافت بازخورد. منتظر تغییرات آزمایشی باشید. | [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) |
|
||||
|
||||
#### راهنماهای نصب و سوالات متداول
|
||||
|
||||
برای مراحل نصب، عیبیابی و سوالات متداول، [مستندات پروژه](https://clash-verge-rev.github.io/) را مطالعه کنید.
|
||||
|
||||
---
|
||||
|
||||
### کانال تلگرام
|
||||
|
||||
برای اطلاع از آخرین اخبار به [@clash_verge_rev](https://t.me/clash_verge_re) بپیوندید.
|
||||
|
||||
## تبلیغات
|
||||
|
||||
#### [Doggygo VPN — شتابدهنده جهانی عملکردگرا](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
- سرویس شبکه برون مرزی با عملکرد بالا به همراه دورههای آزمایشی رایگان، طرحهای تخفیفدار، امکان باز کردن قفل استریم و پشتیبانی درجه یک از پروتکل هیستریا.
|
||||
- از طریق لینک اختصاصی Clash Verge ثبت نام کنید تا یک دوره آزمایشی ۳ روزه با ۱ گیگابایت ترافیک در روز دریافت کنید: [ثبت نام](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
- کوپن تخفیف ۲۰٪ ویژه کاربران Clash Verge: `verge20` (محدود به ۵۰۰ بار استفاده)
|
||||
- بسته تخفیفدار از ۱۵.۸ ین در ماه برای ۱۶۰ گیگابایت، به علاوه ۲۰٪ تخفیف اضافی برای صورتحساب سالانه
|
||||
- توسط یک تیم خارجی با خدمات قابل اعتماد و تا 50٪ سهم درآمد اداره میشود
|
||||
- کلاسترهای متعادل بار با مسیرهای اختصاصی پرسرعت (سازگار با کلاینتهای قدیمی)، تأخیر فوقالعاده کم، پخش روان 4K
|
||||
- اولین ارائهدهنده جهانی که از پروتکل «Hysteria2» پشتیبانی میکند - کاملاً مناسب برای کلاینت Clash Verge
|
||||
- پشتیبانی از سرویسهای استریم و دسترسی به ChatGPT
|
||||
- وبسایت رسمی: [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
## ویژگیها
|
||||
|
||||
- ساخته شده بر اساس Rust با کارایی بالا و فریمورک Tauri 2
|
||||
- با هسته جاسازیشده [Clash.Meta (mihomo)](https://github.com/MetaCubeX/mihomo) ارائه میشود و از تغییر به کانال «آلفا» پشتیبانی میکند.
|
||||
- رابط کاربری تمیز و مرتب با کنترلهای رنگ تم، آیکونهای گروه/سینی پروکسی و `تزریق CSS`
|
||||
- مدیریت پروفایل پیشرفته (ادغام و کمککنندههای اسکریپت) با نکات مربوط به سینتکس پیکربندی
|
||||
- کنترلهای پروکسی سیستم، حالت محافظت و پشتیبانی از `TUN` (آداپتور شبکه مجازی)
|
||||
- ویرایشگرهای بصری برای گرهها و قوانین
|
||||
- پشتیبانگیری و همگامسازی مبتنی بر WebDAV برای تنظیمات
|
||||
|
||||
### سوالات متداول
|
||||
|
||||
برای راهنماییهای مربوط به هر پلتفرم، به [صفحه سوالات متداول](https://clash-verge-rev.github.io/faq/windows.html) مراجعه کنید.
|
||||
|
||||
### اهدا
|
||||
|
||||
[پشتیبانی از توسعه Clash Verge Rev](https://github.com/sponsors/clash-verge-rev)
|
||||
|
||||
## توسعه
|
||||
|
||||
برای دستورالعملهای دقیق مشارکت، به [CONTRIBUTING.md](../CONTRIBUTING.md) مراجعه کنید.
|
||||
|
||||
پس از نصب تمام پیشنیازهای **Tauri**، پوسته توسعه را با دستور زیر اجرا کنید:
|
||||
|
||||
```shell
|
||||
pnpm i
|
||||
pnpm run prebuild
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## مشارکتها
|
||||
|
||||
مشکلات و درخواستهای pull مورد استقبال قرار میگیرند!
|
||||
|
||||
## تقدیر و تشکر
|
||||
|
||||
Clash Verge Rev بر اساس این پروژهها ساخته شده یا از آنها الهام گرفته است:
|
||||
|
||||
- [zzzgydi/clash-verge](https://github.com/zzzgydi/clash-verge): یک رابط کاربری گرافیکی Clash مبتنی بر Tauri برای ویندوز، macOS و لینوکس..
|
||||
- [tauri-apps/tauri](https://github.com/tauri-apps/tauri): ساخت برنامههای دسکتاپ کوچکتر، سریعتر و امنتر با رابط کاربری وب.
|
||||
- [Dreamacro/clash](https://github.com/Dreamacro/clash): یک تونل مبتنی بر قانون که با زبان Go نوشته شده است.
|
||||
- [MetaCubeX/mihomo](https://github.com/MetaCubeX/mihomo): یک تونل مبتنی بر قانون که با زبان Go نوشته شده است.
|
||||
- [Fndroid/clash_for_windows_pkg](https://github.com/Fndroid/clash_for_windows_pkg): رابط کاربری گرافیکی Clash برای ویندوز و macOS.
|
||||
- [vitejs/vite](https://github.com/vitejs/vite): ابزارهای فرانتاند نسل بعدی با DX فوقالعاده سریع.
|
||||
|
||||
## مجوز
|
||||
|
||||
مجوز GPL-3.0. برای جزئیات بیشتر به [فایل مجوز](../LICENSE) مراجعه کنید.
|
||||
+2
-14
@@ -16,7 +16,8 @@
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## プレビュー
|
||||
@@ -62,19 +63,6 @@ Windows (x64/x86)、Linux (x64/arm64)、macOS 10.15+ (Intel/Apple) をサポー
|
||||
- ストリーミングおよび ChatGPT の利用にも対応
|
||||
- 公式サイト: [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
#### ビルド環境スポンサー — [YXVM 専用サーバー](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
本プロジェクトのビルドとリリースは、YXVM の専用サーバーによって支えられています。高速ダウンロードや快適な操作性は、強力なハードウェアがあってこそです。
|
||||
|
||||
🧩 YXVM 専用サーバーの特長:
|
||||
|
||||
- 🌎 最適化されたグローバル回線で圧倒的なダウンロード速度
|
||||
- 🔧 VPS とは異なるベアメタル資源で最高性能を発揮
|
||||
- 🧠 プロキシ運用、Web/CDN ホスティング、CI/CD など高負荷ワークロードに最適
|
||||
- 💡 複数データセンターから即時利用可能。CN2 や IEPL も選択可
|
||||
- 📦 本プロジェクトが使用している構成も販売中。同じ環境を入手できます
|
||||
- 🎯 同じビルド体験をしたい方は [今すぐ YXVM サーバーを注文](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
## 機能
|
||||
|
||||
- 高性能な Rust と Tauri 2 フレームワークに基づくデスクトップアプリ
|
||||
|
||||
+2
-14
@@ -16,7 +16,8 @@
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## 미리보기
|
||||
@@ -62,19 +63,6 @@ Windows (x64/x86), Linux (x64/arm64), macOS 10.15+ (Intel/Apple)을 지원합니
|
||||
- 스트리밍 및 ChatGPT 접근 지원
|
||||
- 공식 사이트: [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
#### 빌드 인프라 스폰서 — [YXVM 전용 서버](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
본 프로젝트의 빌드 및 릴리스는 YXVM 전용 서버에서 구동됩니다. 빠른 다운로드와 경쾌한 사용감은 탄탄한 하드웨어 덕분입니다.
|
||||
|
||||
🧩 YXVM 전용 서버 하이라이트:
|
||||
|
||||
- 🌎 최적화된 글로벌 라우팅으로 대폭 빨라진 다운로드
|
||||
- 🔧 공유 VPS가 아닌 베어메탈 자원으로 최대 성능 제공
|
||||
- 🧠 프록시 워크로드, Web/CDN 호스팅, CI/CD, 고부하 작업에 적합
|
||||
- 💡 CN2 / IEPL 등 다양한 데이터센터 옵션, 즉시 사용 가능
|
||||
- 📦 본 프로젝트가 사용하는 구성도 판매 중 — 동일한 환경을 사용할 수 있습니다
|
||||
- 🎯 동일한 빌드 환경이 필요하다면 [지금 YXVM 서버 주문](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
## 기능
|
||||
|
||||
- 고성능 Rust와 Tauri 2 프레임워크 기반 데스크톱 앱
|
||||
|
||||
+2
-14
@@ -16,7 +16,8 @@ Clash Meta GUI базируется на <a href="https://github.com/tauri-apps/
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
## Предпросмотр
|
||||
|
||||
@@ -58,19 +59,6 @@ Clash Meta GUI базируется на <a href="https://github.com/tauri-apps/
|
||||
- Разблокировка потоковые сервисы и ChatGPT
|
||||
- Официальный сайт: [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
#### Среда сборки и публикации этого проекта полностью поддерживается выделенным сервером [YXVM](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
Благодарим вас за предоставление надежной бэкэнд-среды с эксклюзивными ресурсами, высокой производительностью и высокоскоростной сетью. Если вы считаете, что загрузка файлов происходит достаточно быстро, а использование — достаточно плавно, то это потому, что мы используем серверы высшего уровня!
|
||||
|
||||
🧩 Преимущества выделенного сервера YXVM:
|
||||
|
||||
- 🌎 Премиум-сеть с оптимизацией обратного пути для молниеносной скорости загрузки
|
||||
- 🔧 Выделенные физические серверные ресурсы, не имеющие аналогов среди VPS, обеспечивающие максимальную производительность
|
||||
- 🧠 Идеально подходит для прокси, хостинга веб-сайтов/CDN-сайтов, рабочих процессов CI/CD или любых приложений с высокой нагрузкой
|
||||
- 💡 Поддержка использования сразу после включения, выбор нескольких дата-центров, CN2 / IEPL на выбор
|
||||
- 📦 Эта конфигурация в настоящее время доступна для покупки — не стесняйтесь заказывать ту же модель!
|
||||
- 🎯 Хотите попробовать такую же сборку? [Закажите выделенный сервер YXVM прямо сейчас!](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
## Фичи
|
||||
|
||||
- Основан на произвоительном Rust и фреймворке Tauri 2
|
||||
|
||||
@@ -5,6 +5,7 @@ import configPrettier from "eslint-config-prettier";
|
||||
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
|
||||
import pluginImportX from "eslint-plugin-import-x";
|
||||
import pluginPrettier from "eslint-plugin-prettier";
|
||||
import pluginReactCompiler from "eslint-plugin-react-compiler";
|
||||
import pluginReactHooks from "eslint-plugin-react-hooks";
|
||||
import pluginReactRefresh from "eslint-plugin-react-refresh";
|
||||
import pluginUnusedImports from "eslint-plugin-unused-imports";
|
||||
@@ -17,7 +18,9 @@ export default defineConfig([
|
||||
|
||||
plugins: {
|
||||
js: eslintJS,
|
||||
// @ts-expect-error -- https://github.com/typescript-eslint/typescript-eslint/issues/11543
|
||||
"react-hooks": pluginReactHooks,
|
||||
"react-compiler": pluginReactCompiler,
|
||||
// @ts-expect-error -- https://github.com/un-ts/eslint-plugin-import-x/issues/421
|
||||
"import-x": pluginImportX,
|
||||
"react-refresh": pluginReactRefresh,
|
||||
@@ -34,6 +37,11 @@ export default defineConfig([
|
||||
|
||||
languageOptions: {
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
projectService: {
|
||||
allowDefaultProject: ["src/polyfills/*.js"],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
settings: {
|
||||
@@ -51,6 +59,7 @@ export default defineConfig([
|
||||
// React
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "error",
|
||||
"react-compiler/react-compiler": "error",
|
||||
"react-refresh/only-export-components": [
|
||||
"warn",
|
||||
{ allowConstantExport: true },
|
||||
@@ -132,4 +141,14 @@ export default defineConfig([
|
||||
"prettier/prettier": "warn",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["scripts/**/*.{js,mjs,cjs}", "scripts-workflow/**/*.{js,mjs,cjs}"],
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
+72
-64
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "2.4.3",
|
||||
"version": "2.4.7",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"prepare": "husky || true",
|
||||
@@ -24,14 +24,14 @@
|
||||
"release:autobuild": "pnpm release-version autobuild",
|
||||
"release:deploytest": "pnpm release-version deploytest",
|
||||
"publish-version": "node scripts/publish-version.mjs",
|
||||
"fmt": "cargo fmt --manifest-path ./src-tauri/Cargo.toml",
|
||||
"clippy": "cargo clippy --all-features --all-targets --manifest-path ./src-tauri/Cargo.toml",
|
||||
"lint": "eslint -c eslint.config.ts --max-warnings=0 --cache --cache-location .eslintcache src",
|
||||
"lint:fix": "eslint -c eslint.config.ts --max-warnings=0 --cache --cache-location .eslintcache --fix src",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run"
|
||||
"i18n:check": "node scripts/cleanup-unused-i18n.mjs",
|
||||
"i18n:format": "node scripts/cleanup-unused-i18n.mjs --align --apply",
|
||||
"i18n:types": "node scripts/generate-i18n-keys.mjs",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
@@ -40,97 +40,105 @@
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"@mui/icons-material": "^7.3.5",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@mui/icons-material": "^7.3.8",
|
||||
"@mui/lab": "7.0.0-beta.17",
|
||||
"@mui/material": "^7.3.5",
|
||||
"@mui/x-data-grid": "^8.17.0",
|
||||
"@tauri-apps/api": "2.9.0",
|
||||
"@mui/material": "^7.3.8",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@tanstack/react-virtual": "^3.13.18",
|
||||
"@tauri-apps/api": "2.10.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
||||
"@tauri-apps/plugin-fs": "^2.4.4",
|
||||
"@tauri-apps/plugin-http": "~2.5.4",
|
||||
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||
"@tauri-apps/plugin-http": "~2.5.7",
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-shell": "2.3.3",
|
||||
"@tauri-apps/plugin-updater": "2.9.0",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@tauri-apps/plugin-shell": "2.3.5",
|
||||
"@tauri-apps/plugin-updater": "2.10.0",
|
||||
"ahooks": "^3.9.6",
|
||||
"axios": "^1.13.2",
|
||||
"axios": "^1.13.5",
|
||||
"dayjs": "1.11.19",
|
||||
"foxact": "^0.2.49",
|
||||
"i18next": "^25.6.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-schema": "^0.4.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"monaco-editor": "^0.54.0",
|
||||
"monaco-yaml": "^5.4.0",
|
||||
"foxact": "^0.2.52",
|
||||
"foxts": "^5.2.1",
|
||||
"i18next": "^25.8.13",
|
||||
"ipaddr.js": "^2.3.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"lodash-es": "^4.17.23",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"monaco-yaml": "^5.4.1",
|
||||
"nanoid": "^5.1.6",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-error-boundary": "6.0.0",
|
||||
"react-hook-form": "^7.66.0",
|
||||
"react-i18next": "16.2.4",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"react-error-boundary": "6.1.1",
|
||||
"react-hook-form": "^7.71.2",
|
||||
"react-i18next": "16.5.4",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-monaco-editor": "0.59.0",
|
||||
"react-router": "^7.9.5",
|
||||
"react-virtuoso": "^4.14.1",
|
||||
"swr": "^2.3.6",
|
||||
"tauri-plugin-mihomo-api": "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo",
|
||||
"react-router": "^7.13.0",
|
||||
"react-virtuoso": "^4.18.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"swr": "^2.4.0",
|
||||
"tauri-plugin-mihomo-api": "github:clash-verge-rev/tauri-plugin-mihomo#main",
|
||||
"types-pac": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "^6.0.1",
|
||||
"@eslint-react/eslint-plugin": "^2.3.1",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@tauri-apps/cli": "2.9.3",
|
||||
"@actions/github": "^9.0.0",
|
||||
"@eslint-react/eslint-plugin": "^2.13.0",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@tauri-apps/cli": "2.10.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.10.0",
|
||||
"@types/react": "19.2.2",
|
||||
"@types/react-dom": "19.2.2",
|
||||
"@types/node": "^24.10.13",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@vitejs/plugin-legacy": "^7.2.1",
|
||||
"@vitejs/plugin-react-swc": "^4.2.1",
|
||||
"@vitejs/plugin-react-swc": "^4.2.3",
|
||||
"adm-zip": "^0.5.16",
|
||||
"cli-color": "^2.0.4",
|
||||
"commander": "^14.0.2",
|
||||
"commander": "^14.0.3",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint": "^10.0.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import-x": "^4.16.1",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-react-compiler": "19.1.0-rc.2",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"eslint-plugin-unused-imports": "^4.3.0",
|
||||
"glob": "^11.0.3",
|
||||
"globals": "^16.5.0",
|
||||
"eslint-plugin-react-refresh": "^0.5.0",
|
||||
"eslint-plugin-unused-imports": "^4.4.1",
|
||||
"glob": "^13.0.6",
|
||||
"globals": "^17.3.0",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"husky": "^9.1.7",
|
||||
"jiti": "^2.6.1",
|
||||
"lint-staged": "^16.2.6",
|
||||
"meta-json-schema": "^1.19.14",
|
||||
"lint-staged": "^16.2.7",
|
||||
"node-fetch": "^3.3.2",
|
||||
"prettier": "^3.6.2",
|
||||
"sass": "^1.93.3",
|
||||
"tar": "^7.5.2",
|
||||
"terser": "^5.44.1",
|
||||
"prettier": "^3.8.1",
|
||||
"sass": "^1.97.3",
|
||||
"tar": "^7.5.9",
|
||||
"terser": "^5.46.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.46.3",
|
||||
"vite": "^7.2.2",
|
||||
"vite-plugin-monaco-editor-esm": "^2.0.2",
|
||||
"vite-plugin-svgr": "^4.5.0",
|
||||
"vitest": "^4.0.8"
|
||||
"typescript-eslint": "^8.56.0",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-svgr": "^4.5.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx,js,jsx}": [
|
||||
"eslint --fix --max-warnings=0",
|
||||
"prettier --write",
|
||||
"git add"
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{css,scss,json,md}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@9.13.2"
|
||||
"packageManager": "pnpm@10.29.2",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
"@swc/core",
|
||||
"core-js",
|
||||
"es5-ext",
|
||||
"esbuild",
|
||||
"unrs-resolver"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+2519
-3916
File diff suppressed because it is too large
Load Diff
+15
-7
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": ["config:recommended", ":disableDependencyDashboard"],
|
||||
"baseBranches": ["dev"],
|
||||
"enabledManagers": ["cargo", "npm"],
|
||||
"enabledManagers": ["cargo", "npm", "github-actions"],
|
||||
"labels": ["dependencies"],
|
||||
"ignorePaths": [
|
||||
"**/node_modules/**",
|
||||
@@ -11,14 +11,17 @@
|
||||
"**/test/**",
|
||||
"**/tests/**",
|
||||
"**/__fixtures__/**",
|
||||
"**/crate/**",
|
||||
"shared/**"
|
||||
],
|
||||
"rangeStrategy": "bump",
|
||||
"rangeStrategy": "replace",
|
||||
"packageRules": [
|
||||
{
|
||||
"semanticCommitType": "chore",
|
||||
"matchPackageNames": ["*"]
|
||||
"matchUpdateTypes": ["patch"],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["*"],
|
||||
"semanticCommitType": "chore"
|
||||
},
|
||||
{
|
||||
"description": "Disable node/pnpm version updates",
|
||||
@@ -42,6 +45,11 @@
|
||||
"groupName": "github actions"
|
||||
}
|
||||
],
|
||||
"postUpdateOptions": ["pnpmDedupe", "updateCargoLock"],
|
||||
"ignoreDeps": ["criterion"]
|
||||
"postUpdateOptions": ["pnpmDedupe"],
|
||||
"ignoreDeps": ["criterion"],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"description": "Force update lockfile to track latest commits of git dependencies",
|
||||
"schedule": ["before 5am on monday"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "1.91.0"
|
||||
components = ["rustfmt", "clippy"]
|
||||
@@ -1,4 +1,4 @@
|
||||
max_width = 100
|
||||
max_width = 120
|
||||
hard_tabs = false
|
||||
tab_spaces = 4
|
||||
newline_style = "Auto"
|
||||
@@ -6,7 +6,7 @@ use_small_heuristics = "Default"
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
remove_nested_parens = true
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
merge_derives = true
|
||||
use_try_shorthand = false
|
||||
use_field_init_shorthand = false
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# bump_changelog.sh
|
||||
# - prepend ./Changelog.md to ./docs/Changelog.history.md
|
||||
# - overwrite ./Changelog.md with ./template/Changelog.md
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
CHANGELOG="Changelog.md"
|
||||
HISTORY="docs/Changelog.history.md"
|
||||
TEMPLATE="template/Changelog.md"
|
||||
|
||||
timestamp() { date +"%Y%m%d%H%M%S"; }
|
||||
|
||||
echo "Repo root: $ROOT_DIR"
|
||||
|
||||
if [ ! -f "$CHANGELOG" ]; then
|
||||
echo "Error: $CHANGELOG not found" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ ! -f "$TEMPLATE" ]; then
|
||||
echo "Error: $TEMPLATE not found" >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
BACKUP_DIR=".changelog_backups"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
bak_ts=$(timestamp)
|
||||
cp "$CHANGELOG" "$BACKUP_DIR/Changelog.md.bak.$bak_ts"
|
||||
echo "Backed up $CHANGELOG -> $BACKUP_DIR/Changelog.md.bak.$bak_ts"
|
||||
|
||||
if [ -f "$HISTORY" ]; then
|
||||
cp "$HISTORY" "$BACKUP_DIR/Changelog.history.md.bak.$bak_ts"
|
||||
echo "Backed up $HISTORY -> $BACKUP_DIR/Changelog.history.md.bak.$bak_ts"
|
||||
fi
|
||||
|
||||
# Prepend current Changelog.md content to top of docs/Changelog.history.md
|
||||
tmpfile=$(mktemp)
|
||||
{
|
||||
cat "$CHANGELOG"
|
||||
echo
|
||||
echo ""
|
||||
if [ -f "$HISTORY" ]; then
|
||||
cat "$HISTORY"
|
||||
fi
|
||||
} > "$tmpfile"
|
||||
|
||||
mv "$tmpfile" "$HISTORY"
|
||||
echo "Prepended $CHANGELOG -> $HISTORY"
|
||||
|
||||
# Overwrite Changelog.md with template
|
||||
cp "$TEMPLATE" "$CHANGELOG"
|
||||
echo "Overwrote $CHANGELOG with $TEMPLATE"
|
||||
|
||||
echo "Done. Backups saved under $BACKUP_DIR"
|
||||
|
||||
exit 0
|
||||
@@ -1,102 +0,0 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const LOCALES_DIR = path.resolve(__dirname, "../src/locales");
|
||||
const SRC_DIRS = [
|
||||
path.resolve(__dirname, "../src"),
|
||||
path.resolve(__dirname, "../src-tauri"),
|
||||
];
|
||||
const exts = [".js", ".ts", ".tsx", ".jsx", ".vue", ".rs"];
|
||||
|
||||
// 递归获取所有文件
|
||||
function getAllFiles(dir, exts) {
|
||||
let files = [];
|
||||
fs.readdirSync(dir).forEach((file) => {
|
||||
const full = path.join(dir, file);
|
||||
if (fs.statSync(full).isDirectory()) {
|
||||
files = files.concat(getAllFiles(full, exts));
|
||||
} else if (exts.includes(path.extname(full))) {
|
||||
files.push(full);
|
||||
}
|
||||
});
|
||||
return files;
|
||||
}
|
||||
|
||||
// 读取所有源码内容为一个大字符串
|
||||
function getAllSourceContent() {
|
||||
const files = SRC_DIRS.flatMap((dir) => getAllFiles(dir, exts));
|
||||
return files.map((f) => fs.readFileSync(f, "utf8")).join("\n");
|
||||
}
|
||||
|
||||
// 白名单 key,不检查这些 key 是否被使用
|
||||
const WHITELIST_KEYS = [
|
||||
"theme.light",
|
||||
"theme.dark",
|
||||
"theme.system",
|
||||
"Already Using Latest Core Version",
|
||||
];
|
||||
|
||||
// 主流程
|
||||
function processI18nFile(i18nPath, lang, allSource) {
|
||||
const i18n = JSON.parse(fs.readFileSync(i18nPath, "utf8"));
|
||||
const keys = Object.keys(i18n);
|
||||
|
||||
const used = {};
|
||||
const unused = [];
|
||||
|
||||
let checked = 0;
|
||||
const total = keys.length;
|
||||
keys.forEach((key) => {
|
||||
if (WHITELIST_KEYS.includes(key)) {
|
||||
used[key] = i18n[key];
|
||||
} else {
|
||||
// 只查找一次
|
||||
const regex = new RegExp(`["'\`]${key}["'\`]`);
|
||||
if (regex.test(allSource)) {
|
||||
used[key] = i18n[key];
|
||||
} else {
|
||||
unused.push(key);
|
||||
}
|
||||
}
|
||||
checked++;
|
||||
if (checked % 20 === 0 || checked === total) {
|
||||
const percent = ((checked / total) * 100).toFixed(1);
|
||||
process.stdout.write(
|
||||
`\r[${lang}] Progress: ${checked}/${total} (${percent}%)`,
|
||||
);
|
||||
if (checked === total) process.stdout.write("\n");
|
||||
}
|
||||
});
|
||||
|
||||
// 输出未使用的 key
|
||||
console.log(`\n[${lang}] Unused keys:`, unused);
|
||||
|
||||
// 备份原文件
|
||||
const oldPath = i18nPath + ".old";
|
||||
fs.renameSync(i18nPath, oldPath);
|
||||
|
||||
// 写入精简后的 i18n 文件(保留原文件名)
|
||||
fs.writeFileSync(i18nPath, JSON.stringify(used, null, 2), "utf8");
|
||||
console.log(
|
||||
`[${lang}] Cleaned i18n file written to src/locales/${path.basename(i18nPath)}`,
|
||||
);
|
||||
console.log(`[${lang}] Original file backed up as ${path.basename(oldPath)}`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
// 支持 zhtw.json、zh-tw.json、zh_CN.json 等
|
||||
const files = fs
|
||||
.readdirSync(LOCALES_DIR)
|
||||
.filter((f) => /^[a-z0-9\-_]+\.json$/i.test(f) && !f.endsWith(".old"));
|
||||
const allSource = getAllSourceContent();
|
||||
files.forEach((file) => {
|
||||
const lang = path.basename(file, ".json");
|
||||
processI18nFile(path.join(LOCALES_DIR, file), lang, allSource);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
File diff suppressed because it is too large
Load Diff
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# extract_update_logs.sh
|
||||
# 从 Changelog.md 提取最新版本 (## v...) 的更新内容
|
||||
# 并输出到屏幕或写入环境变量文件(如 GitHub Actions)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CHANGELOG_FILE="Changelog.md"
|
||||
|
||||
if [[ ! -f "$CHANGELOG_FILE" ]]; then
|
||||
echo "❌ 文件不存在: $CHANGELOG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 提取从第一个 '## v' 开始到下一个 '## v' 前的内容
|
||||
UPDATE_LOGS=$(awk '
|
||||
/^## v/ {
|
||||
if (found) exit;
|
||||
found=1
|
||||
}
|
||||
found
|
||||
' "$CHANGELOG_FILE")
|
||||
|
||||
if [[ -z "$UPDATE_LOGS" ]]; then
|
||||
echo "⚠️ 未找到更新日志内容"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "✅ 提取到的最新版本日志内容如下:"
|
||||
echo "----------------------------------------"
|
||||
echo "$UPDATE_LOGS"
|
||||
echo "----------------------------------------"
|
||||
|
||||
# 如果在 GitHub Actions 环境中(GITHUB_ENV 已定义)
|
||||
if [[ -n "${GITHUB_ENV:-}" ]]; then
|
||||
{
|
||||
echo "UPDATE_LOGS<<EOF"
|
||||
echo "$UPDATE_LOGS"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_ENV"
|
||||
echo "✅ 已写入 GitHub 环境变量 UPDATE_LOGS"
|
||||
fi
|
||||
@@ -1,7 +1,7 @@
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { promisify } from "util";
|
||||
|
||||
/**
|
||||
* 为Alpha版本重命名版本号
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env node
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const ROOT_DIR = path.resolve(__dirname, "..");
|
||||
const LOCALE_DIR = path.resolve(ROOT_DIR, "src/locales/en");
|
||||
const KEY_OUTPUT = path.resolve(ROOT_DIR, "src/types/generated/i18n-keys.ts");
|
||||
const RESOURCE_OUTPUT = path.resolve(
|
||||
ROOT_DIR,
|
||||
"src/types/generated/i18n-resources.ts",
|
||||
);
|
||||
|
||||
const isPlainObject = (value) =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const flattenKeys = (data, prefix = "") => {
|
||||
const keys = [];
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
const nextPrefix = prefix ? `${prefix}.${key}` : key;
|
||||
if (isPlainObject(value)) {
|
||||
keys.push(...flattenKeys(value, nextPrefix));
|
||||
} else {
|
||||
keys.push(nextPrefix);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
|
||||
const buildType = (data, indent = 0) => {
|
||||
if (!isPlainObject(data)) {
|
||||
return "string";
|
||||
}
|
||||
|
||||
const entries = Object.entries(data).sort(([a], [b]) => a.localeCompare(b));
|
||||
const pad = " ".repeat(indent);
|
||||
const inner = entries
|
||||
.map(([key, value]) => {
|
||||
const typeStr = buildType(value, indent + 2);
|
||||
return `${" ".repeat(indent + 2)}${JSON.stringify(key)}: ${typeStr};`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return entries.length
|
||||
? `{
|
||||
${inner}
|
||||
${pad}}`
|
||||
: "{}";
|
||||
};
|
||||
|
||||
const loadNamespaceJson = async () => {
|
||||
const dirents = await fs.readdir(LOCALE_DIR, { withFileTypes: true });
|
||||
const namespaces = [];
|
||||
for (const dirent of dirents) {
|
||||
if (!dirent.isFile() || !dirent.name.endsWith(".json")) continue;
|
||||
const name = dirent.name.replace(/\.json$/, "");
|
||||
const filePath = path.join(LOCALE_DIR, dirent.name);
|
||||
const raw = await fs.readFile(filePath, "utf8");
|
||||
const json = JSON.parse(raw);
|
||||
namespaces.push({ name, json });
|
||||
}
|
||||
namespaces.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return namespaces;
|
||||
};
|
||||
|
||||
const buildKeysFile = (keys) => {
|
||||
const arrayLiteral = keys.map((key) => ` "${key}"`).join(",\n");
|
||||
return `// This file is auto-generated by scripts/generate-i18n-keys.mjs\n// Do not edit this file manually.\n\nexport const translationKeys = [\n${arrayLiteral}\n] as const;\n\nexport type TranslationKey = typeof translationKeys[number];\n`;
|
||||
};
|
||||
|
||||
const buildResourcesFile = (namespaces) => {
|
||||
const namespaceEntries = namespaces
|
||||
.map(({ name, json }) => {
|
||||
const typeStr = buildType(json, 4);
|
||||
return ` ${JSON.stringify(name)}: ${typeStr};`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return `// This file is auto-generated by scripts/generate-i18n-keys.mjs\n// Do not edit this file manually.\n\nexport interface TranslationResources {\n translation: {\n${namespaceEntries}\n };\n}\n`;
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
const namespaces = await loadNamespaceJson();
|
||||
const keys = namespaces.flatMap(({ name, json }) => flattenKeys(json, name));
|
||||
const keysContent = buildKeysFile(keys);
|
||||
const resourcesContent = buildResourcesFile(namespaces);
|
||||
await fs.mkdir(path.dirname(KEY_OUTPUT), { recursive: true });
|
||||
await fs.writeFile(KEY_OUTPUT, keysContent, "utf8");
|
||||
await fs.writeFile(RESOURCE_OUTPUT, resourcesContent, "utf8");
|
||||
console.log(`Generated ${keys.length} translation keys.`);
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("Failed to generate i18n metadata:", error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -1,9 +1,10 @@
|
||||
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";
|
||||
import path from "path";
|
||||
|
||||
import { context, getOctokit } from "@actions/github";
|
||||
import AdmZip from "adm-zip";
|
||||
|
||||
const target = process.argv.slice(2)[0];
|
||||
const alpha = process.argv.slice(2)[1];
|
||||
@@ -49,9 +50,9 @@ async function resolvePortable() {
|
||||
zip.addLocalFolder(
|
||||
path.join(
|
||||
releaseDir,
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
|
||||
`Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${arch}`,
|
||||
),
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
|
||||
`Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${arch}`,
|
||||
);
|
||||
zip.addLocalFolder(configDir, ".config");
|
||||
|
||||
@@ -79,11 +80,11 @@ async function resolvePortable() {
|
||||
tag,
|
||||
});
|
||||
|
||||
let assets = release.assets.filter((x) => {
|
||||
const assets = release.assets.filter((x) => {
|
||||
return x.name === zipFile;
|
||||
});
|
||||
if (assets.length > 0) {
|
||||
let id = assets[0].id;
|
||||
const id = assets[0].id;
|
||||
await github.rest.repos.deleteReleaseAsset({
|
||||
...options,
|
||||
asset_id: id,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import AdmZip from "adm-zip";
|
||||
import { createRequire } from "module";
|
||||
import fsp from "fs/promises";
|
||||
import { createRequire } from "module";
|
||||
import path from "path";
|
||||
|
||||
import AdmZip from "adm-zip";
|
||||
|
||||
const target = process.argv.slice(2)[0];
|
||||
const ARCH_MAP = {
|
||||
|
||||
+32
-59
@@ -1,14 +1,16 @@
|
||||
import AdmZip from "adm-zip";
|
||||
import { execSync } from "child_process";
|
||||
import { createHash } from "crypto";
|
||||
import fs from "fs";
|
||||
import fsp from "fs/promises";
|
||||
import path from "path";
|
||||
import zlib from "zlib";
|
||||
|
||||
import AdmZip from "adm-zip";
|
||||
import { glob } from "glob";
|
||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||
import fetch from "node-fetch";
|
||||
import path from "path";
|
||||
import { extract } from "tar";
|
||||
import zlib from "zlib";
|
||||
|
||||
import { log_debug, log_error, log_info, log_success } from "./utils.mjs";
|
||||
|
||||
/**
|
||||
@@ -55,7 +57,7 @@ const ARCH_MAP = {
|
||||
|
||||
const arg1 = process.argv.slice(2)[0];
|
||||
const arg2 = process.argv.slice(2)[1];
|
||||
let target = arg1 === "--force" || arg1 === "-f" ? arg2 : arg1;
|
||||
const target = arg1 === "--force" || arg1 === "-f" ? arg2 : arg1;
|
||||
const { platform, arch } = target
|
||||
? { platform: PLATFORM_MAP[target], arch: ARCH_MAP[target] }
|
||||
: process;
|
||||
@@ -66,6 +68,11 @@ const SIDECAR_HOST = target
|
||||
.toString()
|
||||
.match(/(?<=host: ).+(?=\s*)/g)[0];
|
||||
|
||||
const RESOURCES_DIR = path.join(cwd, "src-tauri", "resources");
|
||||
const SIDECAR_DIR = path.join(cwd, "src-tauri", "sidecar");
|
||||
// Linux service binaries are bundled as externalBin sidecars (see tauri.linux.conf.json)
|
||||
const SERVICE_DIR = platform === "linux" ? SIDECAR_DIR : RESOURCES_DIR;
|
||||
|
||||
// =======================
|
||||
// Version Cache
|
||||
// =======================
|
||||
@@ -113,7 +120,7 @@ async function calculateFileHash(filePath) {
|
||||
const hashSum = createHash("sha256");
|
||||
hashSum.update(fileBuffer);
|
||||
return hashSum.digest("hex");
|
||||
} catch (err) {
|
||||
} catch (ignoreErr) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -367,9 +374,8 @@ async function downloadFile(url, outPath) {
|
||||
// =======================
|
||||
async function resolveSidecar(binInfo) {
|
||||
const { name, targetFile, zipFile, exeFile, downloadURL } = binInfo;
|
||||
const sidecarDir = path.join(cwd, "src-tauri", "sidecar");
|
||||
const sidecarPath = path.join(sidecarDir, targetFile);
|
||||
await fsp.mkdir(sidecarDir, { recursive: true });
|
||||
const sidecarPath = path.join(SIDECAR_DIR, targetFile);
|
||||
await fsp.mkdir(SIDECAR_DIR, { recursive: true });
|
||||
|
||||
if (!FORCE && fs.existsSync(sidecarPath)) {
|
||||
log_success(`"${name}" already exists, skipping download`);
|
||||
@@ -458,9 +464,9 @@ async function resolveSidecar(binInfo) {
|
||||
}
|
||||
|
||||
async function resolveResource(binInfo) {
|
||||
const { file, downloadURL, localPath } = binInfo;
|
||||
const resDir = path.join(cwd, "src-tauri/resources");
|
||||
const targetPath = path.join(resDir, file);
|
||||
const { file, downloadURL, localPath, dir } = binInfo;
|
||||
const baseDir = dir ?? RESOURCES_DIR;
|
||||
const targetPath = path.join(baseDir, file);
|
||||
|
||||
if (!FORCE && fs.existsSync(targetPath) && !downloadURL && !localPath) {
|
||||
log_success(`"${file}" already exists, skipping`);
|
||||
@@ -472,7 +478,7 @@ async function resolveResource(binInfo) {
|
||||
log_success(`"${file}" already exists, skipping download`);
|
||||
return;
|
||||
}
|
||||
await fsp.mkdir(resDir, { recursive: true });
|
||||
await fsp.mkdir(baseDir, { recursive: true });
|
||||
await downloadFile(downloadURL, targetPath);
|
||||
await updateHashCache(targetPath);
|
||||
}
|
||||
@@ -481,7 +487,7 @@ async function resolveResource(binInfo) {
|
||||
if (!(await hasFileChanged(localPath, targetPath))) {
|
||||
return;
|
||||
}
|
||||
await fsp.mkdir(resDir, { recursive: true });
|
||||
await fsp.mkdir(baseDir, { recursive: true });
|
||||
await fsp.copyFile(localPath, targetPath);
|
||||
await updateHashCache(targetPath);
|
||||
log_success(`Copied file: ${file}`);
|
||||
@@ -543,13 +549,12 @@ const resolveServicePermission = async () => {
|
||||
"clash-verge-service-install*",
|
||||
"clash-verge-service-uninstall*",
|
||||
];
|
||||
const resDir = path.join(cwd, "src-tauri/resources");
|
||||
const hashCache = await loadHashCache();
|
||||
let hasChanges = false;
|
||||
|
||||
for (let f of serviceExecutables) {
|
||||
const files = glob.sync(path.join(resDir, f));
|
||||
for (let filePath of files) {
|
||||
for (const f of serviceExecutables) {
|
||||
const files = glob.sync(path.join(SERVICE_DIR, f));
|
||||
for (const filePath of files) {
|
||||
if (fs.existsSync(filePath)) {
|
||||
const currentHash = await calculateFileHash(filePath);
|
||||
const cacheKey = `${filePath}_chmod`;
|
||||
@@ -573,54 +578,34 @@ const resolveServicePermission = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// resolve locales (从 src/locales 复制到 resources/locales,并使用 hash 检查)
|
||||
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);
|
||||
if (!(await hasFileChanged(srcPath, targetPath))) continue;
|
||||
await fsp.copyFile(srcPath, targetPath);
|
||||
await updateHashCache(targetPath);
|
||||
log_success(`Copied locale file: ${file}`);
|
||||
}
|
||||
log_success("All locale files processed successfully");
|
||||
} catch (err) {
|
||||
log_error("Error copying locale files:", err.message);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// =======================
|
||||
// Other resource resolvers (service, mmdb, geosite, geoip, enableLoopback, sysproxy)
|
||||
// Other resource resolvers (service, mmdb, geosite, geoip, enableLoopback)
|
||||
// =======================
|
||||
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service-ipc/releases/download/${SIDECAR_HOST}`;
|
||||
const resolveService = () => {
|
||||
let ext = platform === "win32" ? ".exe" : "";
|
||||
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
||||
const ext = platform === "win32" ? ".exe" : "";
|
||||
const suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
||||
return resolveResource({
|
||||
file: "clash-verge-service" + suffix + ext,
|
||||
dir: SERVICE_DIR,
|
||||
downloadURL: `${SERVICE_URL}/clash-verge-service${ext}`,
|
||||
});
|
||||
};
|
||||
const resolveInstall = () => {
|
||||
let ext = platform === "win32" ? ".exe" : "";
|
||||
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
||||
const ext = platform === "win32" ? ".exe" : "";
|
||||
const suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
||||
return resolveResource({
|
||||
file: "clash-verge-service-install" + suffix + ext,
|
||||
dir: SERVICE_DIR,
|
||||
downloadURL: `${SERVICE_URL}/clash-verge-service-install${ext}`,
|
||||
});
|
||||
};
|
||||
const resolveUninstall = () => {
|
||||
let ext = platform === "win32" ? ".exe" : "";
|
||||
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
||||
const ext = platform === "win32" ? ".exe" : "";
|
||||
const suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
|
||||
return resolveResource({
|
||||
file: "clash-verge-service-uninstall" + suffix + ext,
|
||||
dir: SERVICE_DIR,
|
||||
downloadURL: `${SERVICE_URL}/clash-verge-service-uninstall${ext}`,
|
||||
});
|
||||
};
|
||||
@@ -645,11 +630,6 @@ const resolveEnableLoopback = () =>
|
||||
file: "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 resolveSetDnsScript = () =>
|
||||
resolveResource({
|
||||
@@ -697,12 +677,6 @@ const tasks = [
|
||||
retry: 5,
|
||||
unixOnly: platform === "linux" || platform === "darwin",
|
||||
},
|
||||
{
|
||||
name: "windows-sysproxy",
|
||||
func: resolveWinSysproxy,
|
||||
retry: 5,
|
||||
winOnly: true,
|
||||
},
|
||||
{
|
||||
name: "set_dns_script",
|
||||
func: resolveSetDnsScript,
|
||||
@@ -715,7 +689,6 @@ const tasks = [
|
||||
retry: 5,
|
||||
macosOnly: true,
|
||||
},
|
||||
{ name: "locales", func: resolveLocales, retry: 2 },
|
||||
];
|
||||
|
||||
async function runTask() {
|
||||
|
||||
@@ -30,10 +30,11 @@
|
||||
*/
|
||||
|
||||
import { execSync } from "child_process";
|
||||
import { program } from "commander";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
import { program } from "commander";
|
||||
|
||||
/**
|
||||
* 获取当前 git 短 commit hash
|
||||
* @returns {string}
|
||||
|
||||
+4
-3
@@ -39,9 +39,10 @@ function is_valid_ip() {
|
||||
|
||||
# 获取网络接口和硬件端口
|
||||
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}}
|
||||
# 从网络服务列表中获取硬件端口
|
||||
hardware_port=$(networksetup -listnetworkserviceorder | awk -v dev="$nic" '
|
||||
/^\([0-9]+\) /{port=$0; sub(/^\([0-9]+\) /, "", port)}
|
||||
/\(Hardware Port:/{interface=$NF;sub(/\)/, "", interface); if (interface == dev) {print port; exit}}
|
||||
')
|
||||
|
||||
# 获取当前DNS设置
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import axios from "axios";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
import axios from "axios";
|
||||
|
||||
import { log_error, log_info, log_success } from "./utils.mjs";
|
||||
|
||||
const CHAT_ID_RELEASE = "@clash_verge_re"; // 正式发布频道
|
||||
@@ -88,7 +90,8 @@ async function sendTelegramNotification() {
|
||||
|
||||
const releaseTitle = isAutobuild ? "滚动更新版发布" : "正式发布";
|
||||
const encodedVersion = encodeURIComponent(version);
|
||||
const content = `<b>🎉 <a href="https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild">Clash Verge Rev v${version}</a> ${releaseTitle}</b>\n\n${formattedContent}`;
|
||||
const releaseTag = isAutobuild ? "autobuild" : `v${version}`;
|
||||
const content = `<b>🎉 <a href="https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/${releaseTag}">Clash Verge Rev v${version}</a> ${releaseTitle}</b>\n\n${formattedContent}`;
|
||||
|
||||
// 发送到 Telegram
|
||||
try {
|
||||
|
||||
+3
-10
@@ -1,16 +1,9 @@
|
||||
#!/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
|
||||
}
|
||||
}
|
||||
hardware_port=$(networksetup -listnetworkserviceorder | awk -v dev="$nic" '
|
||||
/^\([0-9]+\) /{port=$0; sub(/^\([0-9]+\) /, "", port)}
|
||||
/\(Hardware Port:/{interface=$NF;sub(/\)/, "", interface); if (interface == dev) {print port; exit}}
|
||||
')
|
||||
|
||||
if [ -f .original_dns.txt ]; then
|
||||
|
||||
@@ -2,9 +2,9 @@ import fs from "fs";
|
||||
import fsp from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
const UPDATE_LOG = "UPDATELOG.md";
|
||||
const UPDATE_LOG = "Changelog.md";
|
||||
|
||||
// parse the UPDATELOG.md
|
||||
// parse the Changelog.md
|
||||
export async function resolveUpdateLog(tag) {
|
||||
const cwd = process.cwd();
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function resolveUpdateLog(tag) {
|
||||
const file = path.join(cwd, UPDATE_LOG);
|
||||
|
||||
if (!fs.existsSync(file)) {
|
||||
throw new Error("could not found UPDATELOG.md");
|
||||
throw new Error("could not found Changelog.md");
|
||||
}
|
||||
|
||||
const data = await fsp.readFile(file, "utf-8");
|
||||
@@ -38,7 +38,7 @@ export async function resolveUpdateLog(tag) {
|
||||
});
|
||||
|
||||
if (!map[tag]) {
|
||||
throw new Error(`could not found "${tag}" in UPDATELOG.md`);
|
||||
throw new Error(`could not found "${tag}" in Changelog.md`);
|
||||
}
|
||||
|
||||
return map[tag].join("\n").trim();
|
||||
@@ -49,7 +49,7 @@ export async function resolveUpdateLogDefault() {
|
||||
const file = path.join(cwd, UPDATE_LOG);
|
||||
|
||||
if (!fs.existsSync(file)) {
|
||||
throw new Error("could not found UPDATELOG.md");
|
||||
throw new Error("could not found Changelog.md");
|
||||
}
|
||||
|
||||
const data = await fsp.readFile(file, "utf-8");
|
||||
@@ -58,7 +58,7 @@ export async function resolveUpdateLogDefault() {
|
||||
const reEnd = /^---/;
|
||||
|
||||
let isCapturing = false;
|
||||
let content = [];
|
||||
const content = [];
|
||||
let firstTag = "";
|
||||
|
||||
for (const line of data.split("\n")) {
|
||||
@@ -77,7 +77,7 @@ export async function resolveUpdateLogDefault() {
|
||||
}
|
||||
|
||||
if (!firstTag) {
|
||||
throw new Error("could not found any version tag in UPDATELOG.md");
|
||||
throw new Error("could not found any version tag in Changelog.md");
|
||||
}
|
||||
|
||||
return content.join("\n").trim();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { context, getOctokit } from "@actions/github";
|
||||
import fetch from "node-fetch";
|
||||
import { getOctokit, context } from "@actions/github";
|
||||
|
||||
import { resolveUpdateLog } from "./updatelog.mjs";
|
||||
|
||||
const UPDATE_TAG_NAME = "updater";
|
||||
@@ -35,7 +36,7 @@ async function resolveUpdater() {
|
||||
|
||||
const updateData = {
|
||||
name: tag.name,
|
||||
notes: await resolveUpdateLog(tag.name), // use updatelog.md
|
||||
notes: await resolveUpdateLog(tag.name), // use Changelog.md
|
||||
pub_date: new Date().toISOString(),
|
||||
platforms: {
|
||||
"windows-x86_64": { signature: "", url: "" },
|
||||
@@ -113,7 +114,7 @@ async function resolveUpdater() {
|
||||
});
|
||||
|
||||
// delete the old assets
|
||||
for (let asset of updateRelease.assets) {
|
||||
for (const asset of updateRelease.assets) {
|
||||
if (asset.name === UPDATE_JSON_FILE) {
|
||||
await github.rest.repos.deleteReleaseAsset({
|
||||
...options,
|
||||
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
import fetch from "node-fetch";
|
||||
import { getOctokit, context } from "@actions/github";
|
||||
import fetch from "node-fetch";
|
||||
|
||||
import { resolveUpdateLog, resolveUpdateLogDefault } from "./updatelog.mjs";
|
||||
|
||||
// Add stable update JSON filenames
|
||||
@@ -259,7 +260,7 @@ async function processRelease(github, options, tag, isAlpha) {
|
||||
const proxyFile = isAlpha ? ALPHA_UPDATE_JSON_PROXY : UPDATE_JSON_PROXY;
|
||||
|
||||
// Delete existing assets with these names
|
||||
for (let asset of updateRelease.assets) {
|
||||
for (const asset of updateRelease.assets) {
|
||||
if (asset.name === jsonFile) {
|
||||
await github.rest.repos.deleteReleaseAsset({
|
||||
...options,
|
||||
|
||||
+98
-219
@@ -1,124 +1,18 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "2.4.3"
|
||||
version = "2.4.7"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "Tunglies", "wonfen", "MystiPanda"]
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/clash-verge-rev/clash-verge-rev.git"
|
||||
default-run = "clash-verge"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[package.metadata.bundle]
|
||||
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.5.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
warp = { version = "0.4.2", features = ["server"] }
|
||||
anyhow = "1.0.100"
|
||||
open = "5.3.2"
|
||||
log = "0.4.28"
|
||||
dunce = "1.0.5"
|
||||
nanoid = "0.4"
|
||||
chrono = "0.4.42"
|
||||
sysinfo = { version = "0.37.2", features = ["network", "system"] }
|
||||
boa_engine = "0.21.0"
|
||||
serde_json = "1.0.145"
|
||||
serde_yaml_ng = "0.10.0"
|
||||
once_cell = "1.21.3"
|
||||
port_scanner = "0.1.5"
|
||||
delay_timer = "0.11.6"
|
||||
parking_lot = "0.12.5"
|
||||
percent-encoding = "2.3.2"
|
||||
tokio = { version = "1.48.0", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
"time",
|
||||
"sync",
|
||||
] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
reqwest = { version = "0.12.24", features = ["json", "cookies"] }
|
||||
regex = "1.12.2"
|
||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" }
|
||||
tauri = { version = "2.9.2", features = [
|
||||
"protocol-asset",
|
||||
"devtools",
|
||||
"tray-icon",
|
||||
"image-ico",
|
||||
"image-png",
|
||||
] }
|
||||
network-interface = { version = "2.0.3", features = ["serde"] }
|
||||
tauri-plugin-shell = "2.3.3"
|
||||
tauri-plugin-dialog = "2.4.2"
|
||||
tauri-plugin-fs = "2.4.4"
|
||||
tauri-plugin-process = "2.3.1"
|
||||
tauri-plugin-clipboard-manager = "2.3.2"
|
||||
tauri-plugin-deep-link = "2.4.5"
|
||||
tauri-plugin-window-state = "2.4.1"
|
||||
zip = "6.0.0"
|
||||
reqwest_dav = "0.2.2"
|
||||
aes-gcm = { version = "0.10.3", features = ["std"] }
|
||||
base64 = "0.22.1"
|
||||
getrandom = "0.3.4"
|
||||
futures = "0.3.31"
|
||||
sys-locale = "0.3.2"
|
||||
libc = "0.2.177"
|
||||
gethostname = "1.1.0"
|
||||
scopeguard = "1.2.0"
|
||||
tauri-plugin-notification = "2.3.3"
|
||||
tokio-stream = "0.1.17"
|
||||
isahc = { version = "1.7.2", default-features = false, features = [
|
||||
"text-decoding",
|
||||
"parking_lot",
|
||||
] }
|
||||
backoff = { version = "0.4.0", features = ["tokio"] }
|
||||
compact_str = { version = "0.9.0", features = ["serde"] }
|
||||
tauri-plugin-http = "2.5.4"
|
||||
flexi_logger = "0.31.7"
|
||||
console-subscriber = { version = "0.5.0", optional = true }
|
||||
tauri-plugin-devtools = { version = "2.0.1" }
|
||||
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo" }
|
||||
clash_verge_logger = { git = "https://github.com/clash-verge-rev/clash-verge-logger" }
|
||||
async-trait = "0.1.89"
|
||||
smartstring = { version = "1.0.1", features = ["serde"] }
|
||||
clash_verge_service_ipc = { version = "2.0.21", features = [
|
||||
"client",
|
||||
], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" }
|
||||
arc-swap = "1.7.1"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
runas = "=1.2.0"
|
||||
deelevate = "0.2.0"
|
||||
winreg = "0.55.0"
|
||||
winapi = { version = "0.3.9", features = [
|
||||
"winbase",
|
||||
"fileapi",
|
||||
"winnt",
|
||||
"handleapi",
|
||||
"errhandlingapi",
|
||||
"minwindef",
|
||||
"winerror",
|
||||
"tlhelp32",
|
||||
"processthreadsapi",
|
||||
"winhttp",
|
||||
"winreg",
|
||||
] }
|
||||
windows-sys = { version = "0.61.2", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
signal-hook = "0.3.18"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-autostart = "2.5.1"
|
||||
tauri-plugin-global-shortcut = "2.3.1"
|
||||
tauri-plugin-updater = "2.9.0"
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["custom-protocol"]
|
||||
@@ -129,117 +23,102 @@ tokio-trace = ["console-subscriber"]
|
||||
clippy = ["tauri/test"]
|
||||
tracing = []
|
||||
|
||||
[[bench]]
|
||||
name = "draft_benchmark"
|
||||
path = "benches/draft_benchmark.rs"
|
||||
harness = false
|
||||
[package.metadata.bundle]
|
||||
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
lto = "thin"
|
||||
opt-level = 3
|
||||
debug = false
|
||||
strip = true
|
||||
overflow-checks = false
|
||||
rpath = false
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.5.5", features = [] }
|
||||
|
||||
[profile.dev]
|
||||
incremental = true
|
||||
codegen-units = 64
|
||||
opt-level = 0
|
||||
debug = true
|
||||
strip = "none"
|
||||
overflow-checks = true
|
||||
lto = false
|
||||
rpath = false
|
||||
[dependencies]
|
||||
clash-verge-draft = { workspace = true }
|
||||
clash-verge-logging = { workspace = true }
|
||||
clash-verge-signal = { workspace = true }
|
||||
clash-verge-i18n = { workspace = true }
|
||||
clash-verge-limiter = { workspace = true }
|
||||
tauri-plugin-clash-verge-sysinfo = { workspace = true }
|
||||
tauri-plugin-clipboard-manager = { workspace = true }
|
||||
tauri = { workspace = true, features = [
|
||||
"protocol-asset",
|
||||
"devtools",
|
||||
"tray-icon",
|
||||
"image-ico",
|
||||
"image-png",
|
||||
] }
|
||||
parking_lot = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
compact_str = { workspace = true }
|
||||
flexi_logger = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml_ng = { workspace = true }
|
||||
smartstring = { workspace = true, features = ["serde"] }
|
||||
bitflags = { workspace = true }
|
||||
warp = { version = "0.4.2", features = ["server"] }
|
||||
open = "5.3.3"
|
||||
dunce = "1.0.5"
|
||||
nanoid = "0.4"
|
||||
chrono = "0.4.43"
|
||||
boa_engine = "0.21.0"
|
||||
once_cell = { version = "1.21.3", features = ["parking_lot"] }
|
||||
delay_timer = "0.11.6"
|
||||
percent-encoding = "2.3.2"
|
||||
reqwest = { version = "0.13.2", features = [
|
||||
"json",
|
||||
"cookies",
|
||||
"rustls",
|
||||
"form",
|
||||
] }
|
||||
regex = "1.12.3"
|
||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", branch = "0.4.5", features = [
|
||||
"guard",
|
||||
] }
|
||||
network-interface = { version = "2.0.5", features = ["serde"] }
|
||||
tauri-plugin-shell = "2.3.5"
|
||||
tauri-plugin-dialog = "2.6.0"
|
||||
tauri-plugin-fs = "2.4.5"
|
||||
tauri-plugin-process = "2.3.1"
|
||||
tauri-plugin-deep-link = "2.4.7"
|
||||
tauri-plugin-window-state = "2.4.1"
|
||||
zip = "8.1.0"
|
||||
reqwest_dav = "0.3.2"
|
||||
aes-gcm = { version = "0.10.3", features = ["std"] }
|
||||
base64 = "0.22.1"
|
||||
getrandom = "0.4.1"
|
||||
futures = "0.3.32"
|
||||
gethostname = "1.1.0"
|
||||
scopeguard = "1.2.0"
|
||||
tauri-plugin-notification = "2.3.3"
|
||||
tokio-stream = "0.1.18"
|
||||
backoff = { version = "0.4.0", features = ["tokio"] }
|
||||
tauri-plugin-http = "2.5.7"
|
||||
console-subscriber = { version = "0.5.0", optional = true }
|
||||
tauri-plugin-devtools = { version = "2.0.1" }
|
||||
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo"}
|
||||
clash_verge_logger = { git = "https://github.com/clash-verge-rev/clash-verge-logger" }
|
||||
async-trait = "0.1.89"
|
||||
clash_verge_service_ipc = { version = "2.1.3", features = [
|
||||
"client",
|
||||
], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" }
|
||||
arc-swap = "1.8.2"
|
||||
rust_iso3166 = "0.1.14"
|
||||
# Use the git repo until the next release after v2.0.0.
|
||||
dark-light = { git = "https://github.com/rust-dark-light/dark-light" }
|
||||
|
||||
[profile.fast-release]
|
||||
inherits = "release"
|
||||
codegen-units = 64
|
||||
incremental = true
|
||||
lto = false
|
||||
opt-level = 0
|
||||
debug = true
|
||||
strip = false
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
deelevate = { workspace = true }
|
||||
runas = "=1.2.0"
|
||||
winreg = "0.55.0"
|
||||
windows = { version = "0.62.2", features = ["Win32_Globalization"] }
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-autostart = "2.5.1"
|
||||
tauri-plugin-global-shortcut = "2.3.1"
|
||||
tauri-plugin-updater = "2.10.0"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.7.0", features = ["async_tokio"] }
|
||||
criterion = { workspace = true }
|
||||
|
||||
[lints.clippy]
|
||||
# Core categories - most important for code safety and correctness
|
||||
correctness = { level = "deny", priority = -1 }
|
||||
suspicious = { level = "deny", priority = -1 }
|
||||
|
||||
# Critical safety lints - warn for now due to extensive existing usage
|
||||
unwrap_used = "warn"
|
||||
expect_used = "warn"
|
||||
panic = "deny"
|
||||
unimplemented = "deny"
|
||||
|
||||
# Development quality lints
|
||||
todo = "warn"
|
||||
dbg_macro = "warn"
|
||||
#print_stdout = "warn"
|
||||
#print_stderr = "warn"
|
||||
|
||||
# Performance lints for proxy application
|
||||
clone_on_ref_ptr = "warn"
|
||||
rc_clone_in_vec_init = "warn"
|
||||
large_stack_arrays = "warn"
|
||||
large_const_arrays = "warn"
|
||||
|
||||
# Security lints
|
||||
#integer_division = "warn"
|
||||
#lossy_float_literal = "warn"
|
||||
#default_numeric_fallback = "warn"
|
||||
|
||||
# Mutex and async lints - strict control
|
||||
async_yields_async = "deny" # Prevents missing await in async blocks
|
||||
mutex_atomic = "deny" # Use atomics instead of Mutex<bool/int>
|
||||
mutex_integer = "deny" # Use AtomicInt instead of Mutex<int>
|
||||
rc_mutex = "deny" # Single-threaded Rc with Mutex is wrong
|
||||
unused_async = "deny" # Too many false positives in Tauri/framework code
|
||||
await_holding_lock = "deny"
|
||||
large_futures = "deny"
|
||||
future_not_send = "deny"
|
||||
|
||||
# Common style improvements
|
||||
redundant_else = "deny" # Too many in existing code
|
||||
needless_continue = "deny" # Too many in existing code
|
||||
needless_raw_string_hashes = "deny" # Too many in existing code
|
||||
|
||||
# Disable noisy categories for existing codebase but keep them available
|
||||
#style = { level = "allow", priority = -1 }
|
||||
#complexity = { level = "allow", priority = -1 }
|
||||
#perf = { level = "allow", priority = -1 }
|
||||
#pedantic = { level = "allow", priority = -1 }
|
||||
#nursery = { level = "allow", priority = -1 }
|
||||
#restriction = { level = "allow", priority = -1 }
|
||||
|
||||
or_fun_call = "deny"
|
||||
cognitive_complexity = "deny"
|
||||
useless_let_if_seq = "deny"
|
||||
use_self = "deny"
|
||||
tuple_array_conversions = "deny"
|
||||
trait_duplication_in_bounds = "deny"
|
||||
suspicious_operation_groupings = "deny"
|
||||
string_lit_as_bytes = "deny"
|
||||
significant_drop_tightening = "deny"
|
||||
significant_drop_in_scrutinee = "deny"
|
||||
redundant_clone = "deny"
|
||||
# option_if_let_else = "deny" // 过于激进,暂时不开启
|
||||
needless_pass_by_ref_mut = "deny"
|
||||
needless_collect = "deny"
|
||||
missing_const_for_fn = "deny"
|
||||
iter_with_drain = "deny"
|
||||
iter_on_single_items = "deny"
|
||||
iter_on_empty_collections = "deny"
|
||||
# fallible_impl_from = "deny" // 过于激进,暂时不开启
|
||||
equatable_if_let = "deny"
|
||||
collection_is_never_read = "deny"
|
||||
branches_sharing_code = "deny"
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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>AssociatedBundleIdentifiers</key>
|
||||
<array>
|
||||
<string>io.github.clash-verge-rev.clash-verge-rev.service</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
+25
-50
@@ -1,18 +1,17 @@
|
||||
use super::CmdResult;
|
||||
use crate::core::sysopt::Sysopt;
|
||||
use crate::utils::resolve::ui::{self, UiReadyStage};
|
||||
use crate::{
|
||||
cmd::StringifyErr,
|
||||
feat, logging,
|
||||
utils::{
|
||||
dirs::{self, PathBufExec},
|
||||
logging::Type,
|
||||
},
|
||||
cmd::StringifyErr as _,
|
||||
feat,
|
||||
utils::dirs::{self, PathBufExec as _},
|
||||
};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use smartstring::alias::String;
|
||||
use std::path::Path;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri::{AppHandle, Manager as _};
|
||||
use tokio::fs;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::AsyncWriteExt as _;
|
||||
|
||||
/// 打开应用程序所在目录
|
||||
#[tauri::command]
|
||||
@@ -46,14 +45,20 @@ pub fn open_web_url(url: String) -> CmdResult<()> {
|
||||
/// 打开 Verge 最新日志
|
||||
#[tauri::command]
|
||||
pub async fn open_app_log() -> CmdResult<()> {
|
||||
open::that(dirs::app_latest_log().stringify_err()?).stringify_err()
|
||||
let log_path = dirs::app_latest_log().stringify_err()?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let log_path = crate::utils::help::snapshot_path(&log_path).stringify_err()?;
|
||||
open::that(log_path).stringify_err()
|
||||
}
|
||||
|
||||
// TODO 后续可以为前端提供接口,当前作为托盘菜单使用
|
||||
/// 打开 Clash 最新日志
|
||||
#[tauri::command]
|
||||
pub async fn open_core_log() -> CmdResult<()> {
|
||||
open::that(dirs::clash_latest_log().stringify_err()?).stringify_err()
|
||||
let log_path = dirs::clash_latest_log().stringify_err()?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let log_path = crate::utils::help::snapshot_path(&log_path).stringify_err()?;
|
||||
open::that(log_path).stringify_err()
|
||||
}
|
||||
|
||||
/// 打开/关闭开发者工具
|
||||
@@ -83,17 +88,14 @@ pub async fn restart_app() -> CmdResult<()> {
|
||||
|
||||
/// 获取便携版标识
|
||||
#[tauri::command]
|
||||
pub fn get_portable_flag() -> CmdResult<bool> {
|
||||
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
|
||||
pub fn get_portable_flag() -> bool {
|
||||
*dirs::PORTABLE_FLAG.get().unwrap_or(&false)
|
||||
}
|
||||
|
||||
/// 获取应用目录
|
||||
#[tauri::command]
|
||||
pub fn get_app_dir() -> CmdResult<String> {
|
||||
let app_home_dir = dirs::app_home_dir()
|
||||
.stringify_err()?
|
||||
.to_string_lossy()
|
||||
.into();
|
||||
let app_home_dir = dirs::app_home_dir().stringify_err()?.to_string_lossy().into();
|
||||
Ok(app_home_dir)
|
||||
}
|
||||
|
||||
@@ -106,10 +108,7 @@ pub fn get_auto_launch_status() -> CmdResult<bool> {
|
||||
/// 下载图标缓存
|
||||
#[tauri::command]
|
||||
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
|
||||
let icon_cache_dir = dirs::app_home_dir()
|
||||
.stringify_err()?
|
||||
.join("icons")
|
||||
.join("cache");
|
||||
let icon_cache_dir = dirs::app_home_dir().stringify_err()?.join("icons").join("cache");
|
||||
let icon_path = icon_cache_dir.join(name.as_str());
|
||||
|
||||
if icon_path.exists() {
|
||||
@@ -135,9 +134,7 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
|
||||
let content = response.bytes().await.stringify_err()?;
|
||||
|
||||
let is_html = content.len() > 15
|
||||
&& (content.starts_with(b"<!DOCTYPE html")
|
||||
|| content.starts_with(b"<html")
|
||||
|| content.starts_with(b"<?xml"));
|
||||
&& (content.starts_with(b"<!DOCTYPE html") || content.starts_with(b"<html") || content.starts_with(b"<?xml"));
|
||||
|
||||
if is_image && !is_html {
|
||||
{
|
||||
@@ -240,36 +237,14 @@ pub async fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<Stri
|
||||
|
||||
/// 通知UI已准备就绪
|
||||
#[tauri::command]
|
||||
pub fn notify_ui_ready() -> CmdResult<()> {
|
||||
pub fn notify_ui_ready() {
|
||||
logging!(info, Type::Cmd, "前端UI已准备就绪");
|
||||
crate::utils::resolve::ui::mark_ui_ready();
|
||||
Ok(())
|
||||
ui::mark_ui_ready();
|
||||
}
|
||||
|
||||
/// UI加载阶段
|
||||
#[tauri::command]
|
||||
pub fn update_ui_stage(stage: String) -> CmdResult<()> {
|
||||
logging!(info, Type::Cmd, "UI加载阶段更新: {}", stage.as_str());
|
||||
|
||||
use crate::utils::resolve::ui::UiReadyStage;
|
||||
|
||||
let stage_enum = match stage.as_str() {
|
||||
"NotStarted" => UiReadyStage::NotStarted,
|
||||
"Loading" => UiReadyStage::Loading,
|
||||
"DomReady" => UiReadyStage::DomReady,
|
||||
"ResourcesLoaded" => UiReadyStage::ResourcesLoaded,
|
||||
"Ready" => UiReadyStage::Ready,
|
||||
_ => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Cmd,
|
||||
"Warning: 未知的UI加载阶段: {}",
|
||||
stage.as_str()
|
||||
);
|
||||
return Err(format!("未知的UI加载阶段: {}", stage.as_str()).into());
|
||||
}
|
||||
};
|
||||
|
||||
crate::utils::resolve::ui::update_ui_ready_stage(stage_enum);
|
||||
Ok(())
|
||||
pub fn update_ui_stage(stage: UiReadyStage) {
|
||||
logging!(info, Type::Cmd, "UI加载阶段更新: {:?}", &stage);
|
||||
ui::update_ui_ready_stage(stage);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::CmdResult;
|
||||
use crate::{cmd::StringifyErr, feat};
|
||||
use crate::{cmd::StringifyErr as _, feat};
|
||||
use feat::LocalBackupFile;
|
||||
use smartstring::alias::String;
|
||||
|
||||
@@ -27,10 +27,14 @@ pub async fn restore_local_backup(filename: String) -> CmdResult<()> {
|
||||
feat::restore_local_backup(filename).await.stringify_err()
|
||||
}
|
||||
|
||||
/// Import local backup into the app's backup directory
|
||||
#[tauri::command]
|
||||
pub async fn import_local_backup(source: String) -> CmdResult<String> {
|
||||
feat::import_local_backup(source).await.stringify_err()
|
||||
}
|
||||
|
||||
/// Export local backup to a user selected destination
|
||||
#[tauri::command]
|
||||
pub async fn export_local_backup(filename: String, destination: String) -> CmdResult<()> {
|
||||
feat::export_local_backup(filename, destination)
|
||||
.await
|
||||
.stringify_err()
|
||||
feat::export_local_backup(filename, destination).await.stringify_err()
|
||||
}
|
||||
|
||||
+29
-51
@@ -1,12 +1,13 @@
|
||||
use super::CmdResult;
|
||||
use crate::feat;
|
||||
use crate::utils::dirs;
|
||||
use crate::{
|
||||
cmd::StringifyErr,
|
||||
cmd::StringifyErr as _,
|
||||
config::{ClashInfo, Config},
|
||||
constants,
|
||||
core::{CoreManager, handle, validate::CoreConfigValidator},
|
||||
};
|
||||
use crate::{feat, logging, utils::logging::Type};
|
||||
use clash_verge_logging::{Type, logging, logging_error};
|
||||
use compact_str::CompactString;
|
||||
use serde_yaml_ng::Mapping;
|
||||
use smartstring::alias::String;
|
||||
@@ -22,13 +23,13 @@ pub async fn copy_clash_env() -> CmdResult {
|
||||
/// 获取Clash信息
|
||||
#[tauri::command]
|
||||
pub async fn get_clash_info() -> CmdResult<ClashInfo> {
|
||||
Ok(Config::clash().await.latest_arc().get_client_info())
|
||||
Ok(Config::clash().await.data_arc().get_client_info())
|
||||
}
|
||||
|
||||
/// 修改Clash配置
|
||||
#[tauri::command]
|
||||
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
|
||||
feat::patch_clash(payload).await.stringify_err()
|
||||
feat::patch_clash(&payload).await.stringify_err()
|
||||
}
|
||||
|
||||
/// 修改Clash模式
|
||||
@@ -45,21 +46,18 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>>
|
||||
|
||||
match CoreManager::global().change_core(&clash_core).await {
|
||||
Ok(_) => {
|
||||
logging_error!(Type::Core, Config::profiles().await.latest_arc().save_file().await);
|
||||
|
||||
// 切换内核后重启内核
|
||||
match CoreManager::global().restart_core().await {
|
||||
Ok(_) => {
|
||||
logging!(
|
||||
info,
|
||||
Type::Core,
|
||||
"core changed and restarted to {clash_core}"
|
||||
);
|
||||
logging!(info, Type::Core, "core changed and restarted to {clash_core}");
|
||||
handle::Handle::notice_message("config_core::change_success", clash_core);
|
||||
handle::Handle::refresh_clash();
|
||||
Ok(None)
|
||||
}
|
||||
Err(err) => {
|
||||
let error_msg: String =
|
||||
format!("Core changed but failed to restart: {err}").into();
|
||||
let error_msg: String = format!("Core changed but failed to restart: {err}").into();
|
||||
handle::Handle::notice_message("config_core::change_error", error_msg.clone());
|
||||
logging!(error, Type::Core, "{error_msg}");
|
||||
Ok(Some(error_msg))
|
||||
@@ -88,6 +86,7 @@ pub async fn start_core() -> CmdResult {
|
||||
/// 关闭核心
|
||||
#[tauri::command]
|
||||
pub async fn stop_core() -> CmdResult {
|
||||
logging_error!(Type::Core, Config::profiles().await.latest_arc().save_file().await);
|
||||
let result = CoreManager::global().stop_core().await.stringify_err();
|
||||
if result.is_ok() {
|
||||
handle::Handle::refresh_clash();
|
||||
@@ -98,6 +97,7 @@ pub async fn stop_core() -> CmdResult {
|
||||
/// 重启核心
|
||||
#[tauri::command]
|
||||
pub async fn restart_core() -> CmdResult {
|
||||
logging_error!(Type::Core, Config::profiles().await.latest_arc().save_file().await);
|
||||
let result = CoreManager::global().restart_core().await.stringify_err();
|
||||
if result.is_ok() {
|
||||
handle::Handle::refresh_clash();
|
||||
@@ -126,9 +126,7 @@ pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
|
||||
use tokio::fs;
|
||||
|
||||
// 获取DNS配置文件路径
|
||||
let dns_path = dirs::app_home_dir()
|
||||
.stringify_err()?
|
||||
.join(constants::files::DNS_CONFIG);
|
||||
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
|
||||
|
||||
// 保存DNS配置到文件
|
||||
let yaml_str = serde_yaml_ng::to_string(&dns_config).stringify_err()?;
|
||||
@@ -143,9 +141,7 @@ pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
|
||||
pub async fn apply_dns_config(apply: bool) -> CmdResult {
|
||||
if apply {
|
||||
// 读取DNS配置文件
|
||||
let dns_path = dirs::app_home_dir()
|
||||
.stringify_err()?
|
||||
.join(constants::files::DNS_CONFIG);
|
||||
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
|
||||
|
||||
if !dns_path.exists() {
|
||||
logging!(warn, Type::Config, "DNS config file not found");
|
||||
@@ -157,10 +153,9 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
|
||||
})?;
|
||||
|
||||
// 解析DNS配置
|
||||
let patch_config = serde_yaml_ng::from_str::<serde_yaml_ng::Mapping>(&dns_yaml)
|
||||
.stringify_err_log(|e| {
|
||||
logging!(error, Type::Config, "Failed to parse DNS config: {e}");
|
||||
})?;
|
||||
let patch_config = serde_yaml_ng::from_str::<serde_yaml_ng::Mapping>(&dns_yaml).stringify_err_log(|e| {
|
||||
logging!(error, Type::Config, "Failed to parse DNS config: {e}");
|
||||
})?;
|
||||
|
||||
logging!(info, Type::Config, "Applying DNS config from file");
|
||||
|
||||
@@ -170,7 +165,7 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
|
||||
|
||||
// 应用DNS配置到运行时配置
|
||||
Config::runtime().await.edit_draft(|d| {
|
||||
d.patch_config(patch);
|
||||
d.patch_config(&patch);
|
||||
});
|
||||
|
||||
// 重新生成配置
|
||||
@@ -180,35 +175,25 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
|
||||
})?;
|
||||
|
||||
// 应用新配置
|
||||
CoreManager::global()
|
||||
.update_config()
|
||||
.await
|
||||
.stringify_err_log(|err| {
|
||||
let err = format!("Failed to apply config with DNS: {err}");
|
||||
logging!(error, Type::Config, "{err}");
|
||||
})?;
|
||||
CoreManager::global().update_config().await.stringify_err_log(|err| {
|
||||
let err = format!("Failed to apply config with DNS: {err}");
|
||||
logging!(error, Type::Config, "{err}");
|
||||
})?;
|
||||
|
||||
logging!(info, Type::Config, "DNS config successfully applied");
|
||||
} else {
|
||||
// 当关闭DNS设置时,重新生成配置(不加载DNS配置文件)
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
"DNS settings disabled, regenerating config"
|
||||
);
|
||||
logging!(info, Type::Config, "DNS settings disabled, regenerating config");
|
||||
|
||||
Config::generate().await.stringify_err_log(|err| {
|
||||
let err = format!("Failed to regenerate config: {err}");
|
||||
logging!(error, Type::Config, "{err}");
|
||||
})?;
|
||||
|
||||
CoreManager::global()
|
||||
.update_config()
|
||||
.await
|
||||
.stringify_err_log(|err| {
|
||||
let err = format!("Failed to apply regenerated config: {err}");
|
||||
logging!(error, Type::Config, "{err}");
|
||||
})?;
|
||||
CoreManager::global().update_config().await.stringify_err_log(|err| {
|
||||
let err = format!("Failed to apply regenerated config: {err}");
|
||||
logging!(error, Type::Config, "{err}");
|
||||
})?;
|
||||
|
||||
logging!(info, Type::Config, "Config regenerated successfully");
|
||||
}
|
||||
@@ -222,9 +207,7 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
|
||||
pub fn check_dns_config_exists() -> CmdResult<bool> {
|
||||
use crate::utils::dirs;
|
||||
|
||||
let dns_path = dirs::app_home_dir()
|
||||
.stringify_err()?
|
||||
.join(constants::files::DNS_CONFIG);
|
||||
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
|
||||
|
||||
Ok(dns_path.exists())
|
||||
}
|
||||
@@ -235,9 +218,7 @@ pub async fn get_dns_config_content() -> CmdResult<String> {
|
||||
use crate::utils::dirs;
|
||||
use tokio::fs;
|
||||
|
||||
let dns_path = dirs::app_home_dir()
|
||||
.stringify_err()?
|
||||
.join(constants::files::DNS_CONFIG);
|
||||
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
|
||||
|
||||
if !fs::try_exists(&dns_path).await.stringify_err()? {
|
||||
return Err("DNS config file not found".into());
|
||||
@@ -265,9 +246,6 @@ pub async fn validate_dns_config() -> CmdResult<(bool, String)> {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_clash_logs() -> CmdResult<Vec<CompactString>> {
|
||||
let logs = CoreManager::global()
|
||||
.get_clash_logs()
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let logs = CoreManager::global().get_clash_logs().await.unwrap_or_default();
|
||||
Ok(logs)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use regex::Regex;
|
||||
use reqwest::{Client, cookie::Jar};
|
||||
|
||||
use crate::{logging, utils::logging::Type};
|
||||
|
||||
use super::UnlockItem;
|
||||
use super::utils::{country_code_to_emoji, get_local_date_string};
|
||||
|
||||
@@ -12,9 +11,13 @@ pub(super) async fn check_bahamut_anime(client: &Client) -> UnlockItem {
|
||||
let cookie_store = Arc::new(Jar::default());
|
||||
|
||||
let client_with_cookies = match Client::builder()
|
||||
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
|
||||
.use_rustls_tls()
|
||||
.user_agent(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
)
|
||||
.cookie_provider(Arc::clone(&cookie_store))
|
||||
.build() {
|
||||
.build()
|
||||
{
|
||||
Ok(client) => client,
|
||||
Err(e) => {
|
||||
logging!(
|
||||
@@ -59,8 +62,7 @@ pub(super) async fn check_bahamut_anime(client: &Client) -> UnlockItem {
|
||||
};
|
||||
}
|
||||
|
||||
let url =
|
||||
format!("https://ani.gamer.com.tw/ajax/token.php?adID=89422&sn=37783&device={device_id}");
|
||||
let url = format!("https://ani.gamer.com.tw/ajax/token.php?adID=89422&sn=37783&device={device_id}");
|
||||
|
||||
let token_result = match client_with_cookies.get(&url).send().await {
|
||||
Ok(response) => match response.text().await {
|
||||
@@ -85,21 +87,14 @@ pub(super) async fn check_bahamut_anime(client: &Client) -> UnlockItem {
|
||||
};
|
||||
}
|
||||
|
||||
let region = match client_with_cookies
|
||||
.get("https://ani.gamer.com.tw/")
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
let region = match client_with_cookies.get("https://ani.gamer.com.tw/").send().await {
|
||||
Ok(response) => match response.text().await {
|
||||
Ok(body) => match Regex::new(r#"data-geo="([^"]+)"#) {
|
||||
Ok(region_re) => region_re
|
||||
.captures(&body)
|
||||
.and_then(|caps| caps.get(1))
|
||||
.map(|m| {
|
||||
let country_code = m.as_str();
|
||||
let emoji = country_code_to_emoji(country_code);
|
||||
format!("{emoji}{country_code}")
|
||||
}),
|
||||
Ok(region_re) => region_re.captures(&body).and_then(|caps| caps.get(1)).map(|m| {
|
||||
let country_code = m.as_str();
|
||||
let emoji = country_code_to_emoji(country_code);
|
||||
format!("{emoji}{country_code}")
|
||||
}),
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use regex::Regex;
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::{logging, utils::logging::Type};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
|
||||
use super::UnlockItem;
|
||||
use super::utils::{country_code_to_emoji, get_local_date_string};
|
||||
@@ -9,8 +9,7 @@ use super::utils::{country_code_to_emoji, get_local_date_string};
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
|
||||
let device_api_url = "https://disney.api.edge.bamgrid.com/devices";
|
||||
let auth_header =
|
||||
"Bearer ZGlzbmV5JmJyb3dzZXImMS4wLjA.Cu56AgSfBTDag5NiRA81oLHkDZfu5L3CKadnefEAY84";
|
||||
let auth_header = "Bearer ZGlzbmV5JmJyb3dzZXImMS4wLjA.Cu56AgSfBTDag5NiRA81oLHkDZfu5L3CKadnefEAY84";
|
||||
|
||||
let device_req_body = serde_json::json!({
|
||||
"deviceFamily": "browser",
|
||||
@@ -39,12 +38,7 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
|
||||
let device_response = match device_result {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Network,
|
||||
"Failed to get Disney+ device response: {}",
|
||||
e
|
||||
);
|
||||
logging!(error, Type::Network, "Failed to get Disney+ device response: {}", e);
|
||||
return UnlockItem {
|
||||
name: "Disney+".to_string(),
|
||||
status: "Failed (Network Connection)".to_string(),
|
||||
@@ -120,18 +114,12 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
|
||||
}
|
||||
};
|
||||
let token_body = [
|
||||
(
|
||||
"grant_type",
|
||||
"urn:ietf:params:oauth:grant-type:token-exchange",
|
||||
),
|
||||
("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange"),
|
||||
("latitude", "0"),
|
||||
("longitude", "0"),
|
||||
("platform", "browser"),
|
||||
("subject_token", assertion_str.as_str()),
|
||||
(
|
||||
"subject_token_type",
|
||||
"urn:bamtech:params:oauth:token-type:device",
|
||||
),
|
||||
("subject_token_type", "urn:bamtech:params:oauth:token-type:device"),
|
||||
];
|
||||
|
||||
let token_result = client
|
||||
@@ -154,12 +142,7 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
|
||||
let token_response = match token_result {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Network,
|
||||
"Failed to get Disney+ token response: {}",
|
||||
e
|
||||
);
|
||||
logging!(error, Type::Network, "Failed to get Disney+ token response: {}", e);
|
||||
return UnlockItem {
|
||||
name: "Disney+".to_string(),
|
||||
status: "Failed (Network Connection)".to_string(),
|
||||
@@ -264,12 +247,7 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
|
||||
let graphql_response = match graphql_result {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Network,
|
||||
"Failed to get Disney+ GraphQL response: {}",
|
||||
e
|
||||
);
|
||||
logging!(error, Type::Network, "Failed to get Disney+ GraphQL response: {}", e);
|
||||
return UnlockItem {
|
||||
name: "Disney+".to_string(),
|
||||
status: "Failed (Network Connection)".to_string(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use regex::Regex;
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::{logging, utils::logging::Type};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
|
||||
use super::UnlockItem;
|
||||
use super::utils::{country_code_to_emoji, get_local_date_string};
|
||||
@@ -18,12 +18,7 @@ pub(super) async fn check_gemini(client: &Client) -> UnlockItem {
|
||||
let re = match Regex::new(r#",2,1,200,"([A-Z]{3})""#) {
|
||||
Ok(re) => re,
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Network,
|
||||
"Failed to compile Gemini regex: {}",
|
||||
e
|
||||
);
|
||||
logging!(error, Type::Network, "Failed to compile Gemini regex: {}", e);
|
||||
return UnlockItem {
|
||||
name: "Gemini".to_string(),
|
||||
status: "Failed".to_string(),
|
||||
|
||||
@@ -4,7 +4,7 @@ use reqwest::Client;
|
||||
use tauri::command;
|
||||
use tokio::{sync::Mutex, task::JoinSet};
|
||||
|
||||
use crate::{logging, utils::logging::Type};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
|
||||
mod bahamut;
|
||||
mod bilibili;
|
||||
@@ -42,6 +42,7 @@ pub async fn get_unlock_items() -> Result<Vec<UnlockItem>, String> {
|
||||
#[command]
|
||||
pub async fn check_media_unlock() -> Result<Vec<UnlockItem>, String> {
|
||||
let client = match Client::builder()
|
||||
.use_rustls_tls()
|
||||
.user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.danger_accept_invalid_certs(true)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use reqwest::Client;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{logging, utils::logging::Type};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
|
||||
use super::UnlockItem;
|
||||
use super::utils::{country_code_to_emoji, get_local_date_string};
|
||||
@@ -50,12 +50,7 @@ pub(super) async fn check_netflix(client: &Client) -> UnlockItem {
|
||||
let status1 = match result1 {
|
||||
Ok(response) => response.status().as_u16(),
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Network,
|
||||
"Failed to get Netflix response 1: {}",
|
||||
e
|
||||
);
|
||||
logging!(error, Type::Network, "Failed to get Netflix response 1: {}", e);
|
||||
return UnlockItem {
|
||||
name: "Netflix".to_string(),
|
||||
status: "Failed".to_string(),
|
||||
@@ -68,12 +63,7 @@ pub(super) async fn check_netflix(client: &Client) -> UnlockItem {
|
||||
let status2 = match result2 {
|
||||
Ok(response) => response.status().as_u16(),
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Network,
|
||||
"Failed to get Netflix response 2: {}",
|
||||
e
|
||||
);
|
||||
logging!(error, Type::Network, "Failed to get Netflix response 2: {}", e);
|
||||
return UnlockItem {
|
||||
name: "Netflix".to_string(),
|
||||
status: "Failed".to_string(),
|
||||
@@ -157,12 +147,7 @@ pub(super) async fn check_netflix(client: &Client) -> UnlockItem {
|
||||
async fn check_netflix_cdn(client: &Client) -> UnlockItem {
|
||||
let url = "https://api.fast.com/netflix/speedtest/v2?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=5";
|
||||
|
||||
match client
|
||||
.get(url)
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
match client.get(url).timeout(std::time::Duration::from_secs(30)).send().await {
|
||||
Ok(response) => {
|
||||
if response.status().as_u16() == 403 {
|
||||
return UnlockItem {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use regex::Regex;
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::{logging, utils::logging::Type};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
|
||||
use super::UnlockItem;
|
||||
use super::utils::{country_code_to_emoji, get_local_date_string};
|
||||
@@ -23,12 +23,7 @@ pub(super) async fn check_prime_video(client: &Client) -> UnlockItem {
|
||||
let response = match result {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Network,
|
||||
"Failed to get Prime Video response: {}",
|
||||
e
|
||||
);
|
||||
logging!(error, Type::Network, "Failed to get Prime Video response: {}", e);
|
||||
return UnlockItem {
|
||||
name: "Prime Video".to_string(),
|
||||
status: "Failed (Network Connection)".to_string(),
|
||||
|
||||
@@ -26,7 +26,7 @@ const DEFAULT_UNLOCK_ITEM_NAMES: [&str; 13] = [
|
||||
"ChatGPT Web",
|
||||
"Claude",
|
||||
"Gemini",
|
||||
"Youtube Premium",
|
||||
"YouTube Premium",
|
||||
"Bahamut Anime",
|
||||
"Netflix",
|
||||
"Disney+",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user