mirror of
https://github.com/HappyLifeOk/cc-3-8-x-mcp.git
synced 2026-06-10 09:46:47 +00:00
跨平台化:移除 AppleScript + 去 macOS 硬编码
- 删除浏览器交互的 AppleScript(osascript 控 Chrome/Safari 做截图/eval/刷新/打开), 改由外部 playwright MCP 承担;保留走 Editor.Message 的跨平台编辑器操作 - openDevDir 打开命令按平台分支(mac=open / win=explorer / linux=xdg-open) - execPath 解析弱化靠注册表/进程查询,删掉 /Applications macOS 硬编码兜底 - editor-control 去 macOS-only 假设,ps 进程查询按平台分支(win 用 wmic) - build-cmd Cocos 安装路径按平台拼 Win 特定逻辑(wmic、CocosDashboard 安装路径)标 TODO[win-verify],待 Windows 实测补全。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,15 +41,27 @@ function parseArgs(rest) {
|
|||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析 CocosCreator 可执行:--cocos 显式优先,否则 --version 拼标准安装路径(macOS)
|
// 按平台拼 Cocos Creator 标准安装路径。Win/Linux 路径规律待目标平台实测,建议优先用 --cocos 显式。
|
||||||
|
function cocosStdPath(ver) {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
return `/Applications/Cocos/Creator/${ver}/CocosCreator.app/Contents/MacOS/CocosCreator`;
|
||||||
|
}
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
// TODO[win-verify]: Win 上 CocosDashboard 安装路径待实测确认(下面是常见默认,未验证)
|
||||||
|
return `C:\\ProgramData\\cocos\\editors\\Creator\\${ver}\\CocosCreator.exe`;
|
||||||
|
}
|
||||||
|
return ''; // linux 等:无标准约定,要求 --cocos 显式
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 CocosCreator 可执行:--cocos 显式优先,否则 --version 拼标准安装路径
|
||||||
function resolveCocos(a) {
|
function resolveCocos(a) {
|
||||||
if (a.cocos) {
|
if (a.cocos) {
|
||||||
if (!fs.existsSync(a.cocos)) die(`--cocos 路径不存在: ${a.cocos}`);
|
if (!fs.existsSync(a.cocos)) die(`--cocos 路径不存在: ${a.cocos}`);
|
||||||
return a.cocos;
|
return a.cocos;
|
||||||
}
|
}
|
||||||
if (a.version) {
|
if (a.version) {
|
||||||
const p = `/Applications/Cocos/Creator/${a.version}/CocosCreator.app/Contents/MacOS/CocosCreator`;
|
const p = cocosStdPath(a.version);
|
||||||
if (!fs.existsSync(p)) die(`版本 ${a.version} 不在标准路径: ${p}\n 用 --cocos <可执行绝对路径> 显式指定`);
|
if (!p || !fs.existsSync(p)) die(`版本 ${a.version} 不在标准安装路径${p ? ': ' + p : ''}\n 用 --cocos <可执行绝对路径> 显式指定`);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
die('需指定 CocosCreator 可执行:--cocos <path> 或 --version <如 3.8.8>');
|
die('需指定 CocosCreator 可执行:--cocos <path> 或 --version <如 3.8.8>');
|
||||||
|
|||||||
@@ -128,17 +128,6 @@ async function getPreviewUrl() {
|
|||||||
return 'http://localhost:unknown-port';
|
return 'http://localhost:unknown-port';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成 AppleScript 匹配预览 tab 的条件:只按端口匹配、忽略 host。
|
|
||||||
* 同一预览实例在多网卡 / 切换网络时 host(IP) 会变,但端口稳定(per-project 编辑器分配)。
|
|
||||||
* 避免字面比完整 IP:port 在环境切换后失配(截图退化全屏、eval 报找不到 tab)。
|
|
||||||
*/
|
|
||||||
function tabMatchClause(url) {
|
|
||||||
var m = url && url.match(/:(\d+)(?:\/|$)/);
|
|
||||||
var port = m ? m[1] : '';
|
|
||||||
return port ? ('URL of t contains ":' + port + '/"') : ('URL of t starts with "' + url + '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
return new Promise(function (resolve) { setTimeout(resolve, ms); });
|
return new Promise(function (resolve) { setTimeout(resolve, ms); });
|
||||||
}
|
}
|
||||||
@@ -161,140 +150,6 @@ async function doReloadScene() {
|
|||||||
log('scene reloaded.');
|
log('scene reloaded.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 在浏览器中打开预览
|
|
||||||
*/
|
|
||||||
async function doOpenPreview() {
|
|
||||||
var url = await getPreviewUrl();
|
|
||||||
// 先查有没有该端口的 tab,有就不重开——避免在已有(带 tid 的)预览 tab 之外再冒一个裸地址 tab
|
|
||||||
var checkScript = [
|
|
||||||
'tell application "Google Chrome"',
|
|
||||||
' repeat with w in windows',
|
|
||||||
' repeat with t in tabs of w',
|
|
||||||
' if ' + tabMatchClause(url) + ' then return "FOUND"',
|
|
||||||
' end repeat',
|
|
||||||
' end repeat',
|
|
||||||
' return "NONE"',
|
|
||||||
'end tell'
|
|
||||||
].join('\n');
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
exec('osascript -e \'' + checkScript.replace(/'/g, "'\\''") + '\'', function (err, stdout) {
|
|
||||||
if (!err && (stdout || '').trim() === 'FOUND') {
|
|
||||||
log('preview tab already open (port matched), skip open');
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
log('opening preview: ' + url);
|
|
||||||
exec('open "' + url + '"', function () { resolve(); });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新已打开的预览浏览器页面
|
|
||||||
*/
|
|
||||||
async function doRefreshPreview() {
|
|
||||||
var url = await getPreviewUrl();
|
|
||||||
log('refreshing preview browser...');
|
|
||||||
// 用 AppleScript 找到预览页面并刷新
|
|
||||||
var script = [
|
|
||||||
'tell application "Google Chrome"',
|
|
||||||
' set found to false',
|
|
||||||
' repeat with w in windows',
|
|
||||||
' repeat with t in tabs of w',
|
|
||||||
' if ' + tabMatchClause(url) + ' then',
|
|
||||||
' tell t to reload',
|
|
||||||
' set found to true',
|
|
||||||
' end if',
|
|
||||||
' end repeat',
|
|
||||||
' end repeat',
|
|
||||||
' if not found then',
|
|
||||||
' open location "' + url + '"',
|
|
||||||
' end if',
|
|
||||||
'end tell'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
exec('osascript -e \'' + script.replace(/'/g, "'\\''") + '\'', function (err) {
|
|
||||||
if (err) {
|
|
||||||
log('Chrome refresh failed, trying Safari...');
|
|
||||||
var safariScript = [
|
|
||||||
'tell application "Safari"',
|
|
||||||
' set found to false',
|
|
||||||
' repeat with w in windows',
|
|
||||||
' repeat with t in tabs of w',
|
|
||||||
' if ' + tabMatchClause(url) + ' then',
|
|
||||||
' tell t to do JavaScript "location.reload()"',
|
|
||||||
' set found to true',
|
|
||||||
' end if',
|
|
||||||
' end repeat',
|
|
||||||
' end repeat',
|
|
||||||
' if not found then',
|
|
||||||
' open location "' + url + '"',
|
|
||||||
' end if',
|
|
||||||
'end tell'
|
|
||||||
].join('\n');
|
|
||||||
exec('osascript -e \'' + safariScript.replace(/'/g, "'\\''") + '\'', function () {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log('preview refreshed.');
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 截取预览浏览器页面的截图
|
|
||||||
*/
|
|
||||||
async function doScreenshot(outputPath) {
|
|
||||||
var url = await getPreviewUrl();
|
|
||||||
log('taking screenshot → ' + outputPath);
|
|
||||||
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
// 等待渲染
|
|
||||||
setTimeout(function () {
|
|
||||||
// 优先用 Chrome DevTools Protocol 截图(更精准)
|
|
||||||
var chromeScript = [
|
|
||||||
'tell application "Google Chrome"',
|
|
||||||
' repeat with w in windows',
|
|
||||||
' repeat with t in tabs of w',
|
|
||||||
' if ' + tabMatchClause(url) + ' then',
|
|
||||||
' set index of w to 1',
|
|
||||||
' set active tab index of w to (index of t)',
|
|
||||||
' delay 0.5',
|
|
||||||
' return id of w',
|
|
||||||
' end if',
|
|
||||||
' end repeat',
|
|
||||||
' end repeat',
|
|
||||||
' return ""',
|
|
||||||
'end tell'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
exec('osascript -e \'' + chromeScript.replace(/'/g, "'\\''") + '\'', function (err, stdout) {
|
|
||||||
var windowId = stdout ? stdout.trim() : '';
|
|
||||||
if (windowId) {
|
|
||||||
// 截取 Chrome 窗口
|
|
||||||
exec('screencapture -o -l ' + windowId + ' "' + outputPath + '"', function (err2) {
|
|
||||||
if (err2) {
|
|
||||||
log('window capture failed, fallback to full screen');
|
|
||||||
exec('screencapture -o "' + outputPath + '"', function () { resolve(); });
|
|
||||||
} else {
|
|
||||||
log('screenshot saved (browser window).');
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 降级:截取整个屏幕
|
|
||||||
log('preview tab not found, capturing full screen');
|
|
||||||
exec('screencapture -o "' + outputPath + '"', function () { resolve(); });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 消息处理(支持从其他扩展或命令行调用) ──
|
// ── 消息处理(支持从其他扩展或命令行调用) ──
|
||||||
|
|
||||||
exports.methods = {
|
exports.methods = {
|
||||||
@@ -302,11 +157,6 @@ exports.methods = {
|
|||||||
await doRefreshAssets();
|
await doRefreshAssets();
|
||||||
await doReloadScene();
|
await doReloadScene();
|
||||||
},
|
},
|
||||||
async screenshot() {
|
|
||||||
var outputPath = path.join(Editor.Project.path, '.dev', 'screenshot.png');
|
|
||||||
await doScreenshot(outputPath);
|
|
||||||
return outputPath;
|
|
||||||
},
|
|
||||||
async queryPreviewUrl() {
|
async queryPreviewUrl() {
|
||||||
var url = await getPreviewUrl();
|
var url = await getPreviewUrl();
|
||||||
// 顺便刷新 dev-reload-info.json,让外部脚本拿到最新端口
|
// 顺便刷新 dev-reload-info.json,让外部脚本拿到最新端口
|
||||||
@@ -342,11 +192,10 @@ exports.methods = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
/** Panel 使用:刷新资源 + 重载场景 + 刷新预览 */
|
/** Panel 使用:刷新资源 + 重载场景 */
|
||||||
async triggerRefresh() {
|
async triggerRefresh() {
|
||||||
await doRefreshAssets();
|
await doRefreshAssets();
|
||||||
await doReloadScene();
|
await doReloadScene();
|
||||||
await doRefreshPreview();
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
/** Panel 使用:重新导入指定 assetUrl */
|
/** Panel 使用:重新导入指定 assetUrl */
|
||||||
@@ -359,7 +208,9 @@ exports.methods = {
|
|||||||
openDevDir() {
|
openDevDir() {
|
||||||
var devDir = path.join(Editor.Project.path, DEV_DIR);
|
var devDir = path.join(Editor.Project.path, DEV_DIR);
|
||||||
if (!fs.existsSync(devDir)) fs.mkdirSync(devDir, { recursive: true });
|
if (!fs.existsSync(devDir)) fs.mkdirSync(devDir, { recursive: true });
|
||||||
exec('open "' + devDir + '"');
|
// 跨平台在系统文件管理器打开:mac=open / win=explorer / linux=xdg-open
|
||||||
|
var opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'explorer' : 'xdg-open';
|
||||||
|
exec(opener + ' "' + devDir + '"');
|
||||||
return devDir;
|
return devDir;
|
||||||
},
|
},
|
||||||
/** Panel 使用:只做场景软重载 */
|
/** Panel 使用:只做场景软重载 */
|
||||||
@@ -368,23 +219,6 @@ exports.methods = {
|
|||||||
await doReloadScene();
|
await doReloadScene();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
/** Panel 使用:在浏览器中打开预览 */
|
|
||||||
async openPreview() {
|
|
||||||
pushCommandLog('panel', 'open-preview');
|
|
||||||
await doOpenPreview();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
/** Panel 使用:截图并把路径复制到剪贴板 */
|
|
||||||
async screenshotCopy() {
|
|
||||||
pushCommandLog('panel', 'screenshot');
|
|
||||||
var outputPath = path.join(Editor.Project.path, DEV_DIR, 'screenshot.png');
|
|
||||||
await doScreenshot(outputPath);
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
exec('printf %s "' + outputPath.replace(/"/g, '\\"') + '" | pbcopy', function () {
|
|
||||||
resolve(outputPath);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/** Panel 使用:清理 .dev 临时产物(保留 dev-reload-info.json / dev-reload-panel.json / cc-mcp-panel.json) */
|
/** Panel 使用:清理 .dev 临时产物(保留 dev-reload-info.json / dev-reload-panel.json / cc-mcp-panel.json) */
|
||||||
cleanDevDir() {
|
cleanDevDir() {
|
||||||
pushCommandLog('panel', 'clean-dev');
|
pushCommandLog('panel', 'clean-dev');
|
||||||
@@ -404,56 +238,6 @@ exports.methods = {
|
|||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
return removed;
|
return removed;
|
||||||
},
|
},
|
||||||
/** Panel 使用:向预览 Chrome 页面注入 JS 代码并返回执行结果 */
|
|
||||||
async evalInPreview(code) {
|
|
||||||
if (!code) return { ok: false, error: 'empty code' };
|
|
||||||
pushCommandLog('panel', 'eval:' + code.slice(0, 40));
|
|
||||||
var url = await getPreviewUrl();
|
|
||||||
// AppleScript 需要把 JS 代码里的双引号转义
|
|
||||||
// 包一层 window.app 校验:连到的 tab 不是游戏(编辑器内嵌预览 / 错 tab)就明确报错,不默默 eval 错上下文
|
|
||||||
var guarded = '(function(){ if(typeof window==="undefined"||!window.app){return "__NO_GAME_CONTEXT__";} return (' + code + '); })()';
|
|
||||||
var escaped = guarded.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
||||||
var script = [
|
|
||||||
'tell application "Google Chrome"',
|
|
||||||
' repeat with w in windows',
|
|
||||||
' repeat with t in tabs of w',
|
|
||||||
' if ' + tabMatchClause(url) + ' then',
|
|
||||||
' return (execute t javascript "' + escaped + '")',
|
|
||||||
' end if',
|
|
||||||
' end repeat',
|
|
||||||
' end repeat',
|
|
||||||
' return "__NO_PREVIEW_TAB__"',
|
|
||||||
'end tell'
|
|
||||||
].join('\n');
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
exec('osascript -e \'' + script.replace(/'/g, "'\\''") + '\'', function (err, stdout, stderr) {
|
|
||||||
if (err) {
|
|
||||||
resolve({ ok: false, error: (stderr || err.message || '').trim() });
|
|
||||||
} else {
|
|
||||||
var out = (stdout || '').trim();
|
|
||||||
if (out === '__NO_PREVIEW_TAB__') {
|
|
||||||
resolve({ ok: false, error: '未找到预览标签页(先在 Chrome 打开 ' + url + ')' });
|
|
||||||
} else if (out === '__NO_GAME_CONTEXT__') {
|
|
||||||
resolve({ ok: false, error: '连到的 tab 没有游戏上下文(window.app undefined)——多半连的是编辑器内嵌预览或别的 tab。游戏要在浏览器跑且 app.ts 已加载;必要时用 playwright 直连游戏 tab' });
|
|
||||||
} else {
|
|
||||||
resolve({ ok: true, result: out });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/** Panel 使用:读取用户自定义的 debug 按钮配置 */
|
|
||||||
getDebugButtons() {
|
|
||||||
var cfgPath = path.join(Editor.Project.path, PANEL_CONFIG_FILE);
|
|
||||||
if (!fs.existsSync(cfgPath)) return [];
|
|
||||||
try {
|
|
||||||
var cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
||||||
if (Array.isArray(cfg.buttons)) return cfg.buttons;
|
|
||||||
return [];
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/** Panel 使用:扫同机其他 worktree 的 dev-reload-info.json */
|
/** Panel 使用:扫同机其他 worktree 的 dev-reload-info.json */
|
||||||
listWorktrees() {
|
listWorktrees() {
|
||||||
var results = [];
|
var results = [];
|
||||||
@@ -575,15 +359,13 @@ function getProjectShortName() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析 Cocos 编辑器主进程可执行路径,写进注册文件供 router 的 editor_restart 拉起用。
|
* 解析 Cocos 编辑器主进程可执行路径,写进注册文件供 router 的 editor_restart 拉起用。
|
||||||
* 优先 process.argv[0](Electron 主进程启动命令首段,即 .app 可执行),按 version 拼标准路径兜底。
|
* 取 process.argv[0] / process.execPath(编辑器主进程可执行,跨平台)。
|
||||||
* 排除 Helper(渲染/GPU 子进程路径),要外层主可执行。解析不到返回空串,router 端还有 ps / version 两级 fallback。
|
* 排除 Helper(渲染/GPU 子进程),解析不到返回空串。
|
||||||
*/
|
*/
|
||||||
function getEditorExecPath() {
|
function getEditorExecPath() {
|
||||||
var candidates = [];
|
var candidates = [];
|
||||||
try { if (process.argv && process.argv[0]) candidates.push(process.argv[0]); } catch (e) { /* ignore */ }
|
try { if (process.argv && process.argv[0]) candidates.push(process.argv[0]); } catch (e) { /* ignore */ }
|
||||||
try { if (process.execPath) candidates.push(process.execPath); } catch (e) { /* ignore */ }
|
try { if (process.execPath) candidates.push(process.execPath); } catch (e) { /* ignore */ }
|
||||||
var ver = (Editor.App && Editor.App.version) ? Editor.App.version : '';
|
|
||||||
if (ver) candidates.push('/Applications/Cocos/Creator/' + ver + '/CocosCreator.app/Contents/MacOS/CocosCreator');
|
|
||||||
for (var i = 0; i < candidates.length; i++) {
|
for (var i = 0; i < candidates.length; i++) {
|
||||||
var c = candidates[i];
|
var c = candidates[i];
|
||||||
if (c && /CocosCreator/.test(c) && c.indexOf('Helper') < 0) {
|
if (c && /CocosCreator/.test(c) && c.indexOf('Helper') < 0) {
|
||||||
@@ -699,20 +481,13 @@ function buildToolCtx() {
|
|||||||
local: {
|
local: {
|
||||||
getPreviewUrl: getPreviewUrl,
|
getPreviewUrl: getPreviewUrl,
|
||||||
doReimport: doReimport,
|
doReimport: doReimport,
|
||||||
doRefreshPreview: doRefreshPreview,
|
|
||||||
doOpenPreview: doOpenPreview,
|
|
||||||
doScreenshot: async function (outputPath) {
|
|
||||||
var p = outputPath || path.join(Editor.Project.path, DEV_DIR, 'screenshot.png');
|
|
||||||
await doScreenshot(p);
|
|
||||||
return p;
|
|
||||||
},
|
|
||||||
doRefreshAssets: doRefreshAssets,
|
doRefreshAssets: doRefreshAssets,
|
||||||
doReloadScene: doReloadScene,
|
doReloadScene: doReloadScene,
|
||||||
evalInPreview: function (code) { return exports.methods.evalInPreview(code); },
|
|
||||||
listWorktrees: function () { return exports.methods.listWorktrees(); },
|
listWorktrees: function () { return exports.methods.listWorktrees(); },
|
||||||
openDevDir: function () { return exports.methods.openDevDir(); },
|
openDevDir: function () { return exports.methods.openDevDir(); },
|
||||||
cleanDevDir: function () { return exports.methods.cleanDevDir(); },
|
cleanDevDir: function () { return exports.methods.cleanDevDir(); },
|
||||||
getStatus: function () { return exports.methods.getStatus(); },
|
getStatus: function () { return exports.methods.getStatus(); },
|
||||||
|
reloadPackage: doRestartSelf,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -748,13 +523,13 @@ async function doOpenPrefab(urlOrPath) {
|
|||||||
log('open-prefab: opened ' + dbUrl + ' (uuid=' + uuid + ')');
|
log('open-prefab: opened ' + dbUrl + ' (uuid=' + uuid + ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重启整个插件(disable → enable)让 main.js / tools.js / server/* 的代码改动生效。
|
/** 重启指定插件(disable → enable)让其 JS 代码改动生效;name 缺省为本插件 cc-3-8-x-mcp。
|
||||||
* 注意:本函数自身处于即将被卸载的 main.js 上下文,必须 fire-and-forget。
|
* 注意:reload 本插件自身时,本函数处于即将被卸载的 main.js 上下文,必须 fire-and-forget。
|
||||||
* Editor.Package.disable 是 host 进程 API,扩展沙箱卸载后仍然有效;
|
* Editor.Package.disable 是 host 进程 API,扩展沙箱卸载后仍然有效;
|
||||||
* enable 在 disable 完成后用 setTimeout 触发,给 unload 收尾留窗口。
|
* enable 在 disable 完成后用 setTimeout 触发,给 unload 收尾留窗口。
|
||||||
*/
|
*/
|
||||||
function doRestartSelf() {
|
function doRestartSelf(name) {
|
||||||
var name = 'cc-3-8-x-mcp';
|
name = name || 'cc-3-8-x-mcp';
|
||||||
log('restart-package: scheduling disable → enable for ' + name);
|
log('restart-package: scheduling disable → enable for ' + name);
|
||||||
setImmediate(function () {
|
setImmediate(function () {
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
@@ -771,18 +546,19 @@ function doRestartSelf() {
|
|||||||
/**
|
/**
|
||||||
* 分发单条 refresh 命令。
|
* 分发单条 refresh 命令。
|
||||||
*
|
*
|
||||||
* 协议精简:只支持 `restart-package`(disable→enable 整个扩展,让 JS 代码改动生效)。
|
* 协议:`restart-package [name]`(disable→enable 指定扩展让 JS 改动生效;缺省 name 重启本插件自身)。
|
||||||
* 资源刷新 / 场景重载 / 预览刷新 / 截图等走 MCP tool(preview_refresh_and_reload /
|
* 资源刷新 / 场景重载 / 预览刷新 / 截图等走 MCP tool(preview_refresh_and_reload /
|
||||||
* asset_reimport / preview_screenshot 等)或面板按钮,不再通过文件协议触发。
|
* asset_reimport 等)或面板按钮,不再通过文件协议触发。
|
||||||
*/
|
*/
|
||||||
async function handleRefreshCommand(cmd) {
|
async function handleRefreshCommand(cmd) {
|
||||||
if (!cmd) return;
|
if (!cmd) return;
|
||||||
pushCommandLog('refresh', cmd);
|
pushCommandLog('refresh', cmd);
|
||||||
if (cmd === 'restart-package') {
|
var parts = cmd.split(/\s+/);
|
||||||
doRestartSelf();
|
if (parts[0] === 'restart-package') {
|
||||||
|
doRestartSelf(parts[1]); // parts[1] 可选:缺省重启自身,指定则 reload 该扩展
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log('refresh: unknown command — ' + cmd + '(仅支持 restart-package)');
|
log('refresh: unknown command — ' + cmd + '(仅支持 restart-package [name])');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 启动 .dev/refresh 文件 watcher(写入命令 → 读取 → 执行 → 清空) */
|
/** 启动 .dev/refresh 文件 watcher(写入命令 → 读取 → 执行 → 清空) */
|
||||||
|
|||||||
@@ -40,16 +40,11 @@
|
|||||||
"get-status": { "methods": ["getStatus"] },
|
"get-status": { "methods": ["getStatus"] },
|
||||||
"get-mcp-config": { "methods": ["getMcpConfig"] },
|
"get-mcp-config": { "methods": ["getMcpConfig"] },
|
||||||
"refresh-assets": { "methods": ["refreshAssets"] },
|
"refresh-assets": { "methods": ["refreshAssets"] },
|
||||||
"screenshot": { "methods": ["screenshot"] },
|
|
||||||
"query-preview-url": { "methods": ["queryPreviewUrl"] },
|
"query-preview-url": { "methods": ["queryPreviewUrl"] },
|
||||||
"trigger-refresh": { "methods": ["triggerRefresh"] },
|
"trigger-refresh": { "methods": ["triggerRefresh"] },
|
||||||
"trigger-reimport": { "methods": ["triggerReimport"] },
|
"trigger-reimport": { "methods": ["triggerReimport"] },
|
||||||
"soft-reload-scene": { "methods": ["softReloadScene"] },
|
"soft-reload-scene": { "methods": ["softReloadScene"] },
|
||||||
"open-preview": { "methods": ["openPreview"] },
|
|
||||||
"screenshot-copy": { "methods": ["screenshotCopy"] },
|
|
||||||
"clean-dev-dir": { "methods": ["cleanDevDir"] },
|
"clean-dev-dir": { "methods": ["cleanDevDir"] },
|
||||||
"eval-in-preview": { "methods": ["evalInPreview"] },
|
|
||||||
"get-debug-buttons": { "methods": ["getDebugButtons"] },
|
|
||||||
"list-worktrees": { "methods": ["listWorktrees"] },
|
"list-worktrees": { "methods": ["listWorktrees"] },
|
||||||
"open-dev-dir": { "methods": ["openDevDir"] }
|
"open-dev-dir": { "methods": ["openDevDir"] }
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-73
@@ -32,11 +32,9 @@ exports.template = /* html */ `
|
|||||||
<section class="actions">
|
<section class="actions">
|
||||||
<header>快捷动作</header>
|
<header>快捷动作</header>
|
||||||
<div class="btn-grid">
|
<div class="btn-grid">
|
||||||
<ui-button id="btnRefresh">刷新(资源+场景+预览)</ui-button>
|
<ui-button id="btnRefresh">刷新(资源+场景)</ui-button>
|
||||||
<ui-button id="btnSoftReload">仅软重载场景</ui-button>
|
<ui-button id="btnSoftReload">仅软重载场景</ui-button>
|
||||||
<ui-button id="btnOpenPreview">打开预览浏览器</ui-button>
|
|
||||||
<ui-button id="btnQueryUrl">查询预览地址</ui-button>
|
<ui-button id="btnQueryUrl">查询预览地址</ui-button>
|
||||||
<ui-button id="btnScreenshot">截图 → 复制路径</ui-button>
|
|
||||||
<ui-button id="btnOpenDev">打开 .dev 目录</ui-button>
|
<ui-button id="btnOpenDev">打开 .dev 目录</ui-button>
|
||||||
<ui-button id="btnClean">清理 .dev 临时文件</ui-button>
|
<ui-button id="btnClean">清理 .dev 临时文件</ui-button>
|
||||||
<ui-button id="btnRefreshStatus" class="secondary">刷新状态</ui-button>
|
<ui-button id="btnRefreshStatus" class="secondary">刷新状态</ui-button>
|
||||||
@@ -48,17 +46,6 @@ exports.template = /* html */ `
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="debug">
|
|
||||||
<header>Debug 注入</header>
|
|
||||||
<div class="eval-row">
|
|
||||||
<ui-input id="evalInput" placeholder='console.log(app.userMod.getUserValue(0))' class="grow"></ui-input>
|
|
||||||
<ui-button id="btnEval">执行</ui-button>
|
|
||||||
</div>
|
|
||||||
<pre id="evalResult" class="eval-result"></pre>
|
|
||||||
<div id="debugButtons" class="debug-buttons"></div>
|
|
||||||
<div class="hint-small">自定义按钮配置:<code>.dev/cc-mcp-panel.json</code>(或旧名 <code>.dev/dev-reload-panel.json</code>)→ <code>{ "buttons": [{ "label": "...", "code": "..." }] }</code></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="worktrees">
|
<section class="worktrees">
|
||||||
<header>同机 Worktree</header>
|
<header>同机 Worktree</header>
|
||||||
<div id="worktreeList" class="wt-list">-</div>
|
<div id="worktreeList" class="wt-list">-</div>
|
||||||
@@ -129,18 +116,12 @@ exports.$ = {
|
|||||||
probeDot: '#probeDot',
|
probeDot: '#probeDot',
|
||||||
btnRefresh: '#btnRefresh',
|
btnRefresh: '#btnRefresh',
|
||||||
btnSoftReload: '#btnSoftReload',
|
btnSoftReload: '#btnSoftReload',
|
||||||
btnOpenPreview: '#btnOpenPreview',
|
|
||||||
btnQueryUrl: '#btnQueryUrl',
|
btnQueryUrl: '#btnQueryUrl',
|
||||||
btnScreenshot: '#btnScreenshot',
|
|
||||||
btnOpenDev: '#btnOpenDev',
|
btnOpenDev: '#btnOpenDev',
|
||||||
btnClean: '#btnClean',
|
btnClean: '#btnClean',
|
||||||
btnRefreshStatus: '#btnRefreshStatus',
|
btnRefreshStatus: '#btnRefreshStatus',
|
||||||
btnReimport: '#btnReimport',
|
btnReimport: '#btnReimport',
|
||||||
reimportInput: '#reimportInput',
|
reimportInput: '#reimportInput',
|
||||||
evalInput: '#evalInput',
|
|
||||||
btnEval: '#btnEval',
|
|
||||||
evalResult: '#evalResult',
|
|
||||||
debugButtons: '#debugButtons',
|
|
||||||
worktreeList: '#worktreeList',
|
worktreeList: '#worktreeList',
|
||||||
logList: '#logList',
|
logList: '#logList',
|
||||||
toast: '#toast',
|
toast: '#toast',
|
||||||
@@ -203,7 +184,6 @@ exports.methods = {
|
|||||||
this.showToast('状态获取失败: ' + (e.message || e));
|
this.showToast('状态获取失败: ' + (e.message || e));
|
||||||
}
|
}
|
||||||
this.refreshWorktrees();
|
this.refreshWorktrees();
|
||||||
this.refreshDebugButtons();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async probePreview(url) {
|
async probePreview(url) {
|
||||||
@@ -237,43 +217,11 @@ exports.methods = {
|
|||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
},
|
},
|
||||||
|
|
||||||
async refreshDebugButtons() {
|
|
||||||
try {
|
|
||||||
const btns = await Editor.Message.request('cc-3-8-x-mcp', 'get-debug-buttons');
|
|
||||||
if (!Array.isArray(btns) || !btns.length) {
|
|
||||||
this.$.debugButtons.innerHTML = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$.debugButtons.innerHTML = '';
|
|
||||||
btns.forEach(cfg => {
|
|
||||||
if (!cfg || !cfg.label || !cfg.code) return;
|
|
||||||
const btn = document.createElement('ui-button');
|
|
||||||
btn.textContent = cfg.label;
|
|
||||||
btn.addEventListener('confirm', () => this.runEval(cfg.code, cfg.label));
|
|
||||||
this.$.debugButtons.appendChild(btn);
|
|
||||||
});
|
|
||||||
} catch (e) { /* ignore */ }
|
|
||||||
},
|
|
||||||
|
|
||||||
async runEval(code, label) {
|
|
||||||
this.showToast('执行: ' + (label || code.slice(0, 30)));
|
|
||||||
try {
|
|
||||||
const r = await Editor.Message.request('cc-3-8-x-mcp', 'eval-in-preview', code);
|
|
||||||
if (r && r.ok) {
|
|
||||||
this.$.evalResult.textContent = '✓ ' + (r.result || '(no return)');
|
|
||||||
} else {
|
|
||||||
this.$.evalResult.textContent = '✗ ' + ((r && r.error) || 'unknown');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.$.evalResult.textContent = '✗ ' + (e.message || e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async onRefreshClick() {
|
async onRefreshClick() {
|
||||||
this.showToast('刷新中…');
|
this.showToast('刷新中…');
|
||||||
try {
|
try {
|
||||||
await Editor.Message.request('cc-3-8-x-mcp', 'trigger-refresh');
|
await Editor.Message.request('cc-3-8-x-mcp', 'trigger-refresh');
|
||||||
this.showToast('已刷新资源+场景+预览');
|
this.showToast('已刷新资源+场景');
|
||||||
this.refreshStatus();
|
this.refreshStatus();
|
||||||
} catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
} catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
||||||
},
|
},
|
||||||
@@ -281,10 +229,6 @@ exports.methods = {
|
|||||||
try { await Editor.Message.request('cc-3-8-x-mcp', 'soft-reload-scene'); this.showToast('场景已软重载'); }
|
try { await Editor.Message.request('cc-3-8-x-mcp', 'soft-reload-scene'); this.showToast('场景已软重载'); }
|
||||||
catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
||||||
},
|
},
|
||||||
async onOpenPreviewClick() {
|
|
||||||
try { await Editor.Message.request('cc-3-8-x-mcp', 'open-preview'); this.showToast('已在浏览器打开预览'); }
|
|
||||||
catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
|
||||||
},
|
|
||||||
async onQueryUrlClick() {
|
async onQueryUrlClick() {
|
||||||
try {
|
try {
|
||||||
const url = await Editor.Message.request('cc-3-8-x-mcp', 'query-preview-url');
|
const url = await Editor.Message.request('cc-3-8-x-mcp', 'query-preview-url');
|
||||||
@@ -292,13 +236,6 @@ exports.methods = {
|
|||||||
this.refreshStatus();
|
this.refreshStatus();
|
||||||
} catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
} catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
||||||
},
|
},
|
||||||
async onScreenshotClick() {
|
|
||||||
this.showToast('截图中…');
|
|
||||||
try {
|
|
||||||
const p = await Editor.Message.request('cc-3-8-x-mcp', 'screenshot-copy');
|
|
||||||
this.showToast('截图路径已复制: ' + p);
|
|
||||||
} catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
|
||||||
},
|
|
||||||
async onOpenDevClick() {
|
async onOpenDevClick() {
|
||||||
try { await Editor.Message.request('cc-3-8-x-mcp', 'open-dev-dir'); this.showToast('已打开 .dev'); }
|
try { await Editor.Message.request('cc-3-8-x-mcp', 'open-dev-dir'); this.showToast('已打开 .dev'); }
|
||||||
catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
||||||
@@ -341,11 +278,6 @@ exports.methods = {
|
|||||||
this.refreshStatus();
|
this.refreshStatus();
|
||||||
} catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
} catch (e) { this.showToast('失败: ' + (e.message || e)); }
|
||||||
},
|
},
|
||||||
async onEvalClick() {
|
|
||||||
const code = (this.$.evalInput.value || '').trim();
|
|
||||||
if (!code) { this.showToast('请输入 JS 代码'); return; }
|
|
||||||
await this.runEval(code);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function escapeHtml(s) {
|
function escapeHtml(s) {
|
||||||
@@ -357,14 +289,11 @@ function escapeHtml(s) {
|
|||||||
exports.ready = function () {
|
exports.ready = function () {
|
||||||
this.$.btnRefresh.addEventListener('confirm', () => this.onRefreshClick());
|
this.$.btnRefresh.addEventListener('confirm', () => this.onRefreshClick());
|
||||||
this.$.btnSoftReload.addEventListener('confirm', () => this.onSoftReloadClick());
|
this.$.btnSoftReload.addEventListener('confirm', () => this.onSoftReloadClick());
|
||||||
this.$.btnOpenPreview.addEventListener('confirm', () => this.onOpenPreviewClick());
|
|
||||||
this.$.btnQueryUrl.addEventListener('confirm', () => this.onQueryUrlClick());
|
this.$.btnQueryUrl.addEventListener('confirm', () => this.onQueryUrlClick());
|
||||||
this.$.btnScreenshot.addEventListener('confirm', () => this.onScreenshotClick());
|
|
||||||
this.$.btnOpenDev.addEventListener('confirm', () => this.onOpenDevClick());
|
this.$.btnOpenDev.addEventListener('confirm', () => this.onOpenDevClick());
|
||||||
this.$.btnClean.addEventListener('confirm', () => this.onCleanClick());
|
this.$.btnClean.addEventListener('confirm', () => this.onCleanClick());
|
||||||
this.$.btnRefreshStatus.addEventListener('confirm', () => this.refreshStatus());
|
this.$.btnRefreshStatus.addEventListener('confirm', () => this.refreshStatus());
|
||||||
this.$.btnReimport.addEventListener('confirm', () => this.onReimportClick());
|
this.$.btnReimport.addEventListener('confirm', () => this.onReimportClick());
|
||||||
this.$.btnEval.addEventListener('confirm', () => this.onEvalClick());
|
|
||||||
this.$.btnCopyMcpUrl.addEventListener('confirm', () => this.onCopyMcpUrl());
|
this.$.btnCopyMcpUrl.addEventListener('confirm', () => this.onCopyMcpUrl());
|
||||||
this.$.btnCopyCli.addEventListener('confirm', () => this.onCopyCli());
|
this.$.btnCopyCli.addEventListener('confirm', () => this.onCopyCli());
|
||||||
this.$.btnRestartMcp.addEventListener('confirm', () => this.onRestartMcp());
|
this.$.btnRestartMcp.addEventListener('confirm', () => this.onRestartMcp());
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
*
|
*
|
||||||
* [editor] tool 命名不加 shortName 前缀(router 全局工具)。
|
* [editor] tool 命名不加 shortName 前缀(router 全局工具)。
|
||||||
*
|
*
|
||||||
* 仅支持 macOS(execPath 解析按 /Applications/Cocos/Creator/<version>/ 规律)。
|
* 跨平台:execPath 优先用注册表(编辑器写入)/ 运行进程查询,不硬编码平台安装路径。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
@@ -141,21 +141,25 @@ function resolveTarget(args) {
|
|||||||
/** 从运行中进程的命令行抓可执行路径(编辑器启动命令首段,--project 之前) */
|
/** 从运行中进程的命令行抓可执行路径(编辑器启动命令首段,--project 之前) */
|
||||||
function execPathFromPs(pid) {
|
function execPathFromPs(pid) {
|
||||||
try {
|
try {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
// TODO[win-verify]: Win 没有 ps。下面用 wmic 拿可执行路径,需在 Win 上实测确认(新版 Win 可能要改 PowerShell Get-CimInstance)
|
||||||
|
var winOut = cp.execFileSync('wmic', ['process', 'where', 'processid=' + pid, 'get', 'ExecutablePath', '/value'], { encoding: 'utf-8' });
|
||||||
|
var wm = winOut.match(/ExecutablePath=(.+)/);
|
||||||
|
return wm ? wm[1].trim() : '';
|
||||||
|
}
|
||||||
|
// mac / linux: ps 抓命令行首段(--project 之前)
|
||||||
var out = cp.execFileSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf-8' }).trim();
|
var out = cp.execFileSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf-8' }).trim();
|
||||||
if (!out) return '';
|
if (!out) return '';
|
||||||
// 形如:/Applications/Cocos/Creator/3.8.8/CocosCreator.app/Contents/MacOS/CocosCreator --project /x ...
|
|
||||||
// 可执行路径本身不含 " --",按它切分安全
|
|
||||||
var idx = out.indexOf(' --');
|
var idx = out.indexOf(' --');
|
||||||
return (idx >= 0 ? out.slice(0, idx) : out.split(/\s+/)[0]).trim();
|
return (idx >= 0 ? out.slice(0, idx) : out.split(/\s+/)[0]).trim();
|
||||||
} catch (e) { return ''; }
|
} catch (e) { return ''; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析编辑器可执行路径,三级 fallback:
|
* 解析编辑器可执行路径,两级 fallback:
|
||||||
* 1. 注册文件 execPath 字段(main.js 写入,最准)
|
* 1. 注册文件 execPath 字段(main.js 写入,最准)
|
||||||
* 2. 从活进程 ps 命令行抓(编辑器还活着时)—— restart 会在 kill 前调用,此时旧进程还在
|
* 2. 从活进程命令行抓(编辑器还活着时)—— restart 会在 kill 前调用,此时旧进程还在
|
||||||
* 3. 按 editorVersion 拼标准安装路径
|
* 全部失败抛错。
|
||||||
* 全部失败抛错,提示带上尝试过的路径。
|
|
||||||
*/
|
*/
|
||||||
function resolveExecPath(entry) {
|
function resolveExecPath(entry) {
|
||||||
if (entry.execPath && fs.existsSync(entry.execPath)) return entry.execPath;
|
if (entry.execPath && fs.existsSync(entry.execPath)) return entry.execPath;
|
||||||
@@ -165,18 +169,11 @@ function resolveExecPath(entry) {
|
|||||||
if (fromPs && fs.existsSync(fromPs)) return fromPs;
|
if (fromPs && fs.existsSync(fromPs)) return fromPs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.editorVersion) {
|
|
||||||
var guess = '/Applications/Cocos/Creator/' + entry.editorVersion +
|
|
||||||
'/CocosCreator.app/Contents/MacOS/CocosCreator';
|
|
||||||
if (fs.existsSync(guess)) return guess;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'editor-control: 无法解析 Cocos 编辑器可执行路径。\n' +
|
'editor-control: 无法解析 Cocos 编辑器可执行路径。\n' +
|
||||||
' 注册文件 execPath: ' + (entry.execPath || '(无)') + '\n' +
|
' 注册文件 execPath: ' + (entry.execPath || '(无)') + '\n' +
|
||||||
' editorVersion: ' + (entry.editorVersion || '(无)') + '\n' +
|
' editorVersion: ' + (entry.editorVersion || '(无)') + '\n' +
|
||||||
'请确认 Cocos Creator 装在标准路径 /Applications/Cocos/Creator/<version>/,' +
|
'请重启编辑器让扩展写入 execPath 字段,或用 editor_spawn 显式传 execPath。'
|
||||||
'或重启编辑器让扩展写入 execPath 字段后再试。'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,38 +220,19 @@ async function killEditor(pid, opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 冷启动场景解析 execPath:进程已不在,没有活进程可 ps 抓,靠四级 fallback:
|
* 冷启动场景解析 execPath:进程已不在,没有活进程可 ps 抓,靠两级 fallback:
|
||||||
* 1. args.execPath 显式
|
* 1. args.execPath 显式
|
||||||
* 2. args.version 拼标准路径
|
* 2. 借任意一条注册 entry 的 execPath —— execPath 是机器级安装路径,跨项目通用、跨平台,
|
||||||
* 3. 借任意一条注册 entry 的 execPath —— execPath 是机器级安装路径,跨项目通用,
|
|
||||||
* 哪怕那条 entry 是别的项目 / 已 stale 也能用
|
* 哪怕那条 entry 是别的项目 / 已 stale 也能用
|
||||||
* 4. 扫 /Applications/Cocos/Creator 下唯一安装版本
|
|
||||||
*/
|
*/
|
||||||
function resolveExecPathForSpawn(args, projectPath) {
|
function resolveExecPathForSpawn(args, projectPath) {
|
||||||
if (args.execPath && fs.existsSync(args.execPath)) return args.execPath;
|
if (args.execPath && fs.existsSync(args.execPath)) return args.execPath;
|
||||||
|
|
||||||
if (args.version) {
|
// 借任意一条注册 entry 的 execPath(机器级安装路径,跨项目通用、跨平台)
|
||||||
var byVer = '/Applications/Cocos/Creator/' + args.version + '/CocosCreator.app/Contents/MacOS/CocosCreator';
|
|
||||||
if (fs.existsSync(byVer)) return byVer;
|
|
||||||
}
|
|
||||||
|
|
||||||
var borrowed = readRegistryEntries().filter(function (e) { return e.execPath && fs.existsSync(e.execPath); })[0];
|
var borrowed = readRegistryEntries().filter(function (e) { return e.execPath && fs.existsSync(e.execPath); })[0];
|
||||||
if (borrowed) return borrowed.execPath;
|
if (borrowed) return borrowed.execPath;
|
||||||
|
|
||||||
var base = '/Applications/Cocos/Creator';
|
throw new Error('editor_spawn: 无法解析 Cocos 可执行路径。请传 execPath(注册表无可借项时无法推断安装路径)。');
|
||||||
try {
|
|
||||||
var vers = fs.readdirSync(base).filter(function (v) {
|
|
||||||
return fs.existsSync(base + '/' + v + '/CocosCreator.app/Contents/MacOS/CocosCreator');
|
|
||||||
});
|
|
||||||
if (vers.length === 1) return base + '/' + vers[0] + '/CocosCreator.app/Contents/MacOS/CocosCreator';
|
|
||||||
if (vers.length > 1) {
|
|
||||||
throw new Error('editor_spawn: ' + base + ' 下有多个版本 [' + vers.join(', ') + '],请用 version 指定要启动哪个。');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (/多个版本/.test(e.message)) throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('editor_spawn: 无法解析 Cocos 可执行路径(execPath/version 都没给,注册表也无可借项)。请传 execPath 或 version。');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -387,7 +365,7 @@ var EDITOR_TOOLS = [
|
|||||||
{
|
{
|
||||||
name: 'editor_restart',
|
name: 'editor_restart',
|
||||||
description: '[editor] 重启 Cocos 编辑器进程(kill 旧实例 → 重新拉起 → 等就绪)。' +
|
description: '[editor] 重启 Cocos 编辑器进程(kill 旧实例 → 重新拉起 → 等就绪)。' +
|
||||||
'不需要编辑器在运行也能调(挂 router 进程)。仅 macOS。' +
|
'不需要编辑器在运行也能调(挂 router 进程)。' +
|
||||||
'返回 oldPid / launchedPid / kill 结果 / ready 状态(含新 pid·port·url)。',
|
'返回 oldPid / launchedPid / kill 结果 / ready 状态(含新 pid·port·url)。',
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -433,7 +411,7 @@ var EDITOR_TOOLS = [
|
|||||||
{
|
{
|
||||||
name: 'editor_spawn',
|
name: 'editor_spawn',
|
||||||
description: '[editor] 从零启动一个 Cocos 编辑器(进程完全不在时用,如崩溃后恢复)。' +
|
description: '[editor] 从零启动一个 Cocos 编辑器(进程完全不在时用,如崩溃后恢复)。' +
|
||||||
'同项目已有活跃实例则直接返回不重复开(Cocos 不支持同项目多开)。仅 macOS。',
|
'同项目已有活跃实例则直接返回不重复开(Cocos 不支持同项目多开)。',
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
|||||||
+15
-45
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
function defineTools(ctx) {
|
function defineTools(ctx) {
|
||||||
var msg = ctx.msg; // async (target, name, ...args) => result
|
var msg = ctx.msg; // async (target, name, ...args) => result
|
||||||
var local = ctx.local; // { getPreviewUrl, doReimport, doRefreshPreview, doOpenPreview, doScreenshot, doRefreshAssets, doReloadScene, evalInPreview, listWorktrees, openDevDir, cleanDevDir, getStatus, getPanelConfig }
|
var local = ctx.local; // { getPreviewUrl, doReimport, doRefreshAssets, doReloadScene, listWorktrees, openDevDir, cleanDevDir, getStatus }
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// ── scene 域 ──
|
// ── scene 域 ──
|
||||||
@@ -251,61 +251,31 @@ function defineTools(ctx) {
|
|||||||
return { url: await local.getPreviewUrl() };
|
return { url: await local.getPreviewUrl() };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'preview_open_browser',
|
|
||||||
description: '在系统默认浏览器打开预览',
|
|
||||||
inputSchema: { type: 'object', properties: {} },
|
|
||||||
handler: async function () {
|
|
||||||
await local.doOpenPreview();
|
|
||||||
return 'ok';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'preview_refresh_browser',
|
|
||||||
description: '刷新已打开的预览浏览器页面(AppleScript 驱动 Chrome/Safari)',
|
|
||||||
inputSchema: { type: 'object', properties: {} },
|
|
||||||
handler: async function () {
|
|
||||||
await local.doRefreshPreview();
|
|
||||||
return 'ok';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'preview_screenshot',
|
|
||||||
description: '截图预览页面到指定路径(默认 .dev/screenshot.png),返回路径',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: { outputPath: { type: 'string' } },
|
|
||||||
},
|
|
||||||
handler: async function (args) {
|
|
||||||
var p = args.outputPath || null;
|
|
||||||
return await local.doScreenshot(p);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'preview_eval_js',
|
|
||||||
description: '向预览 Chrome 页面注入 JS 代码并返回执行结果',
|
|
||||||
inputSchema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: { code: { type: 'string' } },
|
|
||||||
required: ['code'],
|
|
||||||
},
|
|
||||||
handler: async function (args) {
|
|
||||||
return await local.evalInPreview(args.code);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'preview_refresh_and_reload',
|
name: 'preview_refresh_and_reload',
|
||||||
description: '一键:刷新资源 + 软重载场景 + 刷新预览浏览器',
|
description: '一键:刷新资源 + 软重载场景',
|
||||||
inputSchema: { type: 'object', properties: {} },
|
inputSchema: { type: 'object', properties: {} },
|
||||||
handler: async function () {
|
handler: async function () {
|
||||||
await local.doRefreshAssets();
|
await local.doRefreshAssets();
|
||||||
await local.doReloadScene();
|
await local.doReloadScene();
|
||||||
await local.doRefreshPreview();
|
|
||||||
return 'ok';
|
return 'ok';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── local 域 ──
|
// ── local 域 ──
|
||||||
|
{
|
||||||
|
name: 'local_reload_package',
|
||||||
|
description: 'reload(disable→enable)指定编辑器扩展,让其 JS 代码改动生效,无需重启编辑器。本质 Editor.Package.disable→enable,fire-and-forget 立即返回。',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: { name: { type: 'string', description: '扩展名(package.json 的 name,如 state-ctrl-gen)' } },
|
||||||
|
required: ['name'],
|
||||||
|
},
|
||||||
|
handler: async function (args) {
|
||||||
|
local.reloadPackage(args.name);
|
||||||
|
return { ok: true, reloaded: args.name };
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'local_get_status',
|
name: 'local_get_status',
|
||||||
description: '获取插件本地状态(git 分支/HEAD、watchers、预览、命令日志)',
|
description: '获取插件本地状态(git 分支/HEAD、watchers、预览、命令日志)',
|
||||||
|
|||||||
Reference in New Issue
Block a user