@zzp123/mcp-zentao 1.18.7 → 1.18.8
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.
- package/README.md +64 -0
- package/dist/index-dev.js +5 -4
- package/dist/index-pm.js +5 -4
- package/dist/index-qa.js +8 -4
- package/dist/index.js +5 -4
- package/dist/serverTransport.d.ts +20 -0
- package/dist/serverTransport.js +151 -0
- package/dist/transportConfig.d.ts +12 -0
- package/dist/transportConfig.js +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -279,6 +279,70 @@ docker run -d \
|
|
|
279
279
|
}
|
|
280
280
|
```
|
|
281
281
|
|
|
282
|
+
## 🚦 传输方式:STDIO 与 HTTP 双支持
|
|
283
|
+
|
|
284
|
+
默认使用 STDIO 传输(适合本地/CLI 场景)。如果需要通过 URL 与 MCP 客户端通信,可启用 HTTP 传输。两种配置方式任选其一:
|
|
285
|
+
|
|
286
|
+
**方式 1:环境变量**
|
|
287
|
+
- `MCP_TRANSPORT=http` 启用 HTTP(默认 `stdio`)
|
|
288
|
+
- `MCP_HTTP_PORT`(或 `MCP_PORT`):端口,默认 3000
|
|
289
|
+
- `MCP_HTTP_HOST`(或 `MCP_HOST`):监听地址,默认 `0.0.0.0`
|
|
290
|
+
- `MCP_HTTP_PATH`:路由前缀,默认 `/mcp`
|
|
291
|
+
- 安全可选:`MCP_ALLOWED_HOSTS`、`MCP_ALLOWED_ORIGINS`(逗号分隔),`MCP_DNS_PROTECTION=true`
|
|
292
|
+
|
|
293
|
+
示例:
|
|
294
|
+
```bash
|
|
295
|
+
MCP_TRANSPORT=http MCP_HTTP_PORT=3000 MCP_HTTP_PATH=/mcp zentao --config "{\"url\":\"http://your-zentao\",\"username\":\"u\",\"password\":\"p\",\"apiVersion\":\"v1\"}"
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**方式 2:CLI 参数**
|
|
299
|
+
- `--transport http`
|
|
300
|
+
- `--http-port 3000`(或 `--port`)
|
|
301
|
+
- `--http-host 0.0.0.0`(或 `--host`)
|
|
302
|
+
- `--http-path /mcp`
|
|
303
|
+
- 安全可选:`--allowed-hosts host1,host2`,`--allowed-origins http://a,http://b`,`--dns-protection true`
|
|
304
|
+
|
|
305
|
+
示例:
|
|
306
|
+
```bash
|
|
307
|
+
zentao --transport http --http-port 3000 --http-path /mcp --config "{\"url\":\"http://your-zentao\",\"username\":\"u\",\"password\":\"p\",\"apiVersion\":\"v1\"}"
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
HTTP 模式下客户端需要连接到 `http://<host>:<port><path>`(默认 `http://localhost:3000/mcp`)。
|
|
311
|
+
|
|
312
|
+
**方式 3:交互式初始化(最便捷)**
|
|
313
|
+
|
|
314
|
+
运行一次交互式向导,选择 STDIO/HTTP 并保存到 `~/.zentao/transport.json`,后续无需再带参数:
|
|
315
|
+
```bash
|
|
316
|
+
zentao --init-transport
|
|
317
|
+
# 或 zentao-dev / zentao-pm / zentao-qa 均可
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
交互式示例:
|
|
321
|
+
```
|
|
322
|
+
=== MCP 传输方式初始化 ===
|
|
323
|
+
选择传输方式 (1) stdio (2) http [1]: 2
|
|
324
|
+
HTTP 端口 [3000]: 4000
|
|
325
|
+
HTTP 路径前缀 (如 /mcp) [/mcp]: /zentao
|
|
326
|
+
监听地址 [0.0.0.0]: 127.0.0.1
|
|
327
|
+
允许的 Host 列表(逗号分隔,可留空): localhost:4000
|
|
328
|
+
允许的 Origin 列表(逗号分隔,可留空): https://example.com
|
|
329
|
+
启用 DNS Rebinding 防护? (y/N): y
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
最终配置会写入 `~/.zentao/transport.json`,示例:
|
|
333
|
+
```json
|
|
334
|
+
{
|
|
335
|
+
"transport": "http",
|
|
336
|
+
"host": "127.0.0.1",
|
|
337
|
+
"port": 4000,
|
|
338
|
+
"path": "/zentao",
|
|
339
|
+
"allowedHosts": ["localhost:4000"],
|
|
340
|
+
"allowedOrigins": ["https://example.com"],
|
|
341
|
+
"dnsProtection": true
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
该文件优先级低于环境变量/CLI 参数,因此仍可通过临时参数覆盖。
|
|
345
|
+
|
|
282
346
|
## 基本使用
|
|
283
347
|
|
|
284
348
|
```typescript
|
package/dist/index-dev.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { ZentaoAPI } from './api/zentaoApi.js';
|
|
6
5
|
import { loadConfig, saveConfig } from './config.js';
|
|
6
|
+
import { interactiveInitTransport, startServerTransport } from './serverTransport.js';
|
|
7
7
|
// 解析命令行参数
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
9
|
let configData = null;
|
|
10
|
+
if (await interactiveInitTransport({ args })) {
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
10
13
|
// 查找 --config 参数
|
|
11
14
|
const configIndex = args.indexOf('--config');
|
|
12
15
|
if (configIndex !== -1 && configIndex + 1 < args.length) {
|
|
@@ -783,9 +786,7 @@ server.tool("deleteComment", "删除评论 - 只能删除自己的评论,管
|
|
|
783
786
|
}]
|
|
784
787
|
};
|
|
785
788
|
});
|
|
786
|
-
|
|
787
|
-
const transport = new StdioServerTransport();
|
|
788
|
-
await server.connect(transport).catch(err => {
|
|
789
|
+
await startServerTransport(server, { args }).catch(err => {
|
|
789
790
|
process.stderr.write('[FATAL] MCP server failed: ' + String(err) + '\n');
|
|
790
791
|
process.exit(1);
|
|
791
792
|
});
|
package/dist/index-pm.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { ZentaoAPI } from './api/zentaoApi.js';
|
|
6
5
|
import { loadConfig, saveConfig } from './config.js';
|
|
6
|
+
import { interactiveInitTransport, startServerTransport } from './serverTransport.js';
|
|
7
7
|
// 解析命令行参数
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
9
|
let configData = null;
|
|
10
|
+
if (await interactiveInitTransport({ args })) {
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
10
13
|
// 查找 --config 参数
|
|
11
14
|
const configIndex = args.indexOf('--config');
|
|
12
15
|
if (configIndex !== -1 && configIndex + 1 < args.length) {
|
|
@@ -908,9 +911,7 @@ server.tool("deleteComment", "删除评论 - 只能删除自己的评论,管
|
|
|
908
911
|
}]
|
|
909
912
|
};
|
|
910
913
|
});
|
|
911
|
-
|
|
912
|
-
const transport = new StdioServerTransport();
|
|
913
|
-
await server.connect(transport).catch(err => {
|
|
914
|
+
await startServerTransport(server, { args }).catch(err => {
|
|
914
915
|
process.stderr.write('[FATAL] MCP server failed: ' + String(err) + '\n');
|
|
915
916
|
process.exit(1);
|
|
916
917
|
});
|
package/dist/index-qa.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { ZentaoAPI } from './api/zentaoApi.js';
|
|
6
5
|
import { loadConfig, saveConfig } from './config.js';
|
|
6
|
+
import { interactiveInitTransport, startServerTransport } from './serverTransport.js';
|
|
7
7
|
// 解析命令行参数
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
9
|
let configData = null;
|
|
10
|
+
if (await interactiveInitTransport({ args })) {
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
10
13
|
// 查找 --config 参数
|
|
11
14
|
const configIndex = args.indexOf('--config');
|
|
12
15
|
if (configIndex !== -1 && configIndex + 1 < args.length) {
|
|
@@ -720,6 +723,7 @@ server.tool("deleteComment", "删除评论 - 只能删除自己的评论,管
|
|
|
720
723
|
}]
|
|
721
724
|
};
|
|
722
725
|
});
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
+
await startServerTransport(server, { args }).catch(err => {
|
|
727
|
+
process.stderr.write('[FATAL] MCP server failed: ' + String(err) + '\n');
|
|
728
|
+
process.exit(1);
|
|
729
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { ZentaoAPI } from './api/zentaoApi.js';
|
|
6
5
|
import { loadConfig, saveConfig } from './config.js';
|
|
7
6
|
import { withApi } from './mcpHelpers.js';
|
|
7
|
+
import { interactiveInitTransport, startServerTransport } from './serverTransport.js';
|
|
8
8
|
// 解析命令行参数
|
|
9
9
|
const args = process.argv.slice(2);
|
|
10
10
|
let configData = null;
|
|
11
|
+
if (await interactiveInitTransport({ args })) {
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
11
14
|
// 查找 --config 参数
|
|
12
15
|
const configIndex = args.indexOf('--config');
|
|
13
16
|
if (configIndex !== -1 && configIndex + 1 < args.length) {
|
|
@@ -1540,9 +1543,7 @@ server.tool("deleteComment", "删除评论 - 只能删除自己的评论,管
|
|
|
1540
1543
|
}]
|
|
1541
1544
|
};
|
|
1542
1545
|
});
|
|
1543
|
-
|
|
1544
|
-
const transport = new StdioServerTransport();
|
|
1545
|
-
await server.connect(transport).catch(err => {
|
|
1546
|
+
await startServerTransport(server, { args }).catch(err => {
|
|
1546
1547
|
process.stderr.write('[FATAL] MCP server failed: ' + String(err) + '\n');
|
|
1547
1548
|
process.exit(1);
|
|
1548
1549
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
interface TransportLogger {
|
|
3
|
+
info?: (message: string) => void;
|
|
4
|
+
error?: (message: string) => void;
|
|
5
|
+
}
|
|
6
|
+
export interface StartTransportOptions {
|
|
7
|
+
args?: string[];
|
|
8
|
+
logger?: TransportLogger;
|
|
9
|
+
}
|
|
10
|
+
export interface InteractiveInitOptions {
|
|
11
|
+
args?: string[];
|
|
12
|
+
logger?: TransportLogger;
|
|
13
|
+
}
|
|
14
|
+
export declare function startServerTransport(server: McpServer, options?: StartTransportOptions): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* 交互式初始化:让用户选择 STDIO / HTTP 及 HTTP 参数,并将结果写入 ~/.zentao/transport.json
|
|
17
|
+
* 返回 true 表示已处理(应在入口处直接 exit)
|
|
18
|
+
*/
|
|
19
|
+
export declare function interactiveInitTransport(options?: InteractiveInitOptions): Promise<boolean>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import readline from 'node:readline';
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
6
|
+
import { loadTransportConfig, saveTransportConfig } from './transportConfig.js';
|
|
7
|
+
const DEFAULT_HTTP_PORT = 3000;
|
|
8
|
+
const DEFAULT_HTTP_PATH = '/mcp';
|
|
9
|
+
const getArgValue = (args, flag) => {
|
|
10
|
+
for (let i = 0; i < args.length; i++) {
|
|
11
|
+
const arg = args[i];
|
|
12
|
+
if (arg === flag) {
|
|
13
|
+
return args[i + 1];
|
|
14
|
+
}
|
|
15
|
+
if (arg.startsWith(`${flag}=`)) {
|
|
16
|
+
return arg.substring(flag.length + 1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
};
|
|
21
|
+
const parsePort = (value) => {
|
|
22
|
+
if (!value)
|
|
23
|
+
return undefined;
|
|
24
|
+
const parsed = Number(value);
|
|
25
|
+
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 65535) {
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
};
|
|
30
|
+
export async function startServerTransport(server, options = {}) {
|
|
31
|
+
const args = options.args ?? [];
|
|
32
|
+
const logger = options.logger ?? {
|
|
33
|
+
info: (msg) => process.stdout.write(msg + '\n'),
|
|
34
|
+
error: (msg) => process.stderr.write(msg + '\n')
|
|
35
|
+
};
|
|
36
|
+
const fileConfig = loadTransportConfig();
|
|
37
|
+
const mode = (process.env.MCP_TRANSPORT || getArgValue(args, '--transport') || fileConfig?.transport || 'stdio').toLowerCase();
|
|
38
|
+
if (mode !== 'http') {
|
|
39
|
+
const transport = new StdioServerTransport();
|
|
40
|
+
await server.connect(transport);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const portStr = process.env.MCP_HTTP_PORT
|
|
44
|
+
|| process.env.MCP_PORT
|
|
45
|
+
|| getArgValue(args, '--http-port')
|
|
46
|
+
|| getArgValue(args, '--port')
|
|
47
|
+
|| (fileConfig?.port ? String(fileConfig.port) : undefined);
|
|
48
|
+
const host = process.env.MCP_HTTP_HOST
|
|
49
|
+
|| process.env.MCP_HOST
|
|
50
|
+
|| getArgValue(args, '--http-host')
|
|
51
|
+
|| getArgValue(args, '--host')
|
|
52
|
+
|| fileConfig?.host
|
|
53
|
+
|| '0.0.0.0';
|
|
54
|
+
const pathSetting = process.env.MCP_HTTP_PATH
|
|
55
|
+
|| getArgValue(args, '--http-path')
|
|
56
|
+
|| fileConfig?.path
|
|
57
|
+
|| DEFAULT_HTTP_PATH;
|
|
58
|
+
const port = parsePort(portStr) ?? DEFAULT_HTTP_PORT;
|
|
59
|
+
const normalizedPath = pathSetting.startsWith('/') ? pathSetting : `/${pathSetting}`;
|
|
60
|
+
const allowedHosts = (process.env.MCP_ALLOWED_HOSTS || getArgValue(args, '--allowed-hosts'))?.split(',').map(h => h.trim()).filter(Boolean)
|
|
61
|
+
|| fileConfig?.allowedHosts;
|
|
62
|
+
const allowedOrigins = (process.env.MCP_ALLOWED_ORIGINS || getArgValue(args, '--allowed-origins'))?.split(',').map(o => o.trim()).filter(Boolean)
|
|
63
|
+
|| fileConfig?.allowedOrigins;
|
|
64
|
+
const enableDnsProtection = (process.env.MCP_DNS_PROTECTION || getArgValue(args, '--dns-protection')) === 'true'
|
|
65
|
+
|| Boolean(fileConfig?.dnsProtection);
|
|
66
|
+
const httpTransport = new StreamableHTTPServerTransport({
|
|
67
|
+
sessionIdGenerator: () => randomUUID(),
|
|
68
|
+
enableJsonResponse: true,
|
|
69
|
+
allowedHosts,
|
|
70
|
+
allowedOrigins,
|
|
71
|
+
enableDnsRebindingProtection: enableDnsProtection
|
|
72
|
+
});
|
|
73
|
+
await server.connect(httpTransport);
|
|
74
|
+
const httpServer = createServer(async (req, res) => {
|
|
75
|
+
const reqPath = new URL(req.url || '/', 'http://localhost').pathname;
|
|
76
|
+
if (reqPath !== normalizedPath) {
|
|
77
|
+
res.writeHead(404).end('Not Found');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
await httpTransport.handleRequest(req, res);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
logger.error?.(`[ERROR] HTTP transport request failed: ${String(error)}`);
|
|
85
|
+
res.writeHead(500).end('Internal Server Error');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
httpServer.listen(port, host, () => {
|
|
89
|
+
const displayHost = host === '0.0.0.0' ? 'localhost' : host;
|
|
90
|
+
logger.info?.(`[INFO] MCP HTTP server listening at http://${displayHost}:${port}${normalizedPath}`);
|
|
91
|
+
});
|
|
92
|
+
httpServer.on('error', (err) => {
|
|
93
|
+
logger.error?.(`[ERROR] HTTP server error: ${String(err)}`);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 交互式初始化:让用户选择 STDIO / HTTP 及 HTTP 参数,并将结果写入 ~/.zentao/transport.json
|
|
98
|
+
* 返回 true 表示已处理(应在入口处直接 exit)
|
|
99
|
+
*/
|
|
100
|
+
export async function interactiveInitTransport(options = {}) {
|
|
101
|
+
const args = options.args ?? [];
|
|
102
|
+
const logger = options.logger ?? {
|
|
103
|
+
info: (msg) => process.stdout.write(msg + '\n'),
|
|
104
|
+
error: (msg) => process.stderr.write(msg + '\n')
|
|
105
|
+
};
|
|
106
|
+
if (!args.includes('--init-transport')) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const rl = readline.createInterface({
|
|
110
|
+
input: process.stdin,
|
|
111
|
+
output: process.stdout
|
|
112
|
+
});
|
|
113
|
+
const ask = (query) => new Promise(resolve => rl.question(query, resolve));
|
|
114
|
+
try {
|
|
115
|
+
logger.info?.('=== MCP 传输方式初始化 ===');
|
|
116
|
+
const modeInput = await ask('选择传输方式 (1) stdio (2) http [1]: ');
|
|
117
|
+
const chosenMode = modeInput.trim() === '2' ? 'http' : 'stdio';
|
|
118
|
+
if (chosenMode === 'stdio') {
|
|
119
|
+
saveTransportConfig({ transport: 'stdio' });
|
|
120
|
+
logger.info?.('已保存:使用 STDIO 传输');
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
const portInput = await ask('HTTP 端口 [3000]: ');
|
|
124
|
+
const pathInput = await ask('HTTP 路径前缀 (如 /mcp) [/mcp]: ');
|
|
125
|
+
const hostInput = await ask('监听地址 [0.0.0.0]: ');
|
|
126
|
+
const allowedHostsInput = await ask('允许的 Host 列表(逗号分隔,可留空): ');
|
|
127
|
+
const allowedOriginsInput = await ask('允许的 Origin 列表(逗号分隔,可留空): ');
|
|
128
|
+
const dnsInput = await ask('启用 DNS Rebinding 防护? (y/N): ');
|
|
129
|
+
const port = parsePort(portInput?.trim()) ?? DEFAULT_HTTP_PORT;
|
|
130
|
+
const pathSetting = pathInput?.trim() || '/mcp';
|
|
131
|
+
const host = hostInput?.trim() || '0.0.0.0';
|
|
132
|
+
const allowedHosts = allowedHostsInput.split(',').map(s => s.trim()).filter(Boolean);
|
|
133
|
+
const allowedOrigins = allowedOriginsInput.split(',').map(s => s.trim()).filter(Boolean);
|
|
134
|
+
const dnsProtection = dnsInput.trim().toLowerCase() === 'y';
|
|
135
|
+
const config = {
|
|
136
|
+
transport: 'http',
|
|
137
|
+
host,
|
|
138
|
+
port,
|
|
139
|
+
path: pathSetting.startsWith('/') ? pathSetting : `/${pathSetting}`,
|
|
140
|
+
allowedHosts: allowedHosts.length ? allowedHosts : undefined,
|
|
141
|
+
allowedOrigins: allowedOrigins.length ? allowedOrigins : undefined,
|
|
142
|
+
dnsProtection
|
|
143
|
+
};
|
|
144
|
+
saveTransportConfig(config);
|
|
145
|
+
logger.info?.(`已保存:HTTP 模式 http://${host}:${port}${config.path}`);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
rl.close();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type TransportMode = 'stdio' | 'http';
|
|
2
|
+
export interface TransportConfig {
|
|
3
|
+
transport: TransportMode;
|
|
4
|
+
host?: string;
|
|
5
|
+
port?: number;
|
|
6
|
+
path?: string;
|
|
7
|
+
allowedHosts?: string[];
|
|
8
|
+
allowedOrigins?: string[];
|
|
9
|
+
dnsProtection?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function saveTransportConfig(config: TransportConfig): void;
|
|
12
|
+
export declare function loadTransportConfig(): TransportConfig | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
const CONFIG_DIR = path.join(os.homedir(), '.zentao');
|
|
5
|
+
const TRANSPORT_FILE = path.join(CONFIG_DIR, 'transport.json');
|
|
6
|
+
export function saveTransportConfig(config) {
|
|
7
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
8
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
fs.writeFileSync(TRANSPORT_FILE, JSON.stringify(config, null, 2));
|
|
11
|
+
}
|
|
12
|
+
export function loadTransportConfig() {
|
|
13
|
+
try {
|
|
14
|
+
if (fs.existsSync(TRANSPORT_FILE)) {
|
|
15
|
+
const config = JSON.parse(fs.readFileSync(TRANSPORT_FILE, 'utf-8'));
|
|
16
|
+
return config;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// ignore parse errors, return null
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|