This commit is contained in:
li1553770945 2025-06-13 21:27:37 +08:00
parent bd23bfe17c
commit e7856dd8a6
2 changed files with 112 additions and 123 deletions

View File

@ -1,12 +1,11 @@
import { WebSocketServer } from 'ws'; import { WebSocketServer } from 'ws';
import {pino} from 'pino'; import { pino } from 'pino';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { dirname } from 'path'; import { dirname, join } from 'path';
import { routeMessage } from './common/router.js'; import { routeMessage } from './common/router.js';
import { VSCodeWebViewLike } from './hook/adapter.js'; import { VSCodeWebViewLike } from './hook/adapter.js';
import path from 'node:path'; import fs from 'fs/promises'; // 使用 Promise API 替代回调
import * as fs from 'node:fs'; import path from 'path';
import { setRunningCWD } from './hook/setting.js';
import axios from 'axios'; import axios from 'axios';
export interface VSCodeMessage { export interface VSCodeMessage {
@ -15,91 +14,91 @@ export interface VSCodeMessage {
callbackId?: string; 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({ const logger = pino({
transport: { transport: {
target: 'pino-pretty', // 启用 pino-pretty target: 'pino-pretty',
options: { options: {
colorize: true, // 开启颜色 colorize: true,
levelFirst: true, // 先打印日志级别 levelFirst: true,
translateTime: 'SYS:yyyy-mm-dd HH:MM:ss', // 格式化时间 translateTime: 'SYS:yyyy-mm-dd HH:MM:ss',
ignore: 'pid,hostname', // 忽略部分字段 ignore: 'pid,hostname',
} }
} }
}); });
export type MessageHandler = (message: VSCodeMessage) => void; export type MessageHandler = (message: VSCodeMessage) => void;
function refreshConnectionOption(envPath: string) { async function refreshConnectionOption() {
const serverPath = path.join(__dirname, '..', '..', 'servers');
const defaultOption = { const defaultOption = {
connectionType: 'STDIO', connectionType: 'STDIO',
commandString: 'mcp run main.py', commandString: 'mcp run main.py',
cwd: serverPath cwd: serverPath
}; };
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4)); try {
await fs.writeFile(envPath, JSON.stringify(defaultOption, null, 4), 'utf-8');
return { items: [ defaultOption ] }; return { items: [defaultOption] };
} catch (error) {
logger.error('刷新连接配置失败:', error);
throw error;
}
} }
function acquireConnectionOption() { async function acquireConnectionOption() {
const envPath = path.join(__dirname, '..', '.env');
if (!fs.existsSync(envPath)) {
return refreshConnectionOption(envPath);
}
try { 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) { if (!option.items || option.items.length === 0) {
return refreshConnectionOption(envPath); return await refreshConnectionOption();
}
if (option.items && option.items.length === 0) {
return refreshConnectionOption(envPath);
} }
// 按照前端的规范,整理成 commandString 样式 // 按照前端的规范,整理成 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') { if (item.connectionType === 'STDIO') {
item.commandString = [item.command, ...item.args]?.join(' '); item.commandString = [item.command, ...item.args]?.join(' ');
} else { } else {
item.url = item.url; item.url = item.url;
} }
return item; return item;
}); });
return option; return option;
} catch (error) { } catch (error) {
logger.error('读取 .env 配置文件'); logger.error('读取 .env 配置文件失败:', error);
return refreshConnectionOption(envPath); return await refreshConnectionOption();
} }
} }
function updateConnectionOption(data: any) { async function updateConnectionOption(data: any) {
const envPath = path.join(__dirname, '..', '.env'); try {
const connection = { items: data }; await fs.writeFile(envPath, JSON.stringify({ items: data }, null, 4), 'utf-8');
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4)); } 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); setRunningCWD(devHome);
// 启动 WebSocket 服务器
const wss = new WebSocketServer({ port: 8282 }); const wss = new WebSocketServer({ port: 8282 });
console.log('WebSocket 服务器已启动:', 'ws://localhost:8282');
console.log('listen on ws://localhost:8282'); wss.on('connection', (ws) => {
wss.on('connection', (ws: any) => {
// 仿造 webview 进行统一接口访问
const webview = new VSCodeWebViewLike(ws); const webview = new VSCodeWebViewLike(ws);
// 先发送成功建立的消息
webview.postMessage({ webview.postMessage({
command: 'hello', command: 'hello',
data: { data: {
@ -108,34 +107,33 @@ wss.on('connection', (ws: any) => {
} }
}); });
const option = acquireConnectionOption(); acquireConnectionOption().then(option => {
webview.onDidReceiveMessage(async (message) => {
logger.info(`收到命令: [${message.command || '未定义'}]`);
// 注册消息接受的管线 const { command, data } = message;
webview.onDidReceiveMessage(message => {
logger.info(`command: [${message.command || 'No Command'}]`);
const { command, data } = message;
switch (command) { switch (command) {
case 'web/launch-signature': case 'web/launch-signature':
const launchResult = { webview.postMessage({
code: 200, command: 'web/launch-signature',
msg: option.items data: {
}; code: 200,
msg: option.items
}
});
break;
webview.postMessage({ case 'web/update-connection-signature':
command: 'web/launch-signature', await updateConnectionOption(data);
data: launchResult break;
});
break; default:
routeMessage(command, data, webview);
case 'web/update-connection-signature': break;
updateConnectionOption(data); }
break; });
}).catch(error => {
default: logger.error('初始化连接配置失败:', error);
routeMessage(command, data, webview);
break;
}
}); });
}); });

View File

@ -1,12 +1,17 @@
import { WebSocketServer } from 'ws'; import { WebSocketServer } from 'ws';
import pino from 'pino'; import pino from 'pino';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { routeMessage } from './common/router.js'; import { routeMessage } from './common/router.js';
import { VSCodeWebViewLike } from './hook/adapter.js'; import { VSCodeWebViewLike } from './hook/adapter.js';
import path from 'node:path'; import fs from 'fs';
import * as fs from 'node:fs'; import path from 'path';
import { setRunningCWD } from './hook/setting.js'; 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 { export interface VSCodeMessage {
command: string; command: string;
@ -16,12 +21,12 @@ export interface VSCodeMessage {
const logger = pino.default({ const logger = pino.default({
transport: { transport: {
target: 'pino-pretty', // 启用 pino-pretty target: 'pino-pretty',
options: { options: {
colorize: true, // 开启颜色 colorize: true,
levelFirst: true, // 先打印日志级别 levelFirst: true,
translateTime: 'SYS:yyyy-mm-dd HH:MM:ss', // 格式化时间 translateTime: 'SYS:yyyy-mm-dd HH:MM:ss',
ignore: 'pid,hostname', // 忽略部分字段 ignore: 'pid,hostname',
} }
} }
}); });
@ -29,8 +34,7 @@ const logger = pino.default({
export type MessageHandler = (message: VSCodeMessage) => void; export type MessageHandler = (message: VSCodeMessage) => void;
function refreshConnectionOption(envPath: string) { function refreshConnectionOption(envPath: string) {
const serverPath = path.join(__dirname, '..', '..', 'servers'); const serverPath = join(__dirname, '..', '..', 'servers');
const defaultOption = { const defaultOption = {
connectionType: 'STDIO', connectionType: 'STDIO',
commandString: 'mcp run main.py', commandString: 'mcp run main.py',
@ -38,12 +42,11 @@ function refreshConnectionOption(envPath: string) {
}; };
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4)); fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
return { items: [defaultOption] }; return { items: [defaultOption] };
} }
function acquireConnectionOption() { function acquireConnectionOption() {
const envPath = path.join(__dirname, '..', '.env'); const envPath = join(__dirname, '..', '.env');
if (!fs.existsSync(envPath)) { if (!fs.existsSync(envPath)) {
return refreshConnectionOption(envPath); return refreshConnectionOption(envPath);
@ -52,11 +55,7 @@ function acquireConnectionOption() {
try { try {
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8')); const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
if (!option.items) { if (!option.items || option.items.length === 0) {
return refreshConnectionOption(envPath);
}
if (option.items && option.items.length === 0) {
return refreshConnectionOption(envPath); return refreshConnectionOption(envPath);
} }
@ -67,7 +66,6 @@ function acquireConnectionOption() {
} else { } else {
item.url = item.url; item.url = item.url;
} }
return item; 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 不存在!'); console.log('.env.website.local 不存在!');
exit(0); 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) { function updateConnectionOption(data: any) {
const envPath = path.join(__dirname, '..', '.env'); const envPath = join(__dirname, '..', '.env');
const connection = { items: data }; fs.writeFileSync(envPath, JSON.stringify({ items: data }, null, 4));
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
} }
const devHome = path.join(__dirname, '..', '..'); const devHome = join(__dirname, '..', '..');
setRunningCWD(devHome); setRunningCWD(devHome);
function verifyToken(url: string) { function verifyToken(url: string) {
@ -104,30 +104,25 @@ function verifyToken(url: string) {
} }
} }
const wss = new WebSocketServer( const wss = new WebSocketServer({
{ port: 8282,
port: 8282, verifyClient: (info, callback) => {
verifyClient: (info, callback) => { const url = info.req.url || '';
console.log(info.req.url); const ok = verifyToken(url);
const ok = verifyToken(info.req.url || '');
if (!ok) {
if (!ok) { callback(false, 401, 'Unauthorized: Invalid token');
callback(false, 401, 'Unauthorized: Invalid token'); } else {
} else { callback(true);
callback(true); // 允许连接
}
} }
}, }
); });
console.log('listen on ws://localhost:8282'); console.log('listen on ws://localhost:8282');
wss.on('connection', (ws: any) => { wss.on('connection', (ws) => {
// 仿造 webview 进行统一接口访问
const webview = new VSCodeWebViewLike(ws); const webview = new VSCodeWebViewLike(ws);
// 先发送成功建立的消息
webview.postMessage({ webview.postMessage({
command: 'hello', command: 'hello',
data: { data: {
@ -138,23 +133,19 @@ wss.on('connection', (ws: any) => {
const option = acquireConnectionOption(); const option = acquireConnectionOption();
// 注册消息接受的管线
webview.onDidReceiveMessage(message => { webview.onDidReceiveMessage(message => {
logger.info(`command: [${message.command || 'No Command'}]`); logger.info(`command: [${message.command || 'No Command'}]`);
const { command, data } = message; const { command, data } = message;
switch (command) { switch (command) {
case 'web/launch-signature': case 'web/launch-signature':
const launchResult = {
code: 200,
msg: option.items
};
webview.postMessage({ webview.postMessage({
command: 'web/launch-signature', command: 'web/launch-signature',
data: launchResult data: {
code: 200,
msg: option.items
}
}); });
break; break;
case 'web/update-connection-signature': case 'web/update-connection-signature':