aicodeswitch 1.10.2 → 2.0.2
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/CHANGELOG.md +4 -0
- package/bin/cli.js +7 -9
- package/bin/restart.js +7 -229
- package/bin/restore.js +1 -1
- package/bin/start.js +77 -87
- package/bin/stop.js +75 -46
- package/bin/ui.js +19 -134
- package/bin/update.js +1 -1
- package/bin/utils/get-server.js +58 -0
- package/bin/utils/port-utils.js +118 -0
- package/bin/version.js +1 -1
- package/dist/server/database.js +196 -116
- package/dist/server/main.js +116 -21
- package/dist/server/proxy-server.js +334 -158
- package/dist/server/transformers/claude-openai.js +86 -3
- package/dist/server/transformers/streaming.js +4 -1
- package/dist/server/utils.js +16 -0
- package/dist/ui/assets/index-BLqGemLn.js +423 -0
- package/dist/ui/assets/index-IVPeH7yC.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/migration.md +7 -0
- package/package.json +3 -2
- package/public/migration.md +7 -0
- package/dist/ui/assets/index-D6RrKKB5.js +0 -391
- package/dist/ui/assets/index-DU8EG0kT.css +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### 2.0.2 (2026-01-27)
|
|
6
|
+
|
|
7
|
+
### 2.0.1 (2026-01-27)
|
|
8
|
+
|
|
5
9
|
### 1.10.2 (2026-01-26)
|
|
6
10
|
|
|
7
11
|
### 1.10.1 (2026-01-25)
|
package/bin/cli.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
3
|
const args = process.argv.slice(2);
|
|
6
4
|
const command = args[0];
|
|
7
5
|
|
|
8
6
|
const commands = {
|
|
9
|
-
start:
|
|
10
|
-
stop:
|
|
11
|
-
restart:
|
|
12
|
-
update:
|
|
13
|
-
restore:
|
|
14
|
-
version:
|
|
15
|
-
ui:
|
|
7
|
+
start: require('./start'),
|
|
8
|
+
stop: require('./stop'),
|
|
9
|
+
restart: require('./restart'),
|
|
10
|
+
update: require('./update'),
|
|
11
|
+
restore: require('./restore'),
|
|
12
|
+
version: require('./version'),
|
|
13
|
+
ui: require('./ui'),
|
|
16
14
|
};
|
|
17
15
|
|
|
18
16
|
if (!command || !commands[command]) {
|
package/bin/restart.js
CHANGED
|
@@ -1,232 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const chalk = require('chalk');
|
|
6
|
-
const boxen = require('boxen');
|
|
7
|
-
const ora = require('ora');
|
|
1
|
+
// 重启服务器 - 依次执行 stop 和 start 命令
|
|
2
|
+
// 这样可以复用 stop 和 start 命令中的所有逻辑
|
|
3
|
+
const stop = require('./stop');
|
|
4
|
+
const start = require('./start');
|
|
8
5
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// 确保目录存在
|
|
13
|
-
const ensureDir = (filePath) => {
|
|
14
|
-
const dir = path.dirname(filePath);
|
|
15
|
-
if (!fs.existsSync(dir)) {
|
|
16
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const isServerRunning = () => {
|
|
21
|
-
if (!fs.existsSync(PID_FILE)) {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
|
|
27
|
-
// 检查进程是否存在
|
|
28
|
-
process.kill(pid, 0);
|
|
29
|
-
return true;
|
|
30
|
-
} catch (err) {
|
|
31
|
-
// 进程不存在,删除过期的 PID 文件
|
|
32
|
-
fs.unlinkSync(PID_FILE);
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const getServerInfo = () => {
|
|
38
|
-
// 尝试多个可能的配置文件位置
|
|
39
|
-
const possiblePaths = [
|
|
40
|
-
path.join(os.homedir(), '.aicodeswitch', '.env'),
|
|
41
|
-
path.join(os.homedir(), '.aicodeswitch', 'aicodeswitch.conf')
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
let host = '127.0.0.1';
|
|
45
|
-
let port = 4567;
|
|
46
|
-
|
|
47
|
-
for (const dotenvPath of possiblePaths) {
|
|
48
|
-
if (fs.existsSync(dotenvPath)) {
|
|
49
|
-
const content = fs.readFileSync(dotenvPath, 'utf-8');
|
|
50
|
-
const hostMatch = content.match(/HOST=(.+)/);
|
|
51
|
-
const portMatch = content.match(/PORT=(.+)/);
|
|
52
|
-
|
|
53
|
-
if (hostMatch) host = hostMatch[1].trim();
|
|
54
|
-
if (portMatch) port = parseInt(portMatch[1].trim(), 10);
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return { host, port };
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const stopServer = async () => {
|
|
63
|
-
if (!fs.existsSync(PID_FILE)) {
|
|
64
|
-
return true; // 服务未运行,视为停止成功
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const spinner = ora({
|
|
68
|
-
text: chalk.cyan('Stopping server...'),
|
|
69
|
-
color: 'cyan'
|
|
70
|
-
}).start();
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
process.kill(pid, 'SIGTERM');
|
|
77
|
-
|
|
78
|
-
// 等待进程停止
|
|
79
|
-
let attempts = 0;
|
|
80
|
-
const maxAttempts = 10;
|
|
81
|
-
|
|
82
|
-
await new Promise((resolve) => {
|
|
83
|
-
const checkStopped = setInterval(() => {
|
|
84
|
-
attempts++;
|
|
85
|
-
try {
|
|
86
|
-
process.kill(pid, 0);
|
|
87
|
-
if (attempts >= maxAttempts) {
|
|
88
|
-
clearInterval(checkStopped);
|
|
89
|
-
// 强制终止
|
|
90
|
-
try {
|
|
91
|
-
process.kill(pid, 'SIGKILL');
|
|
92
|
-
} catch (e) {
|
|
93
|
-
// 进程可能已经停止
|
|
94
|
-
}
|
|
95
|
-
resolve();
|
|
96
|
-
}
|
|
97
|
-
} catch (err) {
|
|
98
|
-
// 进程已停止
|
|
99
|
-
clearInterval(checkStopped);
|
|
100
|
-
resolve();
|
|
101
|
-
}
|
|
102
|
-
}, 200);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
spinner.succeed(chalk.green('Server stopped'));
|
|
106
|
-
fs.unlinkSync(PID_FILE);
|
|
107
|
-
return true;
|
|
108
|
-
} catch (err) {
|
|
109
|
-
// 进程不存在
|
|
110
|
-
spinner.succeed(chalk.green('Server stopped'));
|
|
111
|
-
fs.unlinkSync(PID_FILE);
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
} catch (err) {
|
|
115
|
-
spinner.fail(chalk.red('Failed to stop server'));
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const startServer = async () => {
|
|
121
|
-
const spinner = ora({
|
|
122
|
-
text: chalk.cyan('Starting server...'),
|
|
123
|
-
color: 'cyan'
|
|
124
|
-
}).start();
|
|
125
|
-
|
|
126
|
-
ensureDir(PID_FILE);
|
|
127
|
-
ensureDir(LOG_FILE);
|
|
128
|
-
|
|
129
|
-
// 找到 main.js 的路径
|
|
130
|
-
const serverPath = path.join(__dirname, '..', 'dist', 'server', 'main.js');
|
|
131
|
-
|
|
132
|
-
if (!fs.existsSync(serverPath)) {
|
|
133
|
-
spinner.fail(chalk.red('Server file not found!'));
|
|
134
|
-
console.log(chalk.yellow(`\nPlease run ${chalk.cyan('npm run build')} first.\n`));
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// 启动服务器进程 - 完全分离
|
|
139
|
-
// 打开日志文件用于输出
|
|
140
|
-
const logFd = fs.openSync(LOG_FILE, 'a');
|
|
141
|
-
|
|
142
|
-
const serverProcess = spawn('node', [serverPath], {
|
|
143
|
-
detached: true,
|
|
144
|
-
stdio: ['ignore', logFd, logFd] // 使用文件描述符
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// 关闭文件描述符(子进程会保持打开)
|
|
148
|
-
fs.closeSync(logFd);
|
|
149
|
-
|
|
150
|
-
// 保存 PID
|
|
151
|
-
fs.writeFileSync(PID_FILE, serverProcess.pid.toString());
|
|
152
|
-
|
|
153
|
-
// 分离进程,让父进程可以退出
|
|
154
|
-
serverProcess.unref();
|
|
155
|
-
|
|
156
|
-
// 等待服务器启动
|
|
157
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
158
|
-
|
|
159
|
-
// 检查服务器是否成功启动
|
|
160
|
-
if (fs.existsSync(PID_FILE)) {
|
|
161
|
-
try {
|
|
162
|
-
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
|
|
163
|
-
process.kill(pid, 0);
|
|
164
|
-
spinner.succeed(chalk.green('Server started'));
|
|
165
|
-
return true;
|
|
166
|
-
} catch (err) {
|
|
167
|
-
spinner.fail(chalk.red('Failed to start server'));
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
spinner.fail(chalk.red('Failed to start server'));
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const restart = async () => {
|
|
177
|
-
console.log('\n');
|
|
178
|
-
|
|
179
|
-
const wasRunning = isServerRunning();
|
|
180
|
-
|
|
181
|
-
if (wasRunning) {
|
|
182
|
-
console.log(chalk.cyan('🔄 Restarting AI Code Switch server...\n'));
|
|
183
|
-
|
|
184
|
-
// 停止服务器
|
|
185
|
-
const stopped = await stopServer();
|
|
186
|
-
if (!stopped) {
|
|
187
|
-
console.log(chalk.red('\nFailed to stop server. Restart aborted.\n'));
|
|
188
|
-
process.exit(1);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// 等待一下确保端口释放
|
|
192
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
193
|
-
} else {
|
|
194
|
-
console.log(chalk.cyan('Starting AI Code Switch server...\n'));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// 启动服务器
|
|
198
|
-
const started = await startServer();
|
|
199
|
-
|
|
200
|
-
if (!started) {
|
|
201
|
-
console.log(chalk.yellow(`\nCheck logs: ${chalk.cyan(LOG_FILE)}\n`));
|
|
202
|
-
process.exit(1);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const { host, port } = getServerInfo();
|
|
206
|
-
const url = `http://${host}:${port}`;
|
|
207
|
-
|
|
208
|
-
// 显示漂亮的启动信息
|
|
209
|
-
console.log(boxen(
|
|
210
|
-
chalk.green.bold('🚀 AI Code Switch Server\n\n') +
|
|
211
|
-
chalk.white('Status: ') + chalk.green.bold('● Running\n') +
|
|
212
|
-
chalk.white('URL: ') + chalk.cyan.bold(url) + '\n' +
|
|
213
|
-
chalk.white('Logs: ') + chalk.gray(LOG_FILE) + '\n\n' +
|
|
214
|
-
chalk.gray('Server has been ' + (wasRunning ? 'restarted' : 'started') + ' successfully'),
|
|
215
|
-
{
|
|
216
|
-
padding: 1,
|
|
217
|
-
margin: 1,
|
|
218
|
-
borderStyle: 'double',
|
|
219
|
-
borderColor: 'green'
|
|
220
|
-
}
|
|
221
|
-
));
|
|
222
|
-
|
|
223
|
-
console.log(chalk.cyan('💡 Tips:\n'));
|
|
224
|
-
console.log(chalk.white(' • Open browser: ') + chalk.cyan(url));
|
|
225
|
-
console.log(chalk.white(' • View logs: ') + chalk.gray(`tail -f ${LOG_FILE}`));
|
|
226
|
-
console.log(chalk.white(' • Stop server: ') + chalk.yellow('aicos stop'));
|
|
227
|
-
console.log('\n');
|
|
228
|
-
|
|
229
|
-
process.exit(0);
|
|
6
|
+
const restart = () => {
|
|
7
|
+
stop({ silent: true, callback: () => start() });
|
|
230
8
|
};
|
|
231
9
|
|
|
232
|
-
module.exports = restart
|
|
10
|
+
module.exports = restart;
|
package/bin/restore.js
CHANGED
package/bin/start.js
CHANGED
|
@@ -5,6 +5,8 @@ const os = require('os');
|
|
|
5
5
|
const chalk = require('chalk');
|
|
6
6
|
const boxen = require('boxen');
|
|
7
7
|
const ora = require('ora');
|
|
8
|
+
const { isServerRunning, getServerInfo } = require('./utils/get-server');
|
|
9
|
+
const { findPidByPort } = require('./utils/port-utils');
|
|
8
10
|
|
|
9
11
|
const PID_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.pid');
|
|
10
12
|
const LOG_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.log');
|
|
@@ -17,71 +19,50 @@ const ensureDir = (filePath) => {
|
|
|
17
19
|
}
|
|
18
20
|
};
|
|
19
21
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
22
|
+
const start = async (options = {}) => {
|
|
23
|
+
const { silent = false, noExit = false, callback } = options;
|
|
24
|
+
console.log('\n');
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
// 已经运行
|
|
27
|
+
const { host, port } = getServerInfo();
|
|
28
|
+
if (isServerRunning() || await findPidByPort(port)) {
|
|
29
|
+
if (!silent) {
|
|
30
|
+
if (!silent) {
|
|
31
|
+
console.log(boxen(
|
|
32
|
+
chalk.yellow.bold('⚠ Server is already running!\n\n') +
|
|
33
|
+
chalk.white(`URL: `) + chalk.cyan.bold(`http://${host}:${port}\n\n`) +
|
|
34
|
+
chalk.white('Use ') + chalk.cyan('aicos restart') + chalk.white(' to restart the server.\n'),
|
|
35
|
+
{
|
|
36
|
+
padding: 1,
|
|
37
|
+
margin: 1,
|
|
38
|
+
borderStyle: 'round',
|
|
39
|
+
borderColor: 'yellow'
|
|
40
|
+
}
|
|
41
|
+
));
|
|
42
|
+
console.log('');
|
|
43
|
+
}
|
|
36
44
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
for (const dotenvPath of possiblePaths) {
|
|
48
|
-
if (fs.existsSync(dotenvPath)) {
|
|
49
|
-
const content = fs.readFileSync(dotenvPath, 'utf-8');
|
|
50
|
-
const hostMatch = content.match(/HOST=(.+)/);
|
|
51
|
-
const portMatch = content.match(/PORT=(.+)/);
|
|
52
|
-
|
|
53
|
-
if (hostMatch) host = hostMatch[1].trim();
|
|
54
|
-
if (portMatch) port = parseInt(portMatch[1].trim(), 10);
|
|
55
|
-
break;
|
|
45
|
+
if (callback) {
|
|
46
|
+
callback();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!noExit) {
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return true;
|
|
56
54
|
}
|
|
55
|
+
if (callback) callback();
|
|
56
|
+
if (!noExit) process.exit(0);
|
|
57
|
+
return true;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
return { host, port };
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const start = async () => {
|
|
63
|
-
console.log('\n');
|
|
64
60
|
|
|
65
|
-
//
|
|
66
|
-
if (isServerRunning()) {
|
|
67
|
-
const { host, port } = getServerInfo();
|
|
68
|
-
console.log(boxen(
|
|
69
|
-
chalk.yellow.bold('⚠ Server is already running!\n\n') +
|
|
70
|
-
chalk.white(`URL: `) + chalk.cyan.bold(`http://${host}:${port}`),
|
|
71
|
-
{
|
|
72
|
-
padding: 1,
|
|
73
|
-
margin: 1,
|
|
74
|
-
borderStyle: 'round',
|
|
75
|
-
borderColor: 'yellow'
|
|
76
|
-
}
|
|
77
|
-
));
|
|
78
|
-
console.log('');
|
|
79
|
-
process.exit(0);
|
|
80
|
-
}
|
|
61
|
+
// 启动服务器
|
|
81
62
|
|
|
82
63
|
const spinner = ora({
|
|
83
64
|
text: chalk.cyan('Starting AI Code Switch server...'),
|
|
84
|
-
color: 'cyan'
|
|
65
|
+
color: 'cyan',
|
|
85
66
|
}).start();
|
|
86
67
|
|
|
87
68
|
ensureDir(PID_FILE);
|
|
@@ -93,7 +74,8 @@ const start = async () => {
|
|
|
93
74
|
if (!fs.existsSync(serverPath)) {
|
|
94
75
|
spinner.fail(chalk.red('Server file not found!'));
|
|
95
76
|
console.log(chalk.yellow(`\nPlease run ${chalk.cyan('npm run build')} first.\n`));
|
|
96
|
-
process.exit(1);
|
|
77
|
+
if (!noExit) process.exit(1);
|
|
78
|
+
return false;
|
|
97
79
|
}
|
|
98
80
|
|
|
99
81
|
// 启动服务器进程 - 完全分离
|
|
@@ -122,45 +104,53 @@ const start = async () => {
|
|
|
122
104
|
try {
|
|
123
105
|
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
|
|
124
106
|
process.kill(pid, 0);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
107
|
+
if (!silent) {
|
|
108
|
+
spinner.succeed(chalk.green('Server started successfully!'));
|
|
109
|
+
|
|
110
|
+
const { host, port } = getServerInfo();
|
|
111
|
+
const url = `http://${host}:${port}`;
|
|
112
|
+
|
|
113
|
+
// 显示漂亮的启动信息
|
|
114
|
+
console.log(boxen(
|
|
115
|
+
chalk.green.bold('🚀 AI Code Switch Server\n\n') +
|
|
116
|
+
chalk.white('Status: ') + chalk.green.bold('● Running\n') +
|
|
117
|
+
chalk.white('URL: ') + chalk.cyan.bold(url) + '\n' +
|
|
118
|
+
chalk.white('PID: ') + chalk.yellow(pid) + '\n' +
|
|
119
|
+
chalk.white('Logs: ') + chalk.gray(LOG_FILE) + '\n\n' +
|
|
120
|
+
chalk.gray('Open the URL in your browser to access the dashboard'),
|
|
121
|
+
{
|
|
122
|
+
padding: 1,
|
|
123
|
+
margin: 1,
|
|
124
|
+
borderStyle: 'double',
|
|
125
|
+
borderColor: 'green'
|
|
126
|
+
}
|
|
127
|
+
));
|
|
128
|
+
|
|
129
|
+
console.log(chalk.cyan('💡 Tips:\n'));
|
|
130
|
+
console.log(chalk.white(' • Open browser: ') + chalk.cyan(url));
|
|
131
|
+
console.log(chalk.white(' • View logs: ') + chalk.gray(`tail -f ${LOG_FILE}`));
|
|
132
|
+
console.log(chalk.white(' • Stop server: ') + chalk.yellow('aicos stop'));
|
|
133
|
+
console.log('\n');
|
|
134
|
+
}
|
|
151
135
|
|
|
136
|
+
// (callback)
|
|
137
|
+
if (callback) callback();
|
|
152
138
|
// 立即退出,返回控制台
|
|
153
|
-
process.exit(0);
|
|
139
|
+
if (!noExit) process.exit(0);
|
|
140
|
+
return true;
|
|
154
141
|
} catch (err) {
|
|
155
142
|
spinner.fail(chalk.red('Failed to start server!'));
|
|
156
143
|
console.log(chalk.yellow(`\nCheck logs: ${chalk.cyan(LOG_FILE)}\n`));
|
|
157
|
-
process.exit(1);
|
|
144
|
+
if (!noExit) process.exit(1);
|
|
145
|
+
return false;
|
|
158
146
|
}
|
|
159
147
|
} else {
|
|
160
148
|
spinner.fail(chalk.red('Failed to start server!'));
|
|
161
149
|
console.log(chalk.yellow(`\nCheck logs: ${chalk.cyan(LOG_FILE)}\n`));
|
|
162
|
-
process.exit(1);
|
|
150
|
+
if (!noExit) process.exit(1);
|
|
151
|
+
return false;
|
|
163
152
|
}
|
|
164
153
|
};
|
|
165
154
|
|
|
166
|
-
|
|
155
|
+
// 导出辅助函数供其他模块使用
|
|
156
|
+
module.exports = start;
|
package/bin/stop.js
CHANGED
|
@@ -4,73 +4,102 @@ const os = require('os');
|
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const boxen = require('boxen');
|
|
6
6
|
const ora = require('ora');
|
|
7
|
+
const { findPidByPort, killProcess, getProcessInfo } = require('./utils/port-utils');
|
|
8
|
+
const { getServerInfo } = require('./utils/get-server');
|
|
7
9
|
|
|
8
10
|
const PID_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.pid');
|
|
9
11
|
|
|
10
|
-
const stop = () => {
|
|
12
|
+
const stop = async (options = {}) => {
|
|
13
|
+
const { callback, silent } = options;
|
|
14
|
+
|
|
11
15
|
console.log('\n');
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
console.log(boxen(
|
|
15
|
-
chalk.yellow.bold('⚠ Server is not running'),
|
|
16
|
-
{
|
|
17
|
-
padding: 1,
|
|
18
|
-
margin: 1,
|
|
19
|
-
borderStyle: 'round',
|
|
20
|
-
borderColor: 'yellow'
|
|
21
|
-
}
|
|
22
|
-
));
|
|
23
|
-
console.log('\n');
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
17
|
+
const spinner = ora({ text: chalk.cyan('Stopping server...'), color: 'cyan' }).start();
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
// 第一步:如果 PID 文件存在,优先通过 PID 文件停止服务器
|
|
20
|
+
if (fs.existsSync(PID_FILE)) {
|
|
21
|
+
try {
|
|
22
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
|
|
31
23
|
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
const processInfo = await getProcessInfo(pid);
|
|
25
|
+
if (!silent) {
|
|
26
|
+
console.log('\n' + chalk.gray(`Process found: ${chalk.white(pid)} (${chalk.gray(processInfo)})`));
|
|
27
|
+
}
|
|
34
28
|
|
|
35
|
-
|
|
36
|
-
try {
|
|
29
|
+
// 尝试终止进程
|
|
37
30
|
process.kill(pid, 'SIGTERM');
|
|
38
31
|
|
|
39
32
|
// 等待进程停止
|
|
40
33
|
let attempts = 0;
|
|
41
34
|
const maxAttempts = 10;
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
await new Promise((resolve) => {
|
|
37
|
+
const checkStopped = setInterval(() => {
|
|
38
|
+
attempts++;
|
|
39
|
+
try {
|
|
40
|
+
process.kill(pid, 0);
|
|
41
|
+
if (attempts >= maxAttempts) {
|
|
42
|
+
clearInterval(checkStopped);
|
|
43
|
+
// 强制终止
|
|
44
|
+
process.kill(pid, 'SIGKILL');
|
|
45
|
+
spinner.warn(chalk.yellow(`PID ${pid} forcefully killed!`));
|
|
46
|
+
fs.unlinkSync(PID_FILE);
|
|
47
|
+
resolve();
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
spinner.succeed(chalk.green(`PID ${pid} killed!`));
|
|
51
|
+
// 进程已停止
|
|
48
52
|
clearInterval(checkStopped);
|
|
49
|
-
// 强制终止
|
|
50
|
-
process.kill(pid, 'SIGKILL');
|
|
51
|
-
spinner.warn(chalk.yellow('Server forcefully stopped'));
|
|
52
53
|
fs.unlinkSync(PID_FILE);
|
|
53
|
-
|
|
54
|
+
resolve();
|
|
54
55
|
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
fs.unlinkSync(PID_FILE);
|
|
60
|
-
showStoppedMessage();
|
|
61
|
-
}
|
|
62
|
-
}, 200);
|
|
63
|
-
|
|
64
|
-
} catch (err) {
|
|
56
|
+
}, 200);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
65
60
|
// 进程不存在
|
|
66
|
-
|
|
61
|
+
if (err.code === 'ESRCH') {
|
|
62
|
+
spinner.warn(chalk.yellow(`PID ${pid} not found!`));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
spinner.fail(chalk.red(`\nError: ${err.message}\n`));
|
|
66
|
+
}
|
|
67
67
|
fs.unlinkSync(PID_FILE);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 第二步:如果 PID 文件不存在,通过端口检测进程并停止
|
|
72
|
+
const { port } = getServerInfo();
|
|
73
|
+
spinner.text = chalk.yellow(`⚠ Checking port... (port: ${port})`);
|
|
74
|
+
const pid = await findPidByPort(port);
|
|
75
|
+
if (pid) {
|
|
76
|
+
spinner.text = chalk.cyan(`Found process on port ${port}, stopping...`);
|
|
77
|
+
|
|
78
|
+
const processInfo = await getProcessInfo(pid);
|
|
79
|
+
if (!silent) {
|
|
80
|
+
console.log('\n' + chalk.gray(`Process found: ${chalk.white(pid)} (${chalk.gray(processInfo)})`));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const killed = await killProcess(pid);
|
|
84
|
+
if (killed) {
|
|
85
|
+
spinner.succeed(chalk.green(`Process ${pid} terminated successfully`));
|
|
86
|
+
if (!silent) {
|
|
87
|
+
showStoppedMessage();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
spinner.fail(chalk.red('Failed to terminate process'));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
spinner.info(chalk.yellow(`No process found on port ${port}`));
|
|
96
|
+
if (!silent) {
|
|
68
97
|
showStoppedMessage();
|
|
69
98
|
}
|
|
70
|
-
} catch (err) {
|
|
71
|
-
spinner.fail(chalk.red('Failed to stop server'));
|
|
72
|
-
console.log(chalk.red(`\nError: ${err.message}\n`));
|
|
73
99
|
}
|
|
100
|
+
|
|
101
|
+
// 第三步:(callback)
|
|
102
|
+
callback && callback();
|
|
74
103
|
};
|
|
75
104
|
|
|
76
105
|
const showStoppedMessage = () => {
|
|
@@ -87,4 +116,4 @@ const showStoppedMessage = () => {
|
|
|
87
116
|
console.log(chalk.white('Use ') + chalk.cyan('aicos start') + chalk.white(' to start the server again.\n'));
|
|
88
117
|
};
|
|
89
118
|
|
|
90
|
-
module.exports = stop
|
|
119
|
+
module.exports = stop;
|