@myassis/gateway 1.0.11 → 1.0.13
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/dist/cli.js +45 -0
- package/dist/index.js +3 -256
- package/dist/main.js +257 -0
- package/dist/services/session/SessionStore.js +26 -6
- package/migrations/001_create_sessions_table.sql +38 -0
- package/migrations/002_alter_message_table.sql +3 -0
- package/migrations/003_add_tool_calls_data.sql +2 -0
- package/migrations/004_add_last_summary.sql +2 -0
- package/migrations/005_add_message_model_name.sql +1 -0
- package/migrations/006_add_unread_count.sql +1 -0
- package/migrations/008_add_agents_is_current.sql +12 -0
- package/migrations/009_add_is_current.sql +1 -0
- package/migrations/010_message_tool_call.sql +4 -0
- package/migrations/011_drop_message_toolcallsdata.sql +1 -0
- package/package.json +5 -3
- package/scripts/fix-all-aliases.js +0 -112
- package/scripts/fix-dist-aliases.js +0 -107
- package/scripts/fix-dist-imports.js +0 -95
- package/scripts/fix-esm-imports.js +0 -83
- package/scripts/fix-imports.js +0 -90
- package/scripts/fix-path-aliases.js +0 -95
- package/scripts/postbuild.js +0 -39
package/dist/cli.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ─── CLI 快速入口(在导入任何模块之前处理参数)─────────────────
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
// 快速响应命令(无需加载任何模块)
|
|
5
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
try {
|
|
9
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
10
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
11
|
+
console.log(pkg.version);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
console.log('1.0.0');
|
|
15
|
+
}
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
19
|
+
console.log(`我的助手 Gateway CLI
|
|
20
|
+
|
|
21
|
+
用法: gateway <命令>
|
|
22
|
+
|
|
23
|
+
服务管理命令:
|
|
24
|
+
install 安装 Gateway 服务(后台运行)
|
|
25
|
+
uninstall 卸载 Gateway 服务
|
|
26
|
+
start 启动 Gateway 服务
|
|
27
|
+
stop 停止 Gateway 服务
|
|
28
|
+
restart 重启 Gateway 服务
|
|
29
|
+
update 更新 Gateway(需重新安装)
|
|
30
|
+
status 查看服务状态
|
|
31
|
+
|
|
32
|
+
CORS 管理命令:
|
|
33
|
+
addCors <域名> 添加允许的跨域域名
|
|
34
|
+
removeCors <域名> 移除允许的跨域域名
|
|
35
|
+
listCors 列出所有自定义跨域域名
|
|
36
|
+
|
|
37
|
+
其他:
|
|
38
|
+
--version, -v 显示版本号
|
|
39
|
+
--help, -h 显示本帮助信息
|
|
40
|
+
`);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
// 需要加载模块的命令,导入主程序
|
|
44
|
+
import('./main.js');
|
|
45
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,257 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import compression from 'compression';
|
|
6
|
-
import http from 'http';
|
|
7
|
-
import { appConfig } from './config/index.js';
|
|
8
|
-
import { getLogger } from '@myassis/shared';
|
|
9
|
-
import authRoutes from './routes/auth.js';
|
|
10
|
-
import agentRoutes from './routes/agent.js';
|
|
11
|
-
import modelsRoutes from './routes/models.js';
|
|
12
|
-
import skillsRoutes from './routes/skills.js';
|
|
13
|
-
import skillHubRoutes from './routes/skillHub.js';
|
|
14
|
-
import chatRoutes from './routes/chat.js';
|
|
15
|
-
import configRoutes from './routes/config.js';
|
|
16
|
-
import settingsRoutes from './routes/settings.js';
|
|
17
|
-
import serviceRoutes from './routes/service.js';
|
|
18
|
-
import tasksRoutes from './routes/tasks.js';
|
|
19
|
-
import uploadRoutes from './routes/upload.js';
|
|
20
|
-
import versionRoutes from './routes/version.js';
|
|
21
|
-
import { errorHandler } from './middleware/errorHandler.js';
|
|
22
|
-
import { authStore } from './stores/index.js';
|
|
23
|
-
import { persistStore } from './stores/persistStore.js';
|
|
24
|
-
import { webSocketService } from './services/WebSocketService.js';
|
|
25
|
-
import { taskSchedulerService } from './services/TaskSchedulerService.js';
|
|
26
|
-
import { getServiceInfo, installService, uninstallService, startService, stopService, restartService, updateService, } from './services/ServiceManager.js';
|
|
27
|
-
const logger = getLogger('index');
|
|
28
|
-
// ─── CLI 模式 ─────────────────────────────────────────
|
|
29
|
-
// gateway install | start | stop | uninstall | status | update
|
|
30
|
-
const cliCommand = process.argv[2];
|
|
31
|
-
if (cliCommand) {
|
|
32
|
-
(async () => {
|
|
33
|
-
try {
|
|
34
|
-
let exitCode = 0;
|
|
35
|
-
if (cliCommand === 'status') {
|
|
36
|
-
const info = await getServiceInfo();
|
|
37
|
-
console.log(`服务名称: ${info.displayName}`);
|
|
38
|
-
console.log(`平台: ${info.platform}`);
|
|
39
|
-
console.log(`已安装: ${info.installed ? '是' : '否'}`);
|
|
40
|
-
console.log(`运行中: ${info.running ? '是' : '否'}`);
|
|
41
|
-
if (!info.canManage)
|
|
42
|
-
console.log(`(当前平台不支持服务管理)`);
|
|
43
|
-
}
|
|
44
|
-
else if (cliCommand === 'addCors') {
|
|
45
|
-
const domain = process.argv[3];
|
|
46
|
-
if (!domain) {
|
|
47
|
-
console.log('用法: gateway addCors <域名>');
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
const added = persistStore.addCorsDomain(domain);
|
|
51
|
-
if (added) {
|
|
52
|
-
console.log(`✅ 已添加 CORS 域名: ${domain}`);
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
console.log(`⚠️ 域名已存在: ${domain}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
else if (cliCommand === 'removeCors') {
|
|
59
|
-
const domain = process.argv[3];
|
|
60
|
-
if (!domain) {
|
|
61
|
-
console.log('用法: gateway removeCors <域名>');
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
const removed = persistStore.removeCorsDomain(domain);
|
|
65
|
-
if (removed) {
|
|
66
|
-
console.log(`✅ 已移除 CORS 域名: ${domain}`);
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
console.log(`⚠️ 域名不存在: ${domain}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
else if (cliCommand === 'listCors') {
|
|
73
|
-
const domains = persistStore.getCorsDomains();
|
|
74
|
-
if (domains.length === 0) {
|
|
75
|
-
console.log('暂无自定义 CORS 域名(仅使用内置内网规则)');
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
console.log(`自定义 CORS 域名 (${domains.length}):`);
|
|
79
|
-
domains.forEach(d => console.log(` - ${d}`));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
else if (cliCommand === '--help' || cliCommand === '-h') {
|
|
83
|
-
console.log(`我的助手 Gateway CLI
|
|
84
|
-
|
|
85
|
-
用法: gateway <命令>
|
|
86
|
-
|
|
87
|
-
服务管理命令:
|
|
88
|
-
install 安装 Gateway 服务(后台运行)
|
|
89
|
-
uninstall 卸载 Gateway 服务
|
|
90
|
-
start 启动 Gateway 服务
|
|
91
|
-
stop 停止 Gateway 服务
|
|
92
|
-
restart 重启 Gateway 服务
|
|
93
|
-
update 更新 Gateway(需重新安装)
|
|
94
|
-
status 查看服务状态
|
|
95
|
-
|
|
96
|
-
CORS 管理命令:
|
|
97
|
-
addCors <域名> 添加允许的跨域域名
|
|
98
|
-
removeCors <域名> 移除允许的跨域域名
|
|
99
|
-
listCors 列出所有自定义跨域域名
|
|
100
|
-
|
|
101
|
-
其他:
|
|
102
|
-
--help, -h 显示本帮助信息
|
|
103
|
-
`);
|
|
104
|
-
const domains = persistStore.getCorsDomains();
|
|
105
|
-
if (domains.length === 0) {
|
|
106
|
-
console.log('暂无自定义 CORS 域名(仅使用内置内网规则)');
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
console.log(`自定义 CORS 域名 (${domains.length}):`);
|
|
110
|
-
domains.forEach(d => console.log(` - ${d}`));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
const fnMap = {
|
|
115
|
-
install: installService,
|
|
116
|
-
uninstall: uninstallService,
|
|
117
|
-
start: startService,
|
|
118
|
-
stop: stopService,
|
|
119
|
-
restart: restartService,
|
|
120
|
-
update: updateService,
|
|
121
|
-
};
|
|
122
|
-
const handler = fnMap[cliCommand];
|
|
123
|
-
if (!handler) {
|
|
124
|
-
console.log(`未知命令: ${cliCommand}。输入 "gateway --help" 查看帮助。`);
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
const result = await handler();
|
|
128
|
-
console.log(result.success ? `✅ ${result.message}` : `❌ ${result.message}`);
|
|
129
|
-
if (!result.success)
|
|
130
|
-
exitCode = 1;
|
|
131
|
-
}
|
|
132
|
-
process.exit(exitCode);
|
|
133
|
-
}
|
|
134
|
-
catch (err) {
|
|
135
|
-
console.error(`❌ 操作失败: ${err.message}`);
|
|
136
|
-
process.exit(1);
|
|
137
|
-
}
|
|
138
|
-
})();
|
|
139
|
-
// CLI 命令执行 process.exit() 后脚本终止,IIFE 异步执行不阻塞
|
|
140
|
-
// 不写 return,让脚本自然结束(IIFE 完成后进程也退了)
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
// ─── HTTP Server 模式 ────────────────────────────────────
|
|
144
|
-
const app = express();
|
|
145
|
-
// Middleware
|
|
146
|
-
app.use(helmet());
|
|
147
|
-
app.use(compression());
|
|
148
|
-
// CORS 配置 - 允许所有内网地址
|
|
149
|
-
const corsOptions = {
|
|
150
|
-
origin: (origin, callback) => {
|
|
151
|
-
// 允许没有 origin 的请求(如移动端或同源请求)
|
|
152
|
-
if (!origin) {
|
|
153
|
-
return callback(null, true);
|
|
154
|
-
}
|
|
155
|
-
// 内网地址正则
|
|
156
|
-
const lanPatterns = [
|
|
157
|
-
/^http:\/\/localhost(:\d+)?$/,
|
|
158
|
-
/^http:\/\/127\.\d+\.\d+\.\d+(:\d+)?$/,
|
|
159
|
-
/^http:\/\/10\.\d+\.\d+\.\d+(:\d+)?$/,
|
|
160
|
-
/^http:\/\/172\.(1[6-9]|2\d|3[01])\.\d+\.\d+(:\d+)?$/,
|
|
161
|
-
/^http:\/\/192\.168\.\d+\.\d+(:\d+)?$/,
|
|
162
|
-
/^http:\/\/\d+\.\d+\.\d+\.\d+(:\d+)?$/,
|
|
163
|
-
/\.local(:\d+)?$/,
|
|
164
|
-
];
|
|
165
|
-
if (lanPatterns.some(pattern => pattern.test(origin))) {
|
|
166
|
-
return callback(null, true);
|
|
167
|
-
}
|
|
168
|
-
// 检查自定义域名(支持子域名匹配)
|
|
169
|
-
const customDomains = persistStore.getCorsDomains();
|
|
170
|
-
const originLower = origin.toLowerCase();
|
|
171
|
-
if (customDomains.some(d => {
|
|
172
|
-
const dLower = d.toLowerCase();
|
|
173
|
-
return originLower === dLower || originLower.endsWith('.' + dLower);
|
|
174
|
-
})) {
|
|
175
|
-
return callback(null, true);
|
|
176
|
-
}
|
|
177
|
-
callback(new Error('Not allowed by CORS'));
|
|
178
|
-
},
|
|
179
|
-
credentials: true,
|
|
180
|
-
};
|
|
181
|
-
app.use(cors(corsOptions));
|
|
182
|
-
app.use(express.json({ limit: '10mb' }));
|
|
183
|
-
// Health check
|
|
184
|
-
app.get('/health', (req, res) => {
|
|
185
|
-
res.json({ status: 'ok', service: 'gateway', version: '2.0.0', wsOnline: webSocketService.getOnlineCount() });
|
|
186
|
-
});
|
|
187
|
-
// Routes
|
|
188
|
-
app.use('/api/v1/auth', authRoutes);
|
|
189
|
-
app.use('/api/v1/agent', agentRoutes);
|
|
190
|
-
app.use('/api/v1/models', modelsRoutes);
|
|
191
|
-
app.use('/api/v1/skills', skillsRoutes);
|
|
192
|
-
app.use('/api/v1/skill-hubs', skillHubRoutes);
|
|
193
|
-
app.use('/api/v1/chats', chatRoutes);
|
|
194
|
-
app.use('/api/v1/config', configRoutes);
|
|
195
|
-
app.use('/api/v1/settings', settingsRoutes);
|
|
196
|
-
app.use('/api/v1/service', serviceRoutes);
|
|
197
|
-
app.use('/api/v1/tasks', tasksRoutes);
|
|
198
|
-
app.use('/api/v1/upload', uploadRoutes);
|
|
199
|
-
app.use('/api/v1/version', versionRoutes);
|
|
200
|
-
// Load auth from persistent storage on startup
|
|
201
|
-
authStore.load();
|
|
202
|
-
// Error handler
|
|
203
|
-
app.use(errorHandler);
|
|
204
|
-
// 创建 HTTP Server(而非 app.listen,以便挂载 WebSocket)
|
|
205
|
-
const server = http.createServer(app);
|
|
206
|
-
// 初始化 WebSocket 服务
|
|
207
|
-
webSocketService.initialize(server);
|
|
208
|
-
// 启动任务调度器
|
|
209
|
-
taskSchedulerService.start();
|
|
210
|
-
// 端口自动顺延:被占用时自动尝试下一个端口
|
|
211
|
-
const startServer = (port) => {
|
|
212
|
-
server.listen(port, () => {
|
|
213
|
-
// 动态更新 appConfig.port(供 WebSocket 广播实际端口)
|
|
214
|
-
appConfig.port = port;
|
|
215
|
-
if (port !== appConfig.port) {
|
|
216
|
-
logger.info(`Port ${appConfig.port} is in use, fallback to port ${port}`);
|
|
217
|
-
}
|
|
218
|
-
logger.info(`我的助手 Gateway Service running on port ${port}`);
|
|
219
|
-
}).on('error', (err) => {
|
|
220
|
-
if (err.code === 'EADDRINUSE') {
|
|
221
|
-
const nextPort = port + 1;
|
|
222
|
-
if (nextPort <= (appConfig.port + 10)) {
|
|
223
|
-
logger.warn(`Port ${port} is in use, trying ${nextPort}...`);
|
|
224
|
-
startServer(nextPort);
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
logger.error(`All ports from ${appConfig.port} to ${appConfig.port + 10} are in use`);
|
|
228
|
-
process.exit(1);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
logger.error(`Server error: ${err.message}`);
|
|
233
|
-
process.exit(1);
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
};
|
|
237
|
-
startServer(appConfig.port);
|
|
238
|
-
// 优雅关闭
|
|
239
|
-
process.on('SIGTERM', () => {
|
|
240
|
-
logger.info('收到 SIGTERM,正在关闭...');
|
|
241
|
-
webSocketService.shutdown();
|
|
242
|
-
taskSchedulerService.stop();
|
|
243
|
-
server.close(() => {
|
|
244
|
-
logger.info('服务已关闭');
|
|
245
|
-
process.exit(0);
|
|
246
|
-
});
|
|
247
|
-
});
|
|
248
|
-
process.on('SIGINT', () => {
|
|
249
|
-
logger.info('收到 SIGINT,正在关闭...');
|
|
250
|
-
webSocketService.shutdown();
|
|
251
|
-
taskSchedulerService.stop();
|
|
252
|
-
server.close(() => {
|
|
253
|
-
logger.info('服务已关闭');
|
|
254
|
-
process.exit(0);
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
}
|
|
2
|
+
// 入口文件 - 委托给 CLI 处理器
|
|
3
|
+
import('./cli.js');
|
|
4
|
+
export {};
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import cors from 'cors';
|
|
4
|
+
import helmet from 'helmet';
|
|
5
|
+
import compression from 'compression';
|
|
6
|
+
import http from 'http';
|
|
7
|
+
import { appConfig } from './config/index.js';
|
|
8
|
+
import { getLogger } from '@myassis/shared';
|
|
9
|
+
import authRoutes from './routes/auth.js';
|
|
10
|
+
import agentRoutes from './routes/agent.js';
|
|
11
|
+
import modelsRoutes from './routes/models.js';
|
|
12
|
+
import skillsRoutes from './routes/skills.js';
|
|
13
|
+
import skillHubRoutes from './routes/skillHub.js';
|
|
14
|
+
import chatRoutes from './routes/chat.js';
|
|
15
|
+
import configRoutes from './routes/config.js';
|
|
16
|
+
import settingsRoutes from './routes/settings.js';
|
|
17
|
+
import serviceRoutes from './routes/service.js';
|
|
18
|
+
import tasksRoutes from './routes/tasks.js';
|
|
19
|
+
import uploadRoutes from './routes/upload.js';
|
|
20
|
+
import versionRoutes from './routes/version.js';
|
|
21
|
+
import { errorHandler } from './middleware/errorHandler.js';
|
|
22
|
+
import { authStore } from './stores/index.js';
|
|
23
|
+
import { persistStore } from './stores/persistStore.js';
|
|
24
|
+
import { webSocketService } from './services/WebSocketService.js';
|
|
25
|
+
import { taskSchedulerService } from './services/TaskSchedulerService.js';
|
|
26
|
+
import { getServiceInfo, installService, uninstallService, startService, stopService, restartService, updateService, } from './services/ServiceManager.js';
|
|
27
|
+
const logger = getLogger('index');
|
|
28
|
+
// ─── CLI 模式 ─────────────────────────────────────────
|
|
29
|
+
// gateway install | start | stop | uninstall | status | update
|
|
30
|
+
const cliCommand = process.argv[2];
|
|
31
|
+
if (cliCommand) {
|
|
32
|
+
(async () => {
|
|
33
|
+
try {
|
|
34
|
+
let exitCode = 0;
|
|
35
|
+
if (cliCommand === 'status') {
|
|
36
|
+
const info = await getServiceInfo();
|
|
37
|
+
console.log(`服务名称: ${info.displayName}`);
|
|
38
|
+
console.log(`平台: ${info.platform}`);
|
|
39
|
+
console.log(`已安装: ${info.installed ? '是' : '否'}`);
|
|
40
|
+
console.log(`运行中: ${info.running ? '是' : '否'}`);
|
|
41
|
+
if (!info.canManage)
|
|
42
|
+
console.log(`(当前平台不支持服务管理)`);
|
|
43
|
+
}
|
|
44
|
+
else if (cliCommand === 'addCors') {
|
|
45
|
+
const domain = process.argv[3];
|
|
46
|
+
if (!domain) {
|
|
47
|
+
console.log('用法: gateway addCors <域名>');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const added = persistStore.addCorsDomain(domain);
|
|
51
|
+
if (added) {
|
|
52
|
+
console.log(`✅ 已添加 CORS 域名: ${domain}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log(`⚠️ 域名已存在: ${domain}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else if (cliCommand === 'removeCors') {
|
|
59
|
+
const domain = process.argv[3];
|
|
60
|
+
if (!domain) {
|
|
61
|
+
console.log('用法: gateway removeCors <域名>');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const removed = persistStore.removeCorsDomain(domain);
|
|
65
|
+
if (removed) {
|
|
66
|
+
console.log(`✅ 已移除 CORS 域名: ${domain}`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(`⚠️ 域名不存在: ${domain}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (cliCommand === 'listCors') {
|
|
73
|
+
const domains = persistStore.getCorsDomains();
|
|
74
|
+
if (domains.length === 0) {
|
|
75
|
+
console.log('暂无自定义 CORS 域名(仅使用内置内网规则)');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.log(`自定义 CORS 域名 (${domains.length}):`);
|
|
79
|
+
domains.forEach(d => console.log(` - ${d}`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (cliCommand === '--help' || cliCommand === '-h') {
|
|
83
|
+
console.log(`我的助手 Gateway CLI
|
|
84
|
+
|
|
85
|
+
用法: gateway <命令>
|
|
86
|
+
|
|
87
|
+
服务管理命令:
|
|
88
|
+
install 安装 Gateway 服务(后台运行)
|
|
89
|
+
uninstall 卸载 Gateway 服务
|
|
90
|
+
start 启动 Gateway 服务
|
|
91
|
+
stop 停止 Gateway 服务
|
|
92
|
+
restart 重启 Gateway 服务
|
|
93
|
+
update 更新 Gateway(需重新安装)
|
|
94
|
+
status 查看服务状态
|
|
95
|
+
|
|
96
|
+
CORS 管理命令:
|
|
97
|
+
addCors <域名> 添加允许的跨域域名
|
|
98
|
+
removeCors <域名> 移除允许的跨域域名
|
|
99
|
+
listCors 列出所有自定义跨域域名
|
|
100
|
+
|
|
101
|
+
其他:
|
|
102
|
+
--help, -h 显示本帮助信息
|
|
103
|
+
`);
|
|
104
|
+
const domains = persistStore.getCorsDomains();
|
|
105
|
+
if (domains.length === 0) {
|
|
106
|
+
console.log('暂无自定义 CORS 域名(仅使用内置内网规则)');
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
console.log(`自定义 CORS 域名 (${domains.length}):`);
|
|
110
|
+
domains.forEach(d => console.log(` - ${d}`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const fnMap = {
|
|
115
|
+
install: installService,
|
|
116
|
+
uninstall: uninstallService,
|
|
117
|
+
start: startService,
|
|
118
|
+
stop: stopService,
|
|
119
|
+
restart: restartService,
|
|
120
|
+
update: updateService,
|
|
121
|
+
};
|
|
122
|
+
const handler = fnMap[cliCommand];
|
|
123
|
+
if (!handler) {
|
|
124
|
+
console.log(`未知命令: ${cliCommand}。输入 "gateway --help" 查看帮助。`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
const result = await handler();
|
|
128
|
+
console.log(result.success ? `✅ ${result.message}` : `❌ ${result.message}`);
|
|
129
|
+
if (!result.success)
|
|
130
|
+
exitCode = 1;
|
|
131
|
+
}
|
|
132
|
+
process.exit(exitCode);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
console.error(`❌ 操作失败: ${err.message}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
})();
|
|
139
|
+
// CLI 命令执行 process.exit() 后脚本终止,IIFE 异步执行不阻塞
|
|
140
|
+
// 不写 return,让脚本自然结束(IIFE 完成后进程也退了)
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// ─── HTTP Server 模式 ────────────────────────────────────
|
|
144
|
+
const app = express();
|
|
145
|
+
// Middleware
|
|
146
|
+
app.use(helmet());
|
|
147
|
+
app.use(compression());
|
|
148
|
+
// CORS 配置 - 允许所有内网地址
|
|
149
|
+
const corsOptions = {
|
|
150
|
+
origin: (origin, callback) => {
|
|
151
|
+
// 允许没有 origin 的请求(如移动端或同源请求)
|
|
152
|
+
if (!origin) {
|
|
153
|
+
return callback(null, true);
|
|
154
|
+
}
|
|
155
|
+
// 内网地址正则
|
|
156
|
+
const lanPatterns = [
|
|
157
|
+
/^http:\/\/localhost(:\d+)?$/,
|
|
158
|
+
/^http:\/\/127\.\d+\.\d+\.\d+(:\d+)?$/,
|
|
159
|
+
/^http:\/\/10\.\d+\.\d+\.\d+(:\d+)?$/,
|
|
160
|
+
/^http:\/\/172\.(1[6-9]|2\d|3[01])\.\d+\.\d+(:\d+)?$/,
|
|
161
|
+
/^http:\/\/192\.168\.\d+\.\d+(:\d+)?$/,
|
|
162
|
+
/^http:\/\/\d+\.\d+\.\d+\.\d+(:\d+)?$/,
|
|
163
|
+
/\.local(:\d+)?$/,
|
|
164
|
+
];
|
|
165
|
+
if (lanPatterns.some(pattern => pattern.test(origin))) {
|
|
166
|
+
return callback(null, true);
|
|
167
|
+
}
|
|
168
|
+
// 检查自定义域名(支持子域名匹配)
|
|
169
|
+
const customDomains = persistStore.getCorsDomains();
|
|
170
|
+
const originLower = origin.toLowerCase();
|
|
171
|
+
if (customDomains.some(d => {
|
|
172
|
+
const dLower = d.toLowerCase();
|
|
173
|
+
return originLower === dLower || originLower.endsWith('.' + dLower);
|
|
174
|
+
})) {
|
|
175
|
+
return callback(null, true);
|
|
176
|
+
}
|
|
177
|
+
callback(new Error('Not allowed by CORS'));
|
|
178
|
+
},
|
|
179
|
+
credentials: true,
|
|
180
|
+
};
|
|
181
|
+
app.use(cors(corsOptions));
|
|
182
|
+
app.use(express.json({ limit: '10mb' }));
|
|
183
|
+
// Health check
|
|
184
|
+
app.get('/health', (req, res) => {
|
|
185
|
+
res.json({ status: 'ok', service: 'gateway', version: '2.0.0', wsOnline: webSocketService.getOnlineCount() });
|
|
186
|
+
});
|
|
187
|
+
// Routes
|
|
188
|
+
app.use('/api/v1/auth', authRoutes);
|
|
189
|
+
app.use('/api/v1/agent', agentRoutes);
|
|
190
|
+
app.use('/api/v1/models', modelsRoutes);
|
|
191
|
+
app.use('/api/v1/skills', skillsRoutes);
|
|
192
|
+
app.use('/api/v1/skill-hubs', skillHubRoutes);
|
|
193
|
+
app.use('/api/v1/chats', chatRoutes);
|
|
194
|
+
app.use('/api/v1/config', configRoutes);
|
|
195
|
+
app.use('/api/v1/settings', settingsRoutes);
|
|
196
|
+
app.use('/api/v1/service', serviceRoutes);
|
|
197
|
+
app.use('/api/v1/tasks', tasksRoutes);
|
|
198
|
+
app.use('/api/v1/upload', uploadRoutes);
|
|
199
|
+
app.use('/api/v1/version', versionRoutes);
|
|
200
|
+
// Load auth from persistent storage on startup
|
|
201
|
+
authStore.load();
|
|
202
|
+
// Error handler
|
|
203
|
+
app.use(errorHandler);
|
|
204
|
+
// 创建 HTTP Server(而非 app.listen,以便挂载 WebSocket)
|
|
205
|
+
const server = http.createServer(app);
|
|
206
|
+
// 初始化 WebSocket 服务
|
|
207
|
+
webSocketService.initialize(server);
|
|
208
|
+
// 启动任务调度器
|
|
209
|
+
taskSchedulerService.start();
|
|
210
|
+
// 端口自动顺延:被占用时自动尝试下一个端口
|
|
211
|
+
const startServer = (port) => {
|
|
212
|
+
server.listen(port, () => {
|
|
213
|
+
// 动态更新 appConfig.port(供 WebSocket 广播实际端口)
|
|
214
|
+
appConfig.port = port;
|
|
215
|
+
if (port !== appConfig.port) {
|
|
216
|
+
logger.info(`Port ${appConfig.port} is in use, fallback to port ${port}`);
|
|
217
|
+
}
|
|
218
|
+
logger.info(`我的助手 Gateway Service running on port ${port}`);
|
|
219
|
+
}).on('error', (err) => {
|
|
220
|
+
if (err.code === 'EADDRINUSE') {
|
|
221
|
+
const nextPort = port + 1;
|
|
222
|
+
if (nextPort <= (appConfig.port + 10)) {
|
|
223
|
+
logger.warn(`Port ${port} is in use, trying ${nextPort}...`);
|
|
224
|
+
startServer(nextPort);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
logger.error(`All ports from ${appConfig.port} to ${appConfig.port + 10} are in use`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
logger.error(`Server error: ${err.message}`);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
startServer(appConfig.port);
|
|
238
|
+
// 优雅关闭
|
|
239
|
+
process.on('SIGTERM', () => {
|
|
240
|
+
logger.info('收到 SIGTERM,正在关闭...');
|
|
241
|
+
webSocketService.shutdown();
|
|
242
|
+
taskSchedulerService.stop();
|
|
243
|
+
server.close(() => {
|
|
244
|
+
logger.info('服务已关闭');
|
|
245
|
+
process.exit(0);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
process.on('SIGINT', () => {
|
|
249
|
+
logger.info('收到 SIGINT,正在关闭...');
|
|
250
|
+
webSocketService.shutdown();
|
|
251
|
+
taskSchedulerService.stop();
|
|
252
|
+
server.close(() => {
|
|
253
|
+
logger.info('服务已关闭');
|
|
254
|
+
process.exit(0);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
@@ -4,6 +4,30 @@ import fs from 'fs';
|
|
|
4
4
|
import { getLogger } from '@myassis/shared';
|
|
5
5
|
import { MigrationManager } from './MigrationManager.js';
|
|
6
6
|
const logger = getLogger('SessionStore');
|
|
7
|
+
export function getDataDir() {
|
|
8
|
+
const execDir = path.dirname(process.execPath);
|
|
9
|
+
const dataDir = path.join(execDir, 'gateway', 'data');
|
|
10
|
+
if (fs.existsSync(dataDir)) {
|
|
11
|
+
return dataDir;
|
|
12
|
+
}
|
|
13
|
+
const defaultDataDir = path.join(process.cwd(), 'data');
|
|
14
|
+
if (!fs.existsSync(defaultDataDir)) {
|
|
15
|
+
fs.mkdirSync(defaultDataDir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
return defaultDataDir;
|
|
18
|
+
}
|
|
19
|
+
export function getDbPath() {
|
|
20
|
+
const dataDir = getDataDir();
|
|
21
|
+
return path.join(dataDir, 'sessions.db');
|
|
22
|
+
}
|
|
23
|
+
export function getMigrationsDir() {
|
|
24
|
+
const execDir = path.dirname(process.execPath);
|
|
25
|
+
const migrationsDir = path.join(execDir, 'gateway', 'migrations');
|
|
26
|
+
if (fs.existsSync(migrationsDir)) {
|
|
27
|
+
return migrationsDir;
|
|
28
|
+
}
|
|
29
|
+
return path.join(process.cwd(), 'migrations');
|
|
30
|
+
}
|
|
7
31
|
/**
|
|
8
32
|
* SessionStore - Data access layer
|
|
9
33
|
* Responsible for direct SQLite operations
|
|
@@ -12,14 +36,10 @@ export class SessionStore {
|
|
|
12
36
|
db;
|
|
13
37
|
migrationManager;
|
|
14
38
|
constructor() {
|
|
15
|
-
const
|
|
16
|
-
if (!fs.existsSync(dataDir)) {
|
|
17
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
18
|
-
}
|
|
19
|
-
const dbPath = path.join(dataDir, 'sessions.db');
|
|
39
|
+
const dbPath = getDbPath();
|
|
20
40
|
this.db = new Database(dbPath);
|
|
21
41
|
this.db.pragma('foreign_keys = ON');
|
|
22
|
-
const migrationsDir =
|
|
42
|
+
const migrationsDir = getMigrationsDir();
|
|
23
43
|
this.migrationManager = new MigrationManager(this.db, migrationsDir);
|
|
24
44
|
this.migrationManager.migrate();
|
|
25
45
|
logger.info(`Initialized at ${dbPath}`);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
-- 创建 agents 表
|
|
2
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
3
|
+
id TEXT PRIMARY KEY,
|
|
4
|
+
user_id TEXT NOT NULL,
|
|
5
|
+
name TEXT NOT NULL,
|
|
6
|
+
description TEXT,
|
|
7
|
+
system_prompt TEXT,
|
|
8
|
+
avatar TEXT,
|
|
9
|
+
config TEXT,
|
|
10
|
+
created_at INTEGER NOT NULL,
|
|
11
|
+
updated_at INTEGER NOT NULL
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
-- 为 sessions 表添加 agent_id 外键
|
|
15
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
16
|
+
id TEXT PRIMARY KEY,
|
|
17
|
+
user_id TEXT NOT NULL,
|
|
18
|
+
agent_id TEXT,
|
|
19
|
+
title TEXT NOT NULL,
|
|
20
|
+
select_model_id TEXT,
|
|
21
|
+
voice_state TEXT,
|
|
22
|
+
created_at INTEGER NOT NULL,
|
|
23
|
+
updated_at INTEGER NOT NULL,
|
|
24
|
+
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
-- 创建 messages 表的 tool_calls 列
|
|
28
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
session_id TEXT NOT NULL,
|
|
31
|
+
role TEXT NOT NULL,
|
|
32
|
+
content TEXT,
|
|
33
|
+
attachments TEXT DEFAULT '[]',
|
|
34
|
+
tool_calls TEXT DEFAULT '[]',
|
|
35
|
+
created_at INTEGER NOT NULL,
|
|
36
|
+
updated_at INTEGER NOT NULL,
|
|
37
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
38
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
alter table messages add column model_name text default null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
alter table sessions add column unread_count integer default 0 not null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Add is_current column to agents table
|
|
2
|
+
ALTER TABLE agents ADD COLUMN is_current INTEGER DEFAULT 0;
|
|
3
|
+
|
|
4
|
+
-- Set the first agent (by created_at) as current for each user
|
|
5
|
+
UPDATE agents
|
|
6
|
+
SET is_current = 1
|
|
7
|
+
WHERE id IN (
|
|
8
|
+
SELECT id FROM agents
|
|
9
|
+
GROUP BY user_id
|
|
10
|
+
ORDER BY created_at ASC
|
|
11
|
+
LIMIT 1
|
|
12
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
alter table sessions add column is_current INTEGER DEFAULT 0;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
alter table messages drop column tool_calls_data;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myassis/gateway",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "我的助手 Gateway Service - 本地 AI 网关服务,支持认证、WebSocket 实时通信和任务调度",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"dist",
|
|
13
13
|
"scripts",
|
|
14
14
|
"README.md",
|
|
15
|
-
"LICENSE"
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"migrations",
|
|
17
|
+
"dist/.env"
|
|
16
18
|
],
|
|
17
19
|
"exports": {
|
|
18
20
|
".": {
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
"dev": "tsx watch src/index.ts",
|
|
25
27
|
"start": "node dist/index.js",
|
|
26
28
|
"build": "tsc --skipLibCheck",
|
|
27
|
-
"postbuild": "node scripts/add-shebang.js &&
|
|
29
|
+
"postbuild": "node scripts/add-shebang.js && npx tsc-alias",
|
|
28
30
|
"pkg:win": "pkg . --targets node18-win-x64 --output myassis-gateway-win.exe",
|
|
29
31
|
"pkg:linux": "pkg . --targets node18-linux-x64 --output myassis-gateway-linux",
|
|
30
32
|
"pkg:all": "npm run pkg:win && npm run pkg:linux",
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* 最终修复脚本:将所有 @/ 路径别名替换为相对路径
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const __dirname = path.dirname(__filename);
|
|
12
|
-
|
|
13
|
-
const srcDir = path.join(__dirname, '..', 'src');
|
|
14
|
-
|
|
15
|
-
// 计算两个路径之间的相对路径
|
|
16
|
-
function getRelativePath(fromFile, toModule) {
|
|
17
|
-
// toModule: @/services/session -> src/services/session
|
|
18
|
-
const targetPath = toModule.replace(/^@\//, '');
|
|
19
|
-
const targetFullPath = path.resolve(srcDir, targetPath);
|
|
20
|
-
|
|
21
|
-
// 计算相对路径
|
|
22
|
-
let relativePath = path.relative(path.dirname(fromFile), targetFullPath);
|
|
23
|
-
|
|
24
|
-
// Windows 路径转换为 Unix 风格
|
|
25
|
-
relativePath = relativePath.replace(/\\/g, '/');
|
|
26
|
-
|
|
27
|
-
// 确保以 ./ 或 ../ 开头
|
|
28
|
-
if (!relativePath.startsWith('.')) {
|
|
29
|
-
relativePath = './' + relativePath;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// 添加 .js 扩展名(ES Module 要求)
|
|
33
|
-
if (!relativePath.endsWith('.js') && !relativePath.endsWith('.ts')) {
|
|
34
|
-
relativePath += '.js';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return relativePath;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 修复文件内容
|
|
41
|
-
function fixFileContent(filePath, content) {
|
|
42
|
-
let modified = false;
|
|
43
|
-
|
|
44
|
-
// 匹配 import ... from '@/...' 或 export ... from '@/...'
|
|
45
|
-
const importRegex = /(import|export)\s+([\s\S]+?)\s+from\s+['"]@\/([^'"]+)['"]/g;
|
|
46
|
-
|
|
47
|
-
const newContent = content.replace(importRegex, (match, keyword, imports, modulePath) => {
|
|
48
|
-
const relativePath = getRelativePath(filePath, '@/' + modulePath);
|
|
49
|
-
modified = true;
|
|
50
|
-
return `${keyword} ${imports} from '${relativePath}'`;
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
return { content: newContent, modified };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 处理单个文件
|
|
57
|
-
function processFile(filePath) {
|
|
58
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
59
|
-
const { content: newContent, modified } = fixFileContent(filePath, content);
|
|
60
|
-
|
|
61
|
-
if (modified) {
|
|
62
|
-
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
63
|
-
console.log(`✓ ${path.relative(process.cwd(), filePath)}`);
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 递归处理目录
|
|
71
|
-
function processDirectory(dir) {
|
|
72
|
-
let fixedCount = 0;
|
|
73
|
-
|
|
74
|
-
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
75
|
-
|
|
76
|
-
for (const item of items) {
|
|
77
|
-
const itemPath = path.join(dir, item.name);
|
|
78
|
-
|
|
79
|
-
if (item.isDirectory()) {
|
|
80
|
-
// 跳过特定目录
|
|
81
|
-
if (!['node_modules', 'dist', '.git', 'coverage'].includes(item.name)) {
|
|
82
|
-
fixedCount += processDirectory(itemPath);
|
|
83
|
-
}
|
|
84
|
-
} else if (item.name.endsWith('.ts') && !item.name.endsWith('.d.ts')) {
|
|
85
|
-
if (processFile(itemPath)) {
|
|
86
|
-
fixedCount++;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return fixedCount;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 主函数
|
|
95
|
-
function main() {
|
|
96
|
-
console.log('🔧 将 @/ 路径别名替换为相对路径...\n');
|
|
97
|
-
|
|
98
|
-
if (!fs.existsSync(srcDir)) {
|
|
99
|
-
console.error('❌ src 目录不存在!');
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const fixedCount = processDirectory(srcDir);
|
|
104
|
-
|
|
105
|
-
console.log(`\n✅ 完成!修复了 ${fixedCount} 个文件`);
|
|
106
|
-
console.log('\n⚠️ 重要提示:');
|
|
107
|
-
console.log(' 1. 请检查修改是否正确');
|
|
108
|
-
console.log(' 2. 运行 npm run build 验证编译');
|
|
109
|
-
console.log(' 3. 提交代码更改');
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
main();
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* 后处理 dist 目录,将 @/ 路径别名替换为相对路径
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
import { execSync } from 'child_process';
|
|
10
|
-
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
|
|
14
|
-
const distDir = path.join(__dirname, '..', 'dist');
|
|
15
|
-
const srcDir = path.join(__dirname, '..', 'src');
|
|
16
|
-
|
|
17
|
-
// 获取从当前文件到 @/ 模块的相对路径
|
|
18
|
-
function resolveAliasToRelative(filePath, aliasPath) {
|
|
19
|
-
// aliasPath: @/services/session
|
|
20
|
-
// 转换为相对于 src 的路径
|
|
21
|
-
const targetPath = aliasPath.replace(/^@\//, '');
|
|
22
|
-
const targetFullPath = path.resolve(srcDir, targetPath);
|
|
23
|
-
|
|
24
|
-
// 计算从当前 dist 文件到目标 dist 文件的相对路径
|
|
25
|
-
const currentDistFile = filePath; // filePath 已经在 dist 中
|
|
26
|
-
const targetDistPath = targetFullPath.replace(srcDir, distDir);
|
|
27
|
-
|
|
28
|
-
let relativePath = path.relative(path.dirname(currentDistFile), targetDistPath);
|
|
29
|
-
|
|
30
|
-
// 确保使用正斜杠(ES Modules 要求)
|
|
31
|
-
relativePath = relativePath.replace(/\\/g, '/');
|
|
32
|
-
|
|
33
|
-
// 确保以 ./ 或 ../ 开头
|
|
34
|
-
if (!relativePath.startsWith('.')) {
|
|
35
|
-
relativePath = './' + relativePath;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// 添加 .js 扩展名
|
|
39
|
-
if (!relativePath.endsWith('.js')) {
|
|
40
|
-
relativePath += '.js';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return relativePath;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 处理单个文件
|
|
47
|
-
function processFile(filePath) {
|
|
48
|
-
let content = fs.readFileSync(filePath, 'utf8');
|
|
49
|
-
let modified = false;
|
|
50
|
-
|
|
51
|
-
// 匹配 import ... from '@/...' 或 export ... from '@/...'
|
|
52
|
-
const importRegex = /(import|export)\s+(.+?)\s+from\s+['"]@\/([^'"]+)['"]/g;
|
|
53
|
-
|
|
54
|
-
content = content.replace(importRegex, (match, keyword, imports, modulePath) => {
|
|
55
|
-
const relativePath = resolveAliasToRelative(filePath, '@/' + modulePath);
|
|
56
|
-
modified = true;
|
|
57
|
-
return `${keyword} ${imports} from '${relativePath}'`;
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
if (modified) {
|
|
61
|
-
fs.writeFileSync(filePath, content, 'utf8');
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 递归处理目录
|
|
69
|
-
function processDirectory(dir) {
|
|
70
|
-
let fixedCount = 0;
|
|
71
|
-
|
|
72
|
-
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
73
|
-
|
|
74
|
-
for (const item of items) {
|
|
75
|
-
const itemPath = path.join(dir, item.name);
|
|
76
|
-
|
|
77
|
-
if (item.isDirectory()) {
|
|
78
|
-
// 跳过 node_modules(如果在 dist 中)
|
|
79
|
-
if (item.name !== 'node_modules') {
|
|
80
|
-
fixedCount += processDirectory(itemPath);
|
|
81
|
-
}
|
|
82
|
-
} else if (item.name.endsWith('.js')) {
|
|
83
|
-
if (processFile(itemPath)) {
|
|
84
|
-
fixedCount++;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return fixedCount;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 主函数
|
|
93
|
-
function main() {
|
|
94
|
-
console.log('🔧 修复 dist 目录中的 @/ 路径别名...\n');
|
|
95
|
-
console.log(`Dist 目录: ${distDir}\n`);
|
|
96
|
-
|
|
97
|
-
if (!fs.existsSync(distDir)) {
|
|
98
|
-
console.error('❌ dist 目录不存在!请先运行 tsc');
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const fixedCount = processDirectory(distDir);
|
|
103
|
-
|
|
104
|
-
console.log(`\n✅ 完成!修复了 ${fixedCount} 个文件`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
main();
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* 修复 dist 目录中的 ES Module 导入
|
|
4
|
-
* 为所有本地导入添加 .js 扩展名
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import fs from 'fs';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
|
-
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
|
|
14
|
-
const distDir = path.join(__dirname, '..', 'dist');
|
|
15
|
-
|
|
16
|
-
// 递归处理所有 .js 文件
|
|
17
|
-
function processFile(filePath) {
|
|
18
|
-
let content = fs.readFileSync(filePath, 'utf8');
|
|
19
|
-
let modified = false;
|
|
20
|
-
|
|
21
|
-
// 匹配 import 和 export ... from 语句
|
|
22
|
-
// 匹配模式:from './xxx' 或 from '../xxx'(不包含协议、不为 / 开头、不包含 .js)
|
|
23
|
-
const importRegex = /(?:import|export)\s+(?:[\w*{}\s,]+from\s+)?['"](\.\.?\/[^'"]+)['"]/g;
|
|
24
|
-
|
|
25
|
-
let match;
|
|
26
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
27
|
-
const importPath = match[1];
|
|
28
|
-
|
|
29
|
-
// 跳过已经有扩展名的
|
|
30
|
-
if (importPath.endsWith('.js') || importPath.endsWith('.json') || importPath.endsWith('.ts')) {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// 跳过 node_modules 中的包(不包含 . 或 .. 开头的)
|
|
35
|
-
if (!importPath.startsWith('./') && !importPath.startsWith('../')) {
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// 添加 .js 扩展名
|
|
40
|
-
const newImportPath = importPath + '.js';
|
|
41
|
-
content = content.replace(
|
|
42
|
-
new RegExp(`['"]${importPath}['"]`, 'g'),
|
|
43
|
-
`'${newImportPath}'`
|
|
44
|
-
);
|
|
45
|
-
modified = true;
|
|
46
|
-
|
|
47
|
-
console.log(` Fixed: ${importPath} -> ${newImportPath}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (modified) {
|
|
51
|
-
fs.writeFileSync(filePath, content, 'utf8');
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// 递归查找并处理所有 .js 文件
|
|
59
|
-
function processDirectory(dir) {
|
|
60
|
-
let fixedCount = 0;
|
|
61
|
-
|
|
62
|
-
const items = fs.readdirSync(dir);
|
|
63
|
-
|
|
64
|
-
items.forEach(item => {
|
|
65
|
-
const itemPath = path.join(dir, item);
|
|
66
|
-
const stat = fs.statSync(itemPath);
|
|
67
|
-
|
|
68
|
-
if (stat.isDirectory()) {
|
|
69
|
-
fixedCount += processDirectory(itemPath);
|
|
70
|
-
} else if (item.endsWith('.js')) {
|
|
71
|
-
if (processFile(itemPath)) {
|
|
72
|
-
fixedCount++;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return fixedCount;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 主函数
|
|
81
|
-
function main() {
|
|
82
|
-
console.log('🔧 修复 dist 目录中的 ES Module 导入...');
|
|
83
|
-
console.log(`目录: ${distDir}\n`);
|
|
84
|
-
|
|
85
|
-
if (!fs.existsSync(distDir)) {
|
|
86
|
-
console.error('❌ dist 目录不存在!请先运行 npm run build');
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const fixedCount = processDirectory(distDir);
|
|
91
|
-
|
|
92
|
-
console.log(`\n✅ 完成!修复了 ${fixedCount} 个文件`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
main();
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
|
-
import { readdir, readFile, writeFile, stat } from 'fs/promises';
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
-
|
|
11
|
-
// 匹配相对导入的正则:import ... from './xxx' 或 import ... from '../xxx'
|
|
12
|
-
const relativeImportRegex = /from\s+['"](\.\/[^'"]*|\\.\\.\/[^'"]*)['"]/g;
|
|
13
|
-
const dynamicImportRegex = /import\s*\(\s*['"](\.\/[^'"]*|\\.\\.\/[^'"]*)['"]\s*\)/g;
|
|
14
|
-
|
|
15
|
-
async function addJsExtensionToImports(filePath) {
|
|
16
|
-
let content = await readFile(filePath, 'utf8');
|
|
17
|
-
let modified = false;
|
|
18
|
-
|
|
19
|
-
// 替换静态导入 - 匹配各种相对路径
|
|
20
|
-
content = content.replace(/from\s+['"]([^'"\n]+)['"]/g, (match, importPath) => {
|
|
21
|
-
// 如果是 node_modules 或绝对路径,跳过
|
|
22
|
-
if (importPath.startsWith('@myassis/') || importPath.startsWith('/')) {
|
|
23
|
-
return match;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// 如果已经有扩展名,跳过
|
|
27
|
-
if (importPath.endsWith('.js') || importPath.endsWith('.json') || importPath.endsWith('.ts') || importPath.endsWith('.cjs') || importPath.endsWith('.mjs')) {
|
|
28
|
-
return match;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// 只处理相对路径 (./ 或 ../)
|
|
32
|
-
if (importPath.startsWith('.') || importPath.startsWith('..')) {
|
|
33
|
-
modified = true;
|
|
34
|
-
return `from '${importPath}.js'`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return match;
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// 替换动态导入
|
|
41
|
-
content = content.replace(dynamicImportRegex, (match, importPath) => {
|
|
42
|
-
if (importPath.endsWith('.js') || importPath.endsWith('.json') || importPath.endsWith('.ts')) {
|
|
43
|
-
return match;
|
|
44
|
-
}
|
|
45
|
-
modified = true;
|
|
46
|
-
return `import('${importPath}.js')`;
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (modified) {
|
|
50
|
-
await writeFile(filePath, content, 'utf8');
|
|
51
|
-
console.log(`✅ Fixed imports in: ${path.relative(__dirname + '/..', filePath)}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function processDirectory(dirPath) {
|
|
56
|
-
const entries = await readdir(dirPath);
|
|
57
|
-
|
|
58
|
-
for (const entry of entries) {
|
|
59
|
-
const fullPath = path.join(dirPath, entry);
|
|
60
|
-
const stats = await stat(fullPath);
|
|
61
|
-
|
|
62
|
-
if (stats.isDirectory()) {
|
|
63
|
-
await processDirectory(fullPath);
|
|
64
|
-
} else if (entry.endsWith('.js')) {
|
|
65
|
-
await addJsExtensionToImports(fullPath);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function main() {
|
|
71
|
-
const distPath = path.join(__dirname, '..', 'dist');
|
|
72
|
-
|
|
73
|
-
if (!fs.existsSync(distPath)) {
|
|
74
|
-
console.error('❌ dist directory not found!');
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
console.log('🔧 Adding .js extensions to relative imports...');
|
|
79
|
-
await processDirectory(distPath);
|
|
80
|
-
console.log('✅ Done!');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
main().catch(console.error);
|
package/scripts/fix-imports.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* 自动修复 ES Module 导入的扩展名
|
|
4
|
-
* 在 TypeScript 文件中,所有本地导入必须包含 .js 扩展名
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import fs from 'fs';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
|
-
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
|
|
14
|
-
// 递归查找所有 .ts 文件(排除 .d.ts)
|
|
15
|
-
function findTsFiles(dir) {
|
|
16
|
-
let results = [];
|
|
17
|
-
const list = fs.readdirSync(dir);
|
|
18
|
-
|
|
19
|
-
list.forEach(file => {
|
|
20
|
-
const filePath = path.join(dir, file);
|
|
21
|
-
const stat = fs.statSync(filePath);
|
|
22
|
-
|
|
23
|
-
if (stat && stat.isDirectory()) {
|
|
24
|
-
// 跳过 node_modules 和 dist
|
|
25
|
-
if (file !== 'node_modules' && file !== 'dist' && file !== '.git') {
|
|
26
|
-
results = results.concat(findTsFiles(filePath));
|
|
27
|
-
}
|
|
28
|
-
} else if (file.endsWith('.ts') && !file.endsWith('.d.ts')) {
|
|
29
|
-
results.push(filePath);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
return results;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// 修复文件中的导入语句
|
|
37
|
-
function fixImports(filePath) {
|
|
38
|
-
let content = fs.readFileSync(filePath, 'utf8');
|
|
39
|
-
let modified = false;
|
|
40
|
-
|
|
41
|
-
// 匹配 import 语句中的本地文件路径(以 ./ 或 ../ 开头,且不以 .js 或 .ts 结尾)
|
|
42
|
-
const importRegex = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
43
|
-
|
|
44
|
-
content = content.replace(importRegex, (match, importPath) => {
|
|
45
|
-
// 如果已经以 .js 或 .ts 结尾,跳过
|
|
46
|
-
if (importPath.endsWith('.js') || importPath.endsWith('.ts')) {
|
|
47
|
-
return match;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 添加 .js 扩展名
|
|
51
|
-
modified = true;
|
|
52
|
-
return `from '${importPath}.js'`;
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
if (modified) {
|
|
56
|
-
fs.writeFileSync(filePath, content, 'utf8');
|
|
57
|
-
console.log(`✓ Fixed: ${path.relative(process.cwd(), filePath)}`);
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 主函数
|
|
65
|
-
function main() {
|
|
66
|
-
const srcDir = path.join(__dirname, '..', 'src');
|
|
67
|
-
|
|
68
|
-
console.log('🔍 查找 TypeScript 文件...');
|
|
69
|
-
const tsFiles = findTsFiles(srcDir);
|
|
70
|
-
console.log(`找到 ${tsFiles.length} 个文件\n`);
|
|
71
|
-
|
|
72
|
-
console.log('🔧 修复导入语句...');
|
|
73
|
-
let fixedCount = 0;
|
|
74
|
-
|
|
75
|
-
tsFiles.forEach(file => {
|
|
76
|
-
if (fixImports(file)) {
|
|
77
|
-
fixedCount++;
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
console.log(`\n✅ 完成!修复了 ${fixedCount} 个文件`);
|
|
82
|
-
|
|
83
|
-
if (fixedCount > 0) {
|
|
84
|
-
console.log('\n⚠️ 请运行以下命令验证修改:');
|
|
85
|
-
console.log(' npm run build');
|
|
86
|
-
console.log(' node dist/index.js');
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
main();
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* 将 @/ 路径别名转换为相对路径
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const __dirname = path.dirname(__filename);
|
|
12
|
-
|
|
13
|
-
const srcDir = path.join(__dirname, '..', 'src');
|
|
14
|
-
|
|
15
|
-
// 计算从当前文件到目标文件的相对路径
|
|
16
|
-
function getRelativePath(fromFile, toPath) {
|
|
17
|
-
// toPath 例如:@/services/agent/AgentManager
|
|
18
|
-
// 转换为:src/services/agent/AgentManager
|
|
19
|
-
const targetPath = toPath.replace(/^@\//, '');
|
|
20
|
-
const targetFullPath = path.join(srcDir, targetPath);
|
|
21
|
-
|
|
22
|
-
// 计算相对路径
|
|
23
|
-
let relativePath = path.relative(path.dirname(fromFile), targetFullPath);
|
|
24
|
-
|
|
25
|
-
// 确保以 ./ 或 ../ 开头
|
|
26
|
-
if (!relativePath.startsWith('.')) {
|
|
27
|
-
relativePath = './' + relativePath;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return relativePath;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 修复文件中的 @/ 导入
|
|
34
|
-
function fixFile(filePath) {
|
|
35
|
-
let content = fs.readFileSync(filePath, 'utf8');
|
|
36
|
-
let modified = false;
|
|
37
|
-
|
|
38
|
-
// 匹配 import ... from '@/...' 或 export ... from '@/...'
|
|
39
|
-
const importRegex = /(import|export)\s+(.+?)\s+from\s+['"]@\/([^'"]+)['"]/g;
|
|
40
|
-
|
|
41
|
-
content = content.replace(importRegex, (match, keyword, imports, modulePath) => {
|
|
42
|
-
const relativePath = getRelativePath(filePath, '@/' + modulePath);
|
|
43
|
-
// 添加 .js 扩展名
|
|
44
|
-
const newPath = relativePath + '.js';
|
|
45
|
-
modified = true;
|
|
46
|
-
return `${keyword} ${imports} from '${newPath}'`;
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (modified) {
|
|
50
|
-
fs.writeFileSync(filePath, content, 'utf8');
|
|
51
|
-
console.log(`✓ Fixed: ${path.relative(process.cwd(), filePath)}`);
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// 递归处理所有 .ts 文件
|
|
59
|
-
function processDirectory(dir) {
|
|
60
|
-
let fixedCount = 0;
|
|
61
|
-
|
|
62
|
-
const items = fs.readdirSync(dir);
|
|
63
|
-
|
|
64
|
-
items.forEach(item => {
|
|
65
|
-
const itemPath = path.join(dir, item);
|
|
66
|
-
const stat = fs.statSync(itemPath);
|
|
67
|
-
|
|
68
|
-
if (stat.isDirectory()) {
|
|
69
|
-
// 跳过 node_modules 和 dist
|
|
70
|
-
if (item !== 'node_modules' && item !== 'dist' && item !== '.git') {
|
|
71
|
-
fixedCount += processDirectory(itemPath);
|
|
72
|
-
}
|
|
73
|
-
} else if (item.endsWith('.ts') && !item.endsWith('.d.ts')) {
|
|
74
|
-
if (fixFile(itemPath)) {
|
|
75
|
-
fixedCount++;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
return fixedCount;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 主函数
|
|
84
|
-
function main() {
|
|
85
|
-
console.log('🔧 将 @/ 路径别名转换为相对路径...\n');
|
|
86
|
-
|
|
87
|
-
const fixedCount = processDirectory(srcDir);
|
|
88
|
-
|
|
89
|
-
console.log(`\n✅ 完成!修复了 ${fixedCount} 个文件`);
|
|
90
|
-
console.log('\n⚠️ 请运行以下命令验证:');
|
|
91
|
-
console.log(' npm run build');
|
|
92
|
-
console.log(' node dist/index.js');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
main();
|
package/scripts/postbuild.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
const files = [
|
|
6
|
-
path.join(process.cwd(), 'dist/index.js'),
|
|
7
|
-
// 如果有其他 CLI 入口文件,也添加到这里
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
files.forEach(filePath => {
|
|
11
|
-
if (!fs.existsSync(filePath)) {
|
|
12
|
-
console.warn(`Warning: ${filePath} does not exist, skipping...`);
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let content = fs.readFileSync(filePath, 'utf8');
|
|
17
|
-
|
|
18
|
-
// 检查是否已有 shebang
|
|
19
|
-
if (!content.startsWith('#!/usr/bin/env node')) {
|
|
20
|
-
// 添加 shebang
|
|
21
|
-
content = '#!/usr/bin/env node\n' + content;
|
|
22
|
-
fs.writeFileSync(filePath, content, 'utf8');
|
|
23
|
-
console.log(`✓ Added shebang to ${path.relative(process.cwd(), filePath)}`);
|
|
24
|
-
} else {
|
|
25
|
-
console.log(`✓ Shebang already exists in ${path.relative(process.cwd(), filePath)}`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// 设置可执行权限(Unix/Linux/macOS)
|
|
29
|
-
if (process.platform !== 'win32') {
|
|
30
|
-
try {
|
|
31
|
-
fs.chmodSync(filePath, '755');
|
|
32
|
-
console.log(`✓ Set executable permission for ${path.relative(process.cwd(), filePath)}`);
|
|
33
|
-
} catch (err) {
|
|
34
|
-
console.warn(`Warning: Could not set executable permission for ${filePath}`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
console.log('\n✓ Postbuild script completed!');
|