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
137 lines
5.2 KiB
JavaScript
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'));
|
|
});
|