mirror of
https://github.com/HappyLifeOk/cc-3-8-x-mcp.git
synced 2026-06-10 17:56:47 +00:00
[mcp] 修复: 支持 CLI 打开项目和资源转发
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
function uniqExisting(paths) {
|
||||
const out = [];
|
||||
const seen = new Set();
|
||||
paths.forEach(function (p) {
|
||||
if (!p || seen.has(p)) return;
|
||||
seen.add(p);
|
||||
try {
|
||||
if (fs.existsSync(p)) out.push(p);
|
||||
} catch (e) { /* ignore */ }
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
function envCandidates(env) {
|
||||
return [
|
||||
env.COCOS_CREATOR,
|
||||
env.COCOS_CREATOR_PATH,
|
||||
env.COCOS_DASHBOARD_CREATOR,
|
||||
];
|
||||
}
|
||||
|
||||
function buildCocosExecPathCandidates(version, platform, env, homeDir) {
|
||||
env = env || process.env;
|
||||
const versions = version ? [version] : [];
|
||||
const candidates = envCandidates(env);
|
||||
|
||||
if (platform === 'win32') {
|
||||
const localAppData = env.LOCALAPPDATA || path.win32.join(homeDir, 'AppData', 'Local');
|
||||
const programFiles = env.ProgramFiles || 'C:\\Program Files';
|
||||
const programFilesX86 = env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
|
||||
const roots = [
|
||||
env.COCOS_EDITOR_ROOT,
|
||||
path.win32.join(localAppData, 'CocosCreator'),
|
||||
path.win32.join(localAppData, 'Programs', 'CocosCreator'),
|
||||
path.win32.join(programFiles, 'CocosCreator'),
|
||||
path.win32.join(programFilesX86, 'CocosCreator'),
|
||||
'C:\\ProgramData\\cocos\\editors\\Creator',
|
||||
'C:\\cocos\\editors\\Creator',
|
||||
'D:\\cocos\\editors\\Creator',
|
||||
'H:\\cocos\\editors\\Creator',
|
||||
];
|
||||
versions.forEach(function (v) {
|
||||
roots.forEach(function (root) {
|
||||
if (!root) return;
|
||||
candidates.push(path.win32.join(root, v, 'CocosCreator.exe'));
|
||||
});
|
||||
candidates.push(path.win32.join('C:\\', 'CocosCreator_' + v, 'CocosCreator.exe'));
|
||||
candidates.push(path.win32.join('D:\\', 'CocosCreator_' + v, 'CocosCreator.exe'));
|
||||
candidates.push(path.win32.join('H:\\', 'CocosCreator_' + v, 'CocosCreator.exe'));
|
||||
});
|
||||
} else if (platform === 'darwin') {
|
||||
versions.forEach(function (v) {
|
||||
candidates.push('/Applications/Cocos/Creator/' + v + '/CocosCreator.app/Contents/MacOS/CocosCreator');
|
||||
candidates.push('/Applications/CocosCreator/Creator/' + v + '/CocosCreator.app/Contents/MacOS/CocosCreator');
|
||||
candidates.push('/Applications/CocosCreator_' + v + '.app/Contents/MacOS/CocosCreator');
|
||||
candidates.push(path.posix.join(homeDir, 'Applications', 'Cocos', 'Creator', v, 'CocosCreator.app', 'Contents', 'MacOS', 'CocosCreator'));
|
||||
});
|
||||
} else {
|
||||
versions.forEach(function (v) {
|
||||
candidates.push('/opt/Cocos/Creator/' + v + '/CocosCreator');
|
||||
candidates.push('/opt/cocos/creator/' + v + '/CocosCreator');
|
||||
candidates.push(path.posix.join(homeDir, 'Cocos', 'Creator', v, 'CocosCreator'));
|
||||
});
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function resolveCocosExec(opts) {
|
||||
const a = opts || {};
|
||||
if (a.cocos) {
|
||||
const explicit = path.resolve(a.cocos);
|
||||
if (!fs.existsSync(explicit)) {
|
||||
throw new Error('--cocos path does not exist: ' + explicit);
|
||||
}
|
||||
return explicit;
|
||||
}
|
||||
|
||||
const found = uniqExisting(buildCocosExecPathCandidates(a.version, process.platform, process.env, os.homedir()));
|
||||
if (found.length === 1) return found[0];
|
||||
if (found.length > 1) {
|
||||
throw new Error('multiple Cocos Creator executables found, pass --cocos explicitly: ' + found.join(', '));
|
||||
}
|
||||
if (a.version) {
|
||||
throw new Error('Cocos Creator ' + a.version + ' not found. Pass --cocos <absolute executable path>.');
|
||||
}
|
||||
throw new Error('missing Cocos Creator executable. Pass --cocos <path> or --version <version>.');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildCocosExecPathCandidates,
|
||||
resolveCocosExec,
|
||||
uniqExisting,
|
||||
};
|
||||
@@ -15,6 +15,8 @@ Usage:
|
||||
cocos-mcp-cli diff <prefabA> <prefabB> # 字段级 diff
|
||||
cocos-mcp-cli create-prefab <out> [--name X] [--width W] [--height H] [--add-spine <uuid>]
|
||||
cocos-mcp-cli extract-prefab <src> <out> --node <selector> [--name X] [--dry-run]
|
||||
cocos-mcp-cli open <project> --version 3.8.8
|
||||
cocos-mcp-cli open --project <project> --cocos <CocosCreator executable>
|
||||
|
||||
Commands:
|
||||
query 只读查询,输出 JSON
|
||||
|
||||
@@ -21,6 +21,7 @@ const { cmdCompactPrefab } = require('./compact-cmd.js');
|
||||
const { cmdEnsureMeta } = require('./ensure-meta-cmd.js');
|
||||
const { cmdBuild } = require('./build-cmd.js');
|
||||
const { cmdFixMeta } = require('./fix-meta-cmd.js');
|
||||
const { cmdOpen } = require('./open-cmd.js');
|
||||
|
||||
function die(msg) {
|
||||
process.stderr.write('Error: ' + msg + '\n');
|
||||
@@ -56,6 +57,8 @@ function main(argv) {
|
||||
cmdEnsureMeta(rest);
|
||||
} else if (cmd === 'build') {
|
||||
cmdBuild(rest);
|
||||
} else if (cmd === 'open') {
|
||||
cmdOpen(rest);
|
||||
} else if (cmd === 'fix-meta') {
|
||||
cmdFixMeta(rest);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
'use strict';
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { resolveCocosExec } = require('./cocos-path.js');
|
||||
|
||||
function die(msg) {
|
||||
process.stderr.write('Error: ' + msg + '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function parseArgs(rest) {
|
||||
const a = { project: '', cocos: '', version: '', dryRun: false, wait: false, noLogin: true };
|
||||
for (let i = 0; i < rest.length; i++) {
|
||||
const k = rest[i];
|
||||
if (k === '--project' || k === '-p') a.project = rest[++i];
|
||||
else if (k === '--cocos' || k === '-c') a.cocos = rest[++i];
|
||||
else if (k === '--version' || k === '-v') a.version = rest[++i];
|
||||
else if (k === '--dry-run') a.dryRun = true;
|
||||
else if (k === '--wait') a.wait = true;
|
||||
else if (k === '--no-login' || k === '--nologin') a.noLogin = true;
|
||||
else if (k === '--with-login') a.noLogin = false;
|
||||
else if (!a.project && k[0] !== '-') a.project = k;
|
||||
else die('unknown argument "' + k + '". See cocos-mcp-cli open --help');
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function buildOpenArgs(projectPath, opts) {
|
||||
const args = ['--project', projectPath];
|
||||
if (!opts || opts.noLogin !== false) args.push('--nologin');
|
||||
return args;
|
||||
}
|
||||
|
||||
function cmdOpen(rest) {
|
||||
if (rest[0] === '--help' || rest[0] === '-h') {
|
||||
process.stdout.write(
|
||||
'cocos-mcp-cli open - open a Cocos Creator project\n\n' +
|
||||
'Usage:\n' +
|
||||
' cocos-mcp-cli open <project> --version 3.8.8\n' +
|
||||
' cocos-mcp-cli open --project <project> --cocos <CocosCreator executable>\n\n' +
|
||||
'Options:\n' +
|
||||
' --project, -p <path> Cocos project root. Positional <project> is also accepted.\n' +
|
||||
' --version, -v <ver> Resolve Cocos Creator by version from common install paths.\n' +
|
||||
' --cocos, -c <path> CocosCreator executable path. Takes precedence over --version.\n' +
|
||||
' --no-login Add Cocos --nologin. Default.\n' +
|
||||
' --with-login Do not add --nologin.\n' +
|
||||
' --wait Wait for the Cocos process to exit instead of detaching.\n' +
|
||||
' --dry-run Print the command without launching.\n'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const a = parseArgs(rest);
|
||||
if (!a.project) die('missing project path: pass <project> or --project <path>');
|
||||
a.project = path.resolve(a.project);
|
||||
if (!fs.existsSync(a.project)) die('project path does not exist: ' + a.project);
|
||||
if (!fs.existsSync(path.join(a.project, 'assets'))) die('not a Cocos project root, missing assets/: ' + a.project);
|
||||
|
||||
let cocos;
|
||||
try {
|
||||
cocos = resolveCocosExec(a);
|
||||
} catch (e) {
|
||||
die(e.message);
|
||||
}
|
||||
|
||||
const argv = buildOpenArgs(a.project, a);
|
||||
if (a.dryRun) {
|
||||
process.stdout.write('[dry-run] ' + cocos + ' ' + argv.map(function (x) {
|
||||
return /\s/.test(x) ? '"' + x + '"' : x;
|
||||
}).join(' ') + '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
process.stdout.write('Opening Cocos project:\n ' + a.project + '\n');
|
||||
const child = spawn(cocos, argv, {
|
||||
stdio: a.wait ? 'inherit' : 'ignore',
|
||||
detached: !a.wait,
|
||||
});
|
||||
child.on('error', function (e) { die('failed to start Cocos Creator: ' + e.message); });
|
||||
if (a.wait) {
|
||||
child.on('exit', function (code) { process.exit(code == null ? 1 : code); });
|
||||
} else {
|
||||
child.unref();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { cmdOpen, parseArgs, buildOpenArgs };
|
||||
@@ -190,3 +190,27 @@ test('CLI set active 非法 value: 非零退出', () => {
|
||||
const result = run(['set', FIXTURE, 'touchArea', 'active', 'maybe']);
|
||||
assert.notEqual(result.status, 0);
|
||||
});
|
||||
test('CLI open --dry-run: accepts positional project and builds --project command', () => {
|
||||
const project = fs.mkdtempSync(path.join(os.tmpdir(), 'cocos-open-project-'));
|
||||
fs.mkdirSync(path.join(project, 'assets'));
|
||||
|
||||
const result = run(['open', project, '--cocos', process.execPath, '--dry-run']);
|
||||
assert.equal(result.status, 0, `stderr: ${result.stderr}`);
|
||||
assert.match(result.stdout, /\[dry-run\]/);
|
||||
assert.match(result.stdout, /--project/);
|
||||
assert.match(result.stdout, /--nologin/);
|
||||
assert.match(result.stdout, new RegExp(project.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')));
|
||||
|
||||
fs.rmSync(project, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('CLI open --with-login: dry-run omits --nologin', () => {
|
||||
const project = fs.mkdtempSync(path.join(os.tmpdir(), 'cocos-open-project-'));
|
||||
fs.mkdirSync(path.join(project, 'assets'));
|
||||
|
||||
const result = run(['open', '--project', project, '--cocos', process.execPath, '--with-login', '--dry-run']);
|
||||
assert.equal(result.status, 0, `stderr: ${result.stderr}`);
|
||||
assert.doesNotMatch(result.stdout, /--nologin/);
|
||||
|
||||
fs.rmSync(project, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user