文档: 整合开发计划文档 & 修复: TypeScript 编译及可靠性改进

This commit is contained in:
火焰库拉
2026-02-03 19:55:51 +08:00
parent 5c1605c9f1
commit 720b38e1ff
21 changed files with 3148 additions and 2501 deletions

107
src/IpcManager.ts Normal file
View File

@@ -0,0 +1,107 @@
// @ts-ignore
const fs = require('fs');
// @ts-ignore
const path = require('path');
/**
* IPC 消息管理器
* 负责解析 IPC 文档并执行消息测试
*/
export class IpcManager {
/**
* 获取所有 IPC 消息列表
* @returns 消息定义列表
*/
public static getIpcMessages(): any[] {
// 获取文档路径
// @ts-ignore
const docPath = Editor.url('packages://mcp-bridge/IPC_MESSAGES.md');
if (!fs.existsSync(docPath)) {
// @ts-ignore
Editor.error(`[IPC Manager] Document not found: ${docPath}`);
return [];
}
const content = fs.readFileSync(docPath, 'utf-8');
const messages: any[] = [];
// 正则匹配 ### `message-name`
const regex = /### `(.*?)`\r?\n([\s\S]*?)(?=### `|$)/g;
let match;
while ((match = regex.exec(content)) !== null) {
const name = match[1];
const body = match[2];
// 解析用途
const purposeMatch = body.match(/- \*\*用途\*\*: (.*)/);
const description = purposeMatch ? purposeMatch[1].trim() : "无描述";
// 解析参数
const paramsMatch = body.match(/- \*\*参数\*\*: (.*)/);
const params = paramsMatch ? paramsMatch[1].trim() : "无";
// 解析返回值
const returnMatch = body.match(/- \*\*返回值\*\*: (.*)/);
const returns = returnMatch ? returnMatch[1].trim() : "无";
// 解析类型
const typeMatch = body.match(/- \*\*类型\*\*: (.*)/);
const type = typeMatch ? typeMatch[1].trim() : "未定义";
// 解析状态
const statusMatch = body.match(/- \*\*状态\*\*: (.*)/);
const status = statusMatch ? statusMatch[1].trim() : "未测试";
// 过滤掉广播事件和渲染进程监听的事件
if (type === "广播事件" || type === "Events listened by Renderer Process" || type === "渲染进程监听") {
continue;
}
messages.push({
name,
description,
params,
returns,
type,
status
});
}
return messages;
}
/**
* 测试发送 IPC 消息
* @param name 消息名称
* @param args 参数
* @returns Promise<any> 测试结果
*/
public static async testIpcMessage(name: string, args: any = null): Promise<any> {
return new Promise((resolve) => {
// 简单防呆:防止执行危险操作
// 如果消息包含 "delete", "remove", "close", "stop" 且没有明确参数确认,则警告
// 但用户要求"快速验证",所以我们默认允许,但如果是无参调用可能有风险
// 这里我们尝试使用 Editor.Ipc.sendToMain 或 requestToMain
// @ts-ignore
// 简单的测试:只是发送看看是否报错。
// 对于 request 类型的消息,我们期望有回调
// Cocos Creator 2.4 API: Editor.Ipc.sendToMain(message, ...args)
try {
// @ts-ignore
if (Editor.Ipc.sendToMain) {
// @ts-ignore
Editor.Ipc.sendToMain(name, args);
resolve({ success: true, message: "Message sent (sendToMain)" });
} else {
resolve({ success: false, message: "Editor.Ipc.sendToMain not available" });
}
} catch (e: any) {
resolve({ success: false, message: `Error: ${e.message}` });
}
});
}
}

192
src/IpcUi.ts Normal file
View File

@@ -0,0 +1,192 @@
// @ts-ignore
const Editor = window.Editor;
export class IpcUi {
private root: ShadowRoot;
private logArea: HTMLTextAreaElement | null = null;
private ipcList: HTMLElement | null = null;
private allMessages: any[] = [];
private filterSelect: HTMLSelectElement | null = null;
private paramInput: HTMLTextAreaElement | null = null;
constructor(root: ShadowRoot) {
this.root = root;
this.bindEvents();
}
private bindEvents() {
const btnScan = this.root.querySelector("#btnScanIpc");
const btnTest = this.root.querySelector("#btnTestIpc");
const cbSelectAll = this.root.querySelector("#cbSelectAllIpc");
this.logArea = this.root.querySelector("#ipcLog") as HTMLTextAreaElement;
this.ipcList = this.root.querySelector("#ipcList") as HTMLElement;
this.filterSelect = this.root.querySelector("#ipcFilter") as HTMLSelectElement;
this.paramInput = this.root.querySelector("#ipcParams") as HTMLTextAreaElement;
if (btnScan) {
btnScan.addEventListener("confirm", () => this.scanMessages());
}
if (btnTest) {
btnTest.addEventListener("confirm", () => this.testSelected());
}
if (cbSelectAll) {
cbSelectAll.addEventListener("change", (e: any) => this.toggleAll(e.detail ? e.detail.value : (e.target.value === 'true' || e.target.checked)));
}
if (this.filterSelect) {
this.filterSelect.addEventListener("change", () => this.filterMessages());
}
}
private scanMessages() {
this.log("Scanning IPC messages...");
// @ts-ignore
Editor.Ipc.sendToMain("mcp-bridge:scan-ipc-messages", (err: any, msgs: any[]) => {
if (err) {
this.log(`Scan Error: ${err}`);
return;
}
if (msgs) {
this.allMessages = msgs;
this.filterMessages();
this.log(`Found ${msgs.length} messages.`);
} else {
this.log("No messages found.");
}
});
}
private filterMessages() {
if (!this.allMessages) return;
const filter = this.filterSelect ? this.filterSelect.value : "all";
let filtered = this.allMessages;
if (filter === "available") {
filtered = this.allMessages.filter(m => m.status === "可用");
} else if (filter === "unavailable") {
filtered = this.allMessages.filter(m => m.status && m.status.includes("不可用"));
} else if (filter === "untested") {
filtered = this.allMessages.filter(m => !m.status || m.status === "未测试");
}
this.renderList(filtered);
}
private renderList(msgs: any[]) {
if (!this.ipcList) return;
this.ipcList.innerHTML = "";
msgs.forEach(msg => {
const item = document.createElement("div");
item.className = "ipc-item";
item.style.padding = "4px";
item.style.borderBottom = "1px solid #333";
item.style.display = "flex";
item.style.alignItems = "center";
// Checkbox
const checkbox = document.createElement("ui-checkbox");
// @ts-ignore
checkbox.value = false;
checkbox.setAttribute("data-name", msg.name);
checkbox.style.marginRight = "8px";
// Content
const content = document.createElement("div");
content.style.flex = "1";
content.style.fontSize = "11px";
let statusColor = "#888"; // Untested
if (msg.status === "可用") statusColor = "#4CAF50"; // Green
else if (msg.status && msg.status.includes("不可用")) statusColor = "#F44336"; // Red
content.innerHTML = `
<div style="display:flex; justify-content:space-between;">
<span style="color: #4CAF50; font-weight: bold;">${msg.name}</span>
<span style="color: ${statusColor}; font-size: 10px; border: 1px solid ${statusColor}; padding: 0 4px; border-radius: 4px;">${msg.status || "未测试"}</span>
</div>
<div style="color: #888;">${msg.description || "No desc"}</div>
<div style="color: #666; font-size: 10px;">Params: ${msg.params || "None"}</div>
`;
// Action Button
const btnRun = document.createElement("ui-button");
btnRun.innerText = "Run";
btnRun.className = "tiny";
btnRun.style.height = "20px";
btnRun.style.lineHeight = "20px";
btnRun.addEventListener("confirm", () => {
this.runTest(msg.name);
});
item.appendChild(checkbox);
item.appendChild(content);
item.appendChild(btnRun);
this.ipcList!.appendChild(item);
});
}
private async testSelected() {
const checkboxes = this.root.querySelectorAll("#ipcList ui-checkbox");
const toTest: string[] = [];
checkboxes.forEach((cb: any) => {
// In Cocos 2.x, ui-checkbox value is boolean
if (cb.checked || cb.value === true) {
toTest.push(cb.getAttribute("data-name"));
}
});
if (toTest.length === 0) {
this.log("No messages selected.");
return;
}
this.log(`Starting batch test for ${toTest.length} messages...`);
for (const name of toTest) {
await this.runTest(name);
}
this.log("Batch test completed.");
}
private runTest(name: string): Promise<void> {
return new Promise((resolve) => {
let params = null;
if (this.paramInput && this.paramInput.value.trim()) {
try {
params = JSON.parse(this.paramInput.value.trim());
} catch (e) {
this.log(`[Error] Invalid JSON Params: ${e}`);
resolve();
return;
}
}
this.log(`Testing: ${name} with params: ${JSON.stringify(params)}...`);
// @ts-ignore
Editor.Ipc.sendToMain("mcp-bridge:test-ipc-message", { name, params }, (err: any, result: any) => {
if (err) {
this.log(`[${name}] Failed: ${err}`);
} else {
this.log(`[${name}] Success: ${JSON.stringify(result)}`);
}
resolve();
});
});
}
private toggleAll(checked: boolean) {
const checkboxes = this.root.querySelectorAll("#ipcList ui-checkbox");
checkboxes.forEach((cb: any) => {
cb.value = checked;
});
}
private log(msg: string) {
if (this.logArea) {
// @ts-ignore
const time = new Date().toLocaleTimeString();
this.logArea.value += `[${time}] ${msg}\n`;
this.logArea.scrollTop = this.logArea.scrollHeight;
}
}
}