Files
mingyuansi b65a4f22c3 fix: Windows spawn, prefab path, preview API, asset deps, error messages; add execute-context resource, tool descriptions, scene/script/asset tools
Bug fixes (verified against Cocos Creator 3.8.8):
- diagnostics: shell:true for .cmd/.bat on Windows (T125/T126)
- prefabs: duplicatePrefab target resolves under assets/ (T420)
- cocos-project: add preview.open candidate for 3.8.8 (T444)
- assets-advanced: detect directory assets in inspectAssetDependencies (T110)
- scene: improve component-not-found errors with compilation hint (T429)

Documentation improvements:
- tool-registry: add enum and injected vars to execute_javascript, path format examples, asset ref limitation note
- resources: add cocos://mcp/execute-context resource with variables, patterns, pitfalls

New tools (core 37->38, full 101->110):
- scene-management: create_scene, query_scene_state (core), copy_paste_node, rename_node, reparent_node
- scripts: create_script with component/plain templates
- prefabs: create_prefab
- assets-advanced: batch_asset_ops, find_unused_assets
2026-06-30 21:53:06 +08:00

155 lines
4.7 KiB
JavaScript

'use strict';
const fs = require('fs');
const path = require('path');
const { execFile } = require('child_process');
function exists(filePath) {
try {
return fs.existsSync(filePath);
} catch (error) {
return false;
}
}
function findTypescriptBinary(projectPath) {
const tscName = process.platform === 'win32' ? 'tsc.cmd' : 'tsc';
const possibleRoots = [
global.Editor && Editor.App && Editor.App.path,
process.resourcesPath,
global.Editor && Editor.App && Editor.App.path ? path.dirname(Editor.App.path) : '',
global.Editor && Editor.App && Editor.App.path ? path.resolve(Editor.App.path, '..') : '',
].filter(Boolean);
const editorBundledCandidates = [];
for (const root of possibleRoots) {
editorBundledCandidates.push(
path.join(root, 'resources', '3d', 'engine', 'node_modules', '.bin', tscName),
path.join(root, 'resources', '3d', 'engine', 'node_modules', 'typescript', 'bin', 'tsc'),
path.join(root, 'resources', '3d', 'engine', 'node_modules', '@cocos', 'typescript', 'bin', 'tsc'),
path.join(root, 'app.asar.unpacked', 'node_modules', 'typescript', 'bin', 'tsc'),
path.join(root, 'Contents', 'Resources', 'resources', '3d', 'engine', 'node_modules', '.bin', tscName),
path.join(root, 'Contents', 'Resources', 'resources', '3d', 'engine', 'node_modules', 'typescript', 'bin', 'tsc'),
path.join(root, 'Contents', 'Resources', 'resources', '3d', 'engine', 'node_modules', '@cocos', 'typescript', 'bin', 'tsc')
);
}
const candidates = [
path.join(projectPath, 'node_modules', '.bin', tscName),
path.join(projectPath, 'node_modules', 'typescript', 'bin', 'tsc'),
...editorBundledCandidates,
process.platform === 'win32' ? 'npx.cmd' : 'npx',
];
for (const candidate of candidates) {
if (candidate.includes(path.sep) && exists(candidate)) {
return candidate;
}
if (!candidate.includes(path.sep)) {
return candidate;
}
}
return process.platform === 'win32' ? 'npx.cmd' : 'npx';
}
function findTsConfig(projectPath, explicitPath) {
if (explicitPath) {
return path.isAbsolute(explicitPath) ? explicitPath : path.join(projectPath, explicitPath);
}
const candidates = [
path.join(projectPath, 'tsconfig.json'),
path.join(projectPath, 'temp', 'tsconfig.cocos.json'),
];
return candidates.find((candidate) => exists(candidate)) || '';
}
function runExec(file, args, cwd) {
return new Promise((resolve) => {
const options = { cwd, maxBuffer: 8 * 1024 * 1024 };
if (process.platform === 'win32' && /\.(cmd|bat)$/.test(file)) {
options.shell = true;
}
execFile(file, args, options, (error, stdout, stderr) => {
resolve({
code: error && typeof error.code === 'number' ? error.code : 0,
stdout: stdout || '',
stderr: stderr || '',
error: error ? error.message : '',
});
});
});
}
function parseTscOutput(output) {
const lines = String(output || '')
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean);
const diagnostics = [];
const regex = /^(.*)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.*)$/i;
for (const line of lines) {
const match = regex.exec(line);
if (!match) {
continue;
}
diagnostics.push({
file: match[1],
line: Number(match[2]),
column: Number(match[3]),
code: match[4],
message: match[5],
});
}
return diagnostics;
}
async function runScriptDiagnostics(projectPath, options = {}) {
const tsconfigPath = findTsConfig(projectPath, options.tsconfigPath);
if (!tsconfigPath || !exists(tsconfigPath)) {
return {
ok: false,
tool: 'typescript',
summary: 'No tsconfig.json was found in the Cocos project.',
diagnostics: [],
stdout: '',
stderr: '',
};
}
const binary = findTypescriptBinary(projectPath);
const args = binary.endsWith('npx') || binary.endsWith('npx.cmd')
? ['tsc', '--noEmit', '-p', tsconfigPath, '--pretty', 'false']
: ['--noEmit', '-p', tsconfigPath, '--pretty', 'false'];
const result = await runExec(binary, args, projectPath);
const mergedOutput = [result.stdout, result.stderr, result.error].filter(Boolean).join('\n').trim();
const diagnostics = parseTscOutput(mergedOutput);
const ok = result.code === 0 && diagnostics.length === 0;
return {
ok,
tool: 'typescript',
binary,
tsconfigPath,
exitCode: result.code,
summary: ok
? 'TypeScript diagnostics completed successfully with no errors.'
: diagnostics.length
? `Found ${diagnostics.length} TypeScript error(s).`
: mergedOutput || 'TypeScript diagnostics reported a non-zero exit code.',
diagnostics,
stdout: result.stdout,
stderr: result.stderr,
};
}
module.exports = {
runScriptDiagnostics,
};