@lancewu602/mihomo-dash 0.0.0-stage → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -2
  3. package/bin/mihomo-dash.js +4 -0
  4. package/package.json +63 -4
  5. package/server/CHANGELOG.md +51 -0
  6. package/server/package.json +13 -0
  7. package/server/src/cli/index.js +75 -0
  8. package/server/src/cli/service.js +167 -0
  9. package/server/src/cli/status.js +245 -0
  10. package/server/src/http/config-files.js +148 -0
  11. package/server/src/http/config-order.js +186 -0
  12. package/server/src/http/config-sections.js +278 -0
  13. package/server/src/http/default-config.js +69 -0
  14. package/server/src/http/groups.js +386 -0
  15. package/server/src/http/index.js +260 -0
  16. package/server/src/http/kernel-admin.js +522 -0
  17. package/server/src/http/kernel-proxy.js +43 -0
  18. package/server/src/http/providers.js +251 -0
  19. package/server/src/http/rules.js +509 -0
  20. package/server/src/http/settings.js +409 -0
  21. package/server/src/http/system-proxy.js +86 -0
  22. package/server/src/index.js +26 -0
  23. package/server/src/kernel/archive.js +181 -0
  24. package/server/src/kernel/binary.js +33 -0
  25. package/server/src/kernel/client.js +138 -0
  26. package/server/src/kernel/install.js +150 -0
  27. package/server/src/kernel/lifecycle.js +339 -0
  28. package/server/src/kernel/run.js +11 -0
  29. package/server/src/shared/args.js +57 -0
  30. package/server/src/shared/atomic-file.js +41 -0
  31. package/server/src/shared/exec.js +44 -0
  32. package/server/src/shared/kernel-config.js +27 -0
  33. package/server/src/shared/log-file.js +92 -0
  34. package/server/src/shared/panel.js +58 -0
  35. package/server/src/shared/paths.js +72 -0
  36. package/server/src/shared/service.js +552 -0
  37. package/server/src/shared/system-proxy.js +375 -0
  38. package/server/src/shared/text.js +11 -0
  39. package/server/tests/cli-service.test.js +153 -0
  40. package/server/tests/config-dir.test.js +133 -0
  41. package/server/tests/config-order.test.js +148 -0
  42. package/server/tests/groups.test.js +558 -0
  43. package/server/tests/kernel-admin.test.js +647 -0
  44. package/server/tests/kernel.test.js +250 -0
  45. package/server/tests/log-file.test.js +110 -0
  46. package/server/tests/providers.test.js +259 -0
  47. package/server/tests/rules.test.js +463 -0
  48. package/server/tests/service.test.js +470 -0
  49. package/server/tests/settings.test.js +456 -0
  50. package/server/tests/status.test.js +269 -0
  51. package/server/tests/system-proxy.test.js +497 -0
  52. package/server/tsconfig.json +17 -0
  53. package/web/dist/assets/index-BpDzYhuI.css +1 -0
  54. package/web/dist/assets/index-WmujD2EM.js +515 -0
  55. package/web/dist/favicon.svg +5 -0
  56. package/web/dist/index.html +14 -0
@@ -0,0 +1,148 @@
1
+ /**
2
+ * config.yaml 顶层键排序(server/src/http/config-order.js + config-files.js 的写入路径)测试。
3
+ *
4
+ * 断言的是「只搬位置、不改内容」:键下面的内容、行尾注释、键上方紧贴的说明、文件头注释都得保住,
5
+ * 块与块之间不留空行;排序结果必须幂等、解析出来跟原文一模一样。
6
+ * 运行:npm test
7
+ */
8
+ import assert from 'node:assert/strict';
9
+ import { after, before, test } from 'node:test';
10
+ import fs from 'node:fs';
11
+ import http from 'node:http';
12
+ import os from 'node:os';
13
+ import path from 'node:path';
14
+ import yaml from 'js-yaml';
15
+
16
+ /** @type {Record<string, any>} */ let mods = {};
17
+ /** @type {string} */ let tmpDir;
18
+ /** @type {string} */ let cfgDir;
19
+ /** @type {import('node:http').Server} */ let kernel;
20
+ /** @type {number} */ let stubPort = 0;
21
+
22
+ const configPath = () => path.join(cfgDir, 'config.yaml');
23
+ const readConfig = () => fs.readFileSync(configPath(), 'utf8');
24
+ const writeConfig = (text) => fs.writeFileSync(configPath(), text);
25
+ /** 桩内核的地址也得写进配置(代码只认 config.yaml 的 external-controller) */
26
+
27
+ before(async () => {
28
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mihomo-dash-order-'));
29
+ cfgDir = path.join(tmpDir, 'mihomo');
30
+ fs.mkdirSync(cfgDir, { recursive: true });
31
+
32
+ kernel = http.createServer((_req, res) => {
33
+ res.writeHead(200, { 'content-type': 'application/json' });
34
+ res.end('{}');
35
+ });
36
+ await new Promise((resolve) => kernel.listen(0, '127.0.0.1', resolve));
37
+ stubPort = kernel.address().port;
38
+
39
+ process.env.MIHOMO_DIR = cfgDir;
40
+ process.env.MIHOMO_DASH_DIR = path.join(tmpDir, 'cli');
41
+ mods = { order: await import('../src/http/config-order.js') };
42
+ mods.files = await import('../src/http/config-files.js');
43
+ });
44
+
45
+ after(async () => {
46
+ await new Promise((resolve) => kernel.close(resolve));
47
+ fs.rmSync(tmpDir, { recursive: true, force: true });
48
+ });
49
+
50
+ test('乱序的顶层键排成固定顺序;内容、注释、缩进一字不改,块之间不留空行', () => {
51
+ const src = `unified-delay: false
52
+ geox-url:
53
+ geosite: "https://x/geosite.dat"
54
+ geoip: "https://x/geoip.dat"
55
+ geo-update-interval: 12
56
+ geodata-mode: true
57
+ mixed-port: 7890
58
+ # 运行模式
59
+ mode: rule
60
+ log-level: warning # 行尾注释也要留着
61
+ external-controller: 127.0.0.1:8190
62
+ ipv6: true
63
+
64
+ # ===== 规则 =====
65
+ rules:
66
+ - MATCH,DIRECT
67
+ `;
68
+ assert.equal(
69
+ mods.order.orderTopLevel(src),
70
+ `mixed-port: 7890
71
+ ipv6: true
72
+ # 运行模式
73
+ mode: rule
74
+ log-level: warning # 行尾注释也要留着
75
+ unified-delay: false
76
+ external-controller: 127.0.0.1:8190
77
+ geodata-mode: true
78
+ geo-update-interval: 12
79
+ geox-url:
80
+ geosite: "https://x/geosite.dat"
81
+ geoip: "https://x/geoip.dat"
82
+ # ===== 规则 =====
83
+ rules:
84
+ - MATCH,DIRECT
85
+ `,
86
+ );
87
+ });
88
+
89
+ test('幂等,且解析结果与原文完全一致(排序只是排版)', () => {
90
+ const src = `rules:\n - MATCH,DIRECT\nmixed-port: 7890\nunified-delay: true\ndns:\n enable: true\n`;
91
+ const once = mods.order.orderTopLevel(src);
92
+ assert.equal(mods.order.orderTopLevel(once), once);
93
+ assert.deepEqual(yaml.load(once), yaml.load(src));
94
+ });
95
+
96
+ test('文件头注释留在最上面;面板不认识的键排在 rules 前面,保持相对顺序', () => {
97
+ const src = `# 我的配置\n# 第二行\n\naaa-unknown: 1\nproxy-providers:\n p:\n url: x\nbbb-unknown: 2\nrules:\n - MATCH,DIRECT\n`;
98
+ assert.equal(
99
+ mods.order.orderTopLevel(src),
100
+ `# 我的配置\n# 第二行\nproxy-providers:\n p:\n url: x\naaa-unknown: 1\nbbb-unknown: 2\nrules:\n - MATCH,DIRECT\n`,
101
+ );
102
+ });
103
+
104
+ test('块之间不留空行:原文分了块的也压成紧凑写法,块内部空行不动', () => {
105
+ assert.equal(
106
+ mods.order.orderTopLevel('rules:\n - MATCH,DIRECT\n\nmixed-port: 7890\n\nmode: rule\n'),
107
+ 'mixed-port: 7890\nmode: rule\nrules:\n - MATCH,DIRECT\n',
108
+ );
109
+ // 段落里面自己留的空行(这里 dns 内部)不算块间分隔,原样保留
110
+ assert.equal(
111
+ mods.order.orderTopLevel('# 头\n\ndns:\n enable: true\n\n ipv6: false\nmixed-port: 7890\n'),
112
+ '# 头\nmixed-port: 7890\ndns:\n enable: true\n\n ipv6: false\n',
113
+ );
114
+ });
115
+
116
+ test('没有顶层键 / 空文件:原样返回,不抛错', () => {
117
+ assert.equal(mods.order.orderTopLevel(''), '');
118
+ assert.equal(mods.order.orderTopLevel('# 只有注释\n'), '# 只有注释\n');
119
+ assert.equal(mods.order.orderTopLevel('proxies:\n'), 'proxies:\n');
120
+ });
121
+
122
+ test('安全网:解析结果对不上时放弃排序,绝不拿用户文件冒险', () => {
123
+ // 同名顶层键在 YAML 里是错的(js-yaml 直接报错):排序应当被跳过,文件原样写回去
124
+ const dup = 'mixed-port: 7890\nrules:\n - MATCH,DIRECT\nmixed-port: 1\n';
125
+ assert.equal(mods.files.orderedConfigText(dup), dup);
126
+ });
127
+
128
+ test('写盘路径:saveFile(配置文件页)排 config.yaml,不排 providers/*.yaml', () => {
129
+ writeConfig('rules:\n - MATCH,DIRECT\nmixed-port: 7890\n');
130
+ mods.files.saveFile('config.yaml', readConfig());
131
+ assert.equal(readConfig(), 'mixed-port: 7890\nrules:\n - MATCH,DIRECT\n');
132
+
133
+ const prov = path.join(cfgDir, 'providers');
134
+ fs.mkdirSync(prov, { recursive: true });
135
+ fs.writeFileSync(path.join(prov, 'p.yaml'), 'proxies: []\n');
136
+ mods.files.saveFile('providers/p.yaml', 'proxies: []\n');
137
+ assert.equal(fs.readFileSync(path.join(prov, 'p.yaml'), 'utf8'), 'proxies: []\n');
138
+ });
139
+
140
+ test('写盘路径:saveConfigWithReload(GEO 键走的那条)也会排序 + 发重载请求', async () => {
141
+ await mods.files.saveConfigWithReload(
142
+ `rules:\n - MATCH,DIRECT\nexternal-controller: 127.0.0.1:${stubPort}\n`,
143
+ );
144
+ assert.equal(
145
+ readConfig(),
146
+ `external-controller: 127.0.0.1:${stubPort}\nrules:\n - MATCH,DIRECT\n`,
147
+ );
148
+ });