Files
funplay-cocos-mcp/test/find-unused-assets.test.js
T
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

137 lines
5.2 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const test = require('node:test');
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const { findUnusedAssets, collectUuidReferences } = require('../lib/tools/assets-advanced');
function createMockAssetsDir() {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'funplay-unused-test-'));
const assetsDir = path.join(tmpDir, 'assets');
fs.mkdirSync(assetsDir, { recursive: true });
return { tmpDir, assetsDir };
}
function writeFile(dir, relPath, content) {
const fullPath = path.join(dir, relPath);
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, content, 'utf8');
}
test('findUnusedAssets returns assets not referenced by any serialized file', async () => {
const { tmpDir, assetsDir } = createMockAssetsDir();
try {
const usedUuid = 'aaa11111-bbbb-cccc-dddd-eeeeeeee1111';
writeFile(assetsDir, 'textures/used.png', 'fake-png');
writeFile(assetsDir, 'textures/used.png.meta', JSON.stringify({ uuid: usedUuid }));
const unusedUuid = 'aaa22222-bbbb-cccc-dddd-eeeeeeee2222';
writeFile(assetsDir, 'textures/unused.png', 'fake-png');
writeFile(assetsDir, 'textures/unused.png.meta', JSON.stringify({ uuid: unusedUuid }));
writeFile(assetsDir, 'scenes/game.scene', JSON.stringify({
__type__: 'cc.SceneAsset',
scene: { __uuid__: usedUuid },
}));
const mockListAssets = async () => [
{ uuid: usedUuid, url: 'db://assets/textures/used.png', type: 'cc.Texture2D' },
{ uuid: unusedUuid, url: 'db://assets/textures/unused.png', type: 'cc.Texture2D' },
];
const result = await findUnusedAssets(tmpDir, {}, mockListAssets);
assert.equal(result.totalScanned, 2);
assert.equal(result.unusedCount, 1);
assert.equal(result.unused[0].uuid, unusedUuid);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
test('findUnusedAssets skips .ts and .scene files from unused list', async () => {
const { tmpDir, assetsDir } = createMockAssetsDir();
try {
const scriptUuid = 'bbb11111-bbbb-cccc-dddd-eeeeeeee3333';
const sceneUuid = 'bbb22222-bbbb-cccc-dddd-eeeeeeee4444';
writeFile(assetsDir, 'scripts/MyScript.ts', 'export class MyScript {}');
writeFile(assetsDir, 'scripts/MyScript.ts.meta', JSON.stringify({ uuid: scriptUuid }));
writeFile(assetsDir, 'scenes/level.scene', '{}');
writeFile(assetsDir, 'scenes/level.scene.meta', JSON.stringify({ uuid: sceneUuid }));
const mockListAssets = async () => [
{ uuid: scriptUuid, url: 'db://assets/scripts/MyScript.ts', type: 'cc.ScriptAsset' },
{ uuid: sceneUuid, url: 'db://assets/scenes/level.scene', type: 'cc.SceneAsset' },
];
const result = await findUnusedAssets(tmpDir, {}, mockListAssets);
assert.equal(result.totalScanned, 2);
assert.equal(result.unusedCount, 0);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
test('findUnusedAssets respects excludeDirectories', async () => {
const { tmpDir, assetsDir } = createMockAssetsDir();
try {
const referencedUuid = 'ccc11111-bbbb-cccc-dddd-eeeeeeee5555';
const unreferencedUuid = 'ccc22222-bbbb-cccc-dddd-eeeeeeee6666';
writeFile(assetsDir, 'textures/a.png', 'fake');
writeFile(assetsDir, 'textures/a.png.meta', JSON.stringify({ uuid: referencedUuid }));
writeFile(assetsDir, 'internal/b.png', 'fake');
writeFile(assetsDir, 'internal/b.png.meta', JSON.stringify({ uuid: unreferencedUuid }));
writeFile(assetsDir, 'internal/hidden.scene', JSON.stringify({
__type__: 'cc.SceneAsset',
tex: { __uuid__: referencedUuid },
}));
const mockListAssets = async () => [
{ uuid: referencedUuid, url: 'db://assets/textures/a.png', type: 'cc.Texture2D' },
{ uuid: unreferencedUuid, url: 'db://assets/internal/b.png', type: 'cc.Texture2D' },
];
const result = await findUnusedAssets(tmpDir, {
excludeDirectories: ['internal'],
}, mockListAssets);
assert.equal(result.totalScanned, 2);
assert.equal(result.unusedCount, 2);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
test('findUnusedAssets respects limit option', async () => {
const { tmpDir, assetsDir } = createMockAssetsDir();
try {
const assets = [];
for (let i = 0; i < 5; i++) {
const uuid = `ddd${i}1111-bbbb-cccc-dddd-eeeeeeee777${i}`;
writeFile(assetsDir, `textures/tex${i}.png`, 'fake');
writeFile(assetsDir, `textures/tex${i}.png.meta`, JSON.stringify({ uuid }));
assets.push({ uuid, url: `db://assets/textures/tex${i}.png`, type: 'cc.Texture2D' });
}
const mockListAssets = async () => assets;
const result = await findUnusedAssets(tmpDir, { limit: 3 }, mockListAssets);
assert.equal(result.totalScanned, 5);
assert.equal(result.unusedCount, 5);
assert.equal(result.unused.length, 3);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
test('collectUuidReferences is re-exported and works', () => {
const refs = collectUuidReferences(JSON.stringify({
__uuid__: 'abc12345-def6-7890-abcd-ef1234567890',
}));
assert.ok(refs.some((r) => r.uuid === 'abc12345-def6-7890-abcd-ef1234567890'));
});