diff --git a/service/src/main.ts b/service/src/main.ts index 5063957..aabdf3b 100644 --- a/service/src/main.ts +++ b/service/src/main.ts @@ -1,12 +1,11 @@ import { WebSocketServer } from 'ws'; -import {pino} from 'pino'; +import { pino } from 'pino'; import { fileURLToPath } from 'url'; -import { dirname } from 'path'; +import { dirname, join } from 'path'; import { routeMessage } from './common/router.js'; import { VSCodeWebViewLike } from './hook/adapter.js'; -import path from 'node:path'; -import * as fs from 'node:fs'; -import { setRunningCWD } from './hook/setting.js'; +import fs from 'fs/promises'; // 使用 Promise API 替代回调 +import path from 'path'; import axios from 'axios'; export interface VSCodeMessage { @@ -15,91 +14,91 @@ export interface VSCodeMessage { callbackId?: string; } +// 适配 ESM 的 __dirname +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// 统一路径变量 +const devHome = join(__dirname, '..', '..'); +const serverPath = join(devHome, 'servers'); +const envPath = join(__dirname, '..', '.env'); + const logger = pino({ transport: { - target: 'pino-pretty', // 启用 pino-pretty + target: 'pino-pretty', options: { - colorize: true, // 开启颜色 - levelFirst: true, // 先打印日志级别 - translateTime: 'SYS:yyyy-mm-dd HH:MM:ss', // 格式化时间 - ignore: 'pid,hostname', // 忽略部分字段 + colorize: true, + levelFirst: true, + translateTime: 'SYS:yyyy-mm-dd HH:MM:ss', + ignore: 'pid,hostname', } } }); export type MessageHandler = (message: VSCodeMessage) => void; -function refreshConnectionOption(envPath: string) { - const serverPath = path.join(__dirname, '..', '..', 'servers'); - +async function refreshConnectionOption() { const defaultOption = { connectionType: 'STDIO', commandString: 'mcp run main.py', cwd: serverPath }; - fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4)); - - return { items: [ defaultOption ] }; + try { + await fs.writeFile(envPath, JSON.stringify(defaultOption, null, 4), 'utf-8'); + return { items: [defaultOption] }; + } catch (error) { + logger.error('刷新连接配置失败:', error); + throw error; + } } -function acquireConnectionOption() { - const envPath = path.join(__dirname, '..', '.env'); - - if (!fs.existsSync(envPath)) { - return refreshConnectionOption(envPath); - } - +async function acquireConnectionOption() { try { - const option = JSON.parse(fs.readFileSync(envPath, 'utf-8')); + const data = await fs.readFile(envPath, 'utf-8'); + const option = JSON.parse(data); - if (!option.items) { - return refreshConnectionOption(envPath); - } - - if (option.items && option.items.length === 0) { - return refreshConnectionOption(envPath); + if (!option.items || option.items.length === 0) { + return await refreshConnectionOption(); } // 按照前端的规范,整理成 commandString 样式 - option.items = option.items.map((item: any) => { + option.items = option.items.map((item: { connectionType: string; commandString: string; command: any; args: any; url: any; }) => { if (item.connectionType === 'STDIO') { item.commandString = [item.command, ...item.args]?.join(' '); } else { item.url = item.url; } - return item; }); return option; - } catch (error) { - logger.error('读取 .env 配置文件'); - return refreshConnectionOption(envPath); + logger.error('读取 .env 配置文件失败:', error); + return await refreshConnectionOption(); } } -function updateConnectionOption(data: any) { - const envPath = path.join(__dirname, '..', '.env'); - const connection = { items: data }; - fs.writeFileSync(envPath, JSON.stringify(connection, null, 4)); +async function updateConnectionOption(data: any) { + try { + await fs.writeFile(envPath, JSON.stringify({ items: data }, null, 4), 'utf-8'); + } catch (error) { + logger.error('更新连接配置失败:', error); + throw error; + } } -const __dirname = dirname(fileURLToPath(import.meta.url)); -const devHome = path.join(__dirname, '..', '..'); +// 设置运行时路径 +import { setRunningCWD } from './hook/setting.js'; setRunningCWD(devHome); +// 启动 WebSocket 服务器 const wss = new WebSocketServer({ port: 8282 }); +console.log('WebSocket 服务器已启动:', 'ws://localhost:8282'); -console.log('listen on ws://localhost:8282'); - -wss.on('connection', (ws: any) => { - - // 仿造 webview 进行统一接口访问 +wss.on('connection', (ws) => { const webview = new VSCodeWebViewLike(ws); - // 先发送成功建立的消息 webview.postMessage({ command: 'hello', data: { @@ -108,34 +107,33 @@ wss.on('connection', (ws: any) => { } }); - const option = acquireConnectionOption(); + acquireConnectionOption().then(option => { + webview.onDidReceiveMessage(async (message) => { + logger.info(`收到命令: [${message.command || '未定义'}]`); - // 注册消息接受的管线 - webview.onDidReceiveMessage(message => { - logger.info(`command: [${message.command || 'No Command'}]`); - const { command, data } = message; + const { command, data } = message; - switch (command) { - case 'web/launch-signature': - const launchResult = { - code: 200, - msg: option.items - }; + switch (command) { + case 'web/launch-signature': + webview.postMessage({ + command: 'web/launch-signature', + data: { + code: 200, + msg: option.items + } + }); + break; - webview.postMessage({ - command: 'web/launch-signature', - data: launchResult - }); + case 'web/update-connection-signature': + await updateConnectionOption(data); + break; - break; - - case 'web/update-connection-signature': - updateConnectionOption(data); - break; - - default: - routeMessage(command, data, webview); - break; - } + default: + routeMessage(command, data, webview); + break; + } + }); + }).catch(error => { + logger.error('初始化连接配置失败:', error); }); }); \ No newline at end of file diff --git a/service/src/server.ts b/service/src/server.ts index f20c0a5..2d26fd2 100644 --- a/service/src/server.ts +++ b/service/src/server.ts @@ -1,12 +1,17 @@ import { WebSocketServer } from 'ws'; import pino from 'pino'; - +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; import { routeMessage } from './common/router.js'; import { VSCodeWebViewLike } from './hook/adapter.js'; -import path from 'node:path'; -import * as fs from 'node:fs'; +import fs from 'fs'; +import path from 'path'; import { setRunningCWD } from './hook/setting.js'; -import { exit } from 'node:process'; +import { exit } from 'process'; + +// 适配 ESM 的 __dirname +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); export interface VSCodeMessage { command: string; @@ -16,12 +21,12 @@ export interface VSCodeMessage { const logger = pino.default({ transport: { - target: 'pino-pretty', // 启用 pino-pretty + target: 'pino-pretty', options: { - colorize: true, // 开启颜色 - levelFirst: true, // 先打印日志级别 - translateTime: 'SYS:yyyy-mm-dd HH:MM:ss', // 格式化时间 - ignore: 'pid,hostname', // 忽略部分字段 + colorize: true, + levelFirst: true, + translateTime: 'SYS:yyyy-mm-dd HH:MM:ss', + ignore: 'pid,hostname', } } }); @@ -29,8 +34,7 @@ const logger = pino.default({ export type MessageHandler = (message: VSCodeMessage) => void; function refreshConnectionOption(envPath: string) { - const serverPath = path.join(__dirname, '..', '..', 'servers'); - + const serverPath = join(__dirname, '..', '..', 'servers'); const defaultOption = { connectionType: 'STDIO', commandString: 'mcp run main.py', @@ -38,12 +42,11 @@ function refreshConnectionOption(envPath: string) { }; fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4)); - return { items: [defaultOption] }; } function acquireConnectionOption() { - const envPath = path.join(__dirname, '..', '.env'); + const envPath = join(__dirname, '..', '.env'); if (!fs.existsSync(envPath)) { return refreshConnectionOption(envPath); @@ -52,11 +55,7 @@ function acquireConnectionOption() { try { const option = JSON.parse(fs.readFileSync(envPath, 'utf-8')); - if (!option.items) { - return refreshConnectionOption(envPath); - } - - if (option.items && option.items.length === 0) { + if (!option.items || option.items.length === 0) { return refreshConnectionOption(envPath); } @@ -67,7 +66,6 @@ function acquireConnectionOption() { } else { item.url = item.url; } - return item; }); @@ -79,20 +77,22 @@ function acquireConnectionOption() { } } -if (!fs.existsSync(path.join(__dirname, '..', '.env.website.local'))) { +// 验证 .env.website.local 存在性 +const localEnvPath = join(__dirname, '..', '.env.website.local'); +if (!fs.existsSync(localEnvPath)) { console.log('.env.website.local 不存在!'); exit(0); } -const authPassword = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.env.website.local'), 'utf-8')).password; +// 读取认证密码 +const authPassword = JSON.parse(fs.readFileSync(localEnvPath, 'utf-8')).password; function updateConnectionOption(data: any) { - const envPath = path.join(__dirname, '..', '.env'); - const connection = { items: data }; - fs.writeFileSync(envPath, JSON.stringify(connection, null, 4)); + const envPath = join(__dirname, '..', '.env'); + fs.writeFileSync(envPath, JSON.stringify({ items: data }, null, 4)); } -const devHome = path.join(__dirname, '..', '..'); +const devHome = join(__dirname, '..', '..'); setRunningCWD(devHome); function verifyToken(url: string) { @@ -104,30 +104,25 @@ function verifyToken(url: string) { } } -const wss = new WebSocketServer( - { - port: 8282, - verifyClient: (info, callback) => { - console.log(info.req.url); - const ok = verifyToken(info.req.url || ''); - - if (!ok) { - callback(false, 401, 'Unauthorized: Invalid token'); - } else { - callback(true); // 允许连接 - } +const wss = new WebSocketServer({ + port: 8282, + verifyClient: (info, callback) => { + const url = info.req.url || ''; + const ok = verifyToken(url); + + if (!ok) { + callback(false, 401, 'Unauthorized: Invalid token'); + } else { + callback(true); } - }, -); + } +}); console.log('listen on ws://localhost:8282'); -wss.on('connection', (ws: any) => { - - // 仿造 webview 进行统一接口访问 +wss.on('connection', (ws) => { const webview = new VSCodeWebViewLike(ws); - // 先发送成功建立的消息 webview.postMessage({ command: 'hello', data: { @@ -138,23 +133,19 @@ wss.on('connection', (ws: any) => { const option = acquireConnectionOption(); - // 注册消息接受的管线 webview.onDidReceiveMessage(message => { logger.info(`command: [${message.command || 'No Command'}]`); const { command, data } = message; switch (command) { case 'web/launch-signature': - const launchResult = { - code: 200, - msg: option.items - }; - webview.postMessage({ command: 'web/launch-signature', - data: launchResult + data: { + code: 200, + msg: option.items + } }); - break; case 'web/update-connection-signature':