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
155 lines
4.7 KiB
JavaScript
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,
|
|
};
|