cocos2d-cli 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Cocos Creator CLI
4
+ * Command-line tools for AI to read and manipulate Cocos Creator 2.4.x project scenes
5
+ */
6
+
7
+ const path = require('path');
8
+
9
+ // 命令映射
10
+ const commands = {
11
+ tree: '../src/commands/tree',
12
+ get: '../src/commands/get',
13
+ set: '../src/commands/set',
14
+ add: '../src/commands/add',
15
+ 'add-component': '../src/commands/add-component',
16
+ 'remove': '../src/commands/remove',
17
+ delete: '../src/commands/delete',
18
+ build: '../src/commands/build'
19
+ };
20
+
21
+ // 帮助信息
22
+ function showHelp() {
23
+ console.log(`
24
+ Cocos Creator CLI - 场景操作工具集
25
+
26
+ 用法:
27
+ cocos2.4 <command> [options]
28
+
29
+ 命令:
30
+ tree <场景文件路径> 查看节点树
31
+ get <场景文件路径> <节点> 获取节点信息
32
+ set <场景文件路径> <节点> [选项] 修改节点属性
33
+ add <场景文件路径> <父节点> <名称> 添加节点
34
+ add-component <场景文件路径> <节点> <类型> 给节点添加组件
35
+ remove <场景路径> <索引> 删除节点或组件
36
+ delete <场景文件路径> <节点> 删除节点
37
+ build <项目目录> 构建组件映射
38
+
39
+ 选项:
40
+ --name=<名称> 修改节点名称
41
+ --active=true/false 修改激活状态
42
+ --x=<数值> 修改 X 坐标
43
+ --y=<数值> 修改 Y 坐标
44
+ --width=<数值> 修改宽度
45
+ --height=<数值> 修改高度
46
+ --anchorX=<0-1> 修改锚点 X
47
+ --anchorY=<0-1> 修改锚点 Y
48
+ --opacity=<0-255> 修改透明度
49
+ --color=<#RRGGBB> 修改颜色
50
+ --rotation=<角度> 修改旋转角度
51
+ --scaleX=<数值> 修改 X 缩放
52
+ --scaleY=<数值> 修改 Y 缩放
53
+ --type=sprite/label 添加节点时指定组件类型
54
+ --at=<位置> 添加节点时指定插入位置
55
+
56
+ 示例:
57
+ cocos2.4 tree assets/main.fire
58
+ cocos2.4 get assets/main.fire Canvas
59
+ cocos2.4 set assets/main.fire Canvas/Player --x=100 --y=200
60
+ cocos2.4 add assets/main.fire Canvas NewSprite --type=sprite --x=100
61
+ cocos2.4 add-component assets/main.fire Canvas/Player sprite
62
+ cocos2.4 delete assets/main.fire OldNode
63
+ cocos2.4 build ./my-project
64
+
65
+ 版本: 1.0.2
66
+ `);
67
+ }
68
+
69
+ // 解析参数
70
+ const args = process.argv.slice(2);
71
+
72
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
73
+ showHelp();
74
+ process.exit(0);
75
+ }
76
+
77
+ const commandName = args[0];
78
+ const commandPath = commands[commandName];
79
+
80
+ if (!commandPath) {
81
+ console.error(`未知命令: ${commandName}`);
82
+ console.error('运行 cocos-cli --help 查看可用命令');
83
+ process.exit(1);
84
+ }
85
+
86
+ // 加载并执行命令
87
+ try {
88
+ const command = require(commandPath);
89
+ command.run(args.slice(1));
90
+ } catch (err) {
91
+ console.error(`命令执行失败: ${err.message}`);
92
+ process.exit(1);
93
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "01e12Vix3xPZaV9rG8NNMhT": "CharacterAnimation",
3
+ "0c1120T6UNNk5mllwLCpN0/": "GameScene",
4
+ "0c5a3bepqdAUZOCSqDOe7tX": "StageManager",
5
+ "166a2V/C3pMsLYQh5Cfhh0M": "Tile",
6
+ "1777aIKiDVM2ayJgXQsFRbc": "InfoNode",
7
+ "2000bQ16aVPOowsYmWzQHBl": "TilemapCamera",
8
+ "25dfekurxJEpa4v3Hll7c7S": "PlayerController",
9
+ "2a19dBOCe1GUpzKNfMsQQR7": "Pathfinder",
10
+ "329ecFYmkpK+ZTeUQlUmyHq": "MapCharacter",
11
+ "36c13QGxrxExoOv5anzjyRj": "EventManager",
12
+ "394e4Gg6E1Dh6AedkHAFZH+": "index",
13
+ "3cc7eQO1TZPTYTHr3v/2h1L": "SceneScript",
14
+ "42cc8kWoTtFHqEMb8ClTUBk": "GameExpressionEvaluator",
15
+ "7541bC/enRKeocYItojXgyK": "Gamepiece",
16
+ "781adeySrdN27K4uANiWTCT": "MinimapRenderer",
17
+ "9701aXPPNhO2IJ1bgr/25pJ": "PlayerManager",
18
+ "a4e62KPZVNEbZO1mvkzyOQt": "FilmEffect",
19
+ "a4ff8dbnCJNsLLBFaNYXrQp": "Player",
20
+ "a89e2IVZx5Bn50oCifRT+GC": "Gameboard",
21
+ "c4069o+d29NprrTzmS9V1Mb": "ResourceManager",
22
+ "e0920R8VWpPOpxr0VnPlCqA": "GamepieceRegistry",
23
+ "e24c1oXqIpLvqqgMFn+zl3A": "Tilemap",
24
+ "f07feXxRJVMj7Vg6tTtIi+T": "index"
25
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "cocos2d-cli",
3
+ "version": "1.0.3",
4
+ "description": "Command-line tools for AI to read and manipulate Cocos Creator 2.4.x project scenes",
5
+ "main": "src/lib/index.js",
6
+ "bin": {
7
+ "cocos2.4": "./bin/cocos-cli.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "data"
13
+ ],
14
+ "scripts": {
15
+ "test": "bash test/run.sh",
16
+ "test:structure": "node test/verify_structure.js",
17
+ "test:cli": "bash test/run.sh"
18
+ },
19
+ "keywords": [
20
+ "cocos",
21
+ "cocos-creator",
22
+ "cli",
23
+ "scene",
24
+ "fire"
25
+ ],
26
+ "author": "",
27
+ "license": "MIT",
28
+ "engines": {
29
+ "node": ">=14.0.0"
30
+ }
31
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * add-component 命令 - 给已存在的节点添加组件
3
+ */
4
+
5
+ const { loadScene, saveScene, buildMaps, findNodeIndex, refreshEditor } = require('../lib/fire-utils');
6
+ const { Components, generateId } = require('../lib/components');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ // 加载脚本映射
11
+ function loadScriptMap(projectPath) {
12
+ const mapPath = path.join(projectPath, 'data', 'script_map.json');
13
+ try {
14
+ if (fs.existsSync(mapPath)) {
15
+ return JSON.parse(fs.readFileSync(mapPath, 'utf-8'));
16
+ }
17
+ } catch (e) {
18
+ // 忽略错误
19
+ }
20
+ return {};
21
+ }
22
+
23
+ // 创建自定义脚本组件
24
+ function createScriptComponent(scriptUuid, nodeId, scriptMap) {
25
+ const scriptInfo = scriptMap[scriptUuid];
26
+ const typeName = scriptInfo ? scriptInfo.name : scriptUuid;
27
+
28
+ return {
29
+ "__type__": typeName,
30
+ "_name": "",
31
+ "_objFlags": 0,
32
+ "node": { "__id__": nodeId },
33
+ "_enabled": true,
34
+ "_id": generateId()
35
+ };
36
+ }
37
+
38
+ function run(args) {
39
+ if (args.length < 3) {
40
+ console.log(JSON.stringify({
41
+ error: '用法: cocos2.4 add-component <场景文件路径> <节点路径> <组件类型>'
42
+ }));
43
+ return;
44
+ }
45
+
46
+ const scenePath = args[0];
47
+ const nodeRef = args[1];
48
+ const componentType = args[2];
49
+
50
+ try {
51
+ const data = loadScene(scenePath);
52
+ const { indexMap } = buildMaps(data);
53
+
54
+ // 查找节点
55
+ const nodeIndex = findNodeIndex(data, indexMap, nodeRef);
56
+
57
+ if (nodeIndex === null || !data[nodeIndex]) {
58
+ console.log(JSON.stringify({ error: `找不到节点: ${nodeRef}` }));
59
+ return;
60
+ }
61
+
62
+ const node = data[nodeIndex];
63
+
64
+ // 检查是否已有该类型组件
65
+ const existingComp = node._components?.find(comp => {
66
+ const compData = data[comp.__id__];
67
+ if (!compData) return false;
68
+ const compType = compData.__type__;
69
+ return compType === componentType || compType === 'cc.' + componentType.charAt(0).toUpperCase() + componentType.slice(1);
70
+ });
71
+
72
+ if (existingComp) {
73
+ console.log(JSON.stringify({
74
+ error: `节点 "${node._name}" 已有 ${componentType} 组件`,
75
+ nodeIndex,
76
+ nodeName: node._name
77
+ }));
78
+ return;
79
+ }
80
+
81
+ // 加载脚本映射(从场景文件所在项目的 data 目录)
82
+ const projectPath = path.dirname(scenePath);
83
+ const scriptMap = loadScriptMap(projectPath);
84
+
85
+ // 创建组件
86
+ let componentData;
87
+ const compIndex = data.length;
88
+
89
+ // 检查是否是内置组件
90
+ const builtInTypes = ['sprite', 'label', 'button', 'layout', 'widget', 'camera', 'canvas', 'particleSystem'];
91
+
92
+ if (builtInTypes.includes(componentType.toLowerCase())) {
93
+ // 内置组件
94
+ const typeKey = componentType.toLowerCase();
95
+ componentData = Components[typeKey]?.(nodeIndex);
96
+
97
+ if (!componentData) {
98
+ console.log(JSON.stringify({ error: `不支持的组件类型: ${componentType}` }));
99
+ return;
100
+ }
101
+ } else {
102
+ // 自定义脚本组件 - 查找脚本UUID
103
+ let scriptUuid = null;
104
+
105
+ // 在 scriptMap 中查找
106
+ for (const [uuid, info] of Object.entries(scriptMap)) {
107
+ if (info.name === componentType) {
108
+ scriptUuid = uuid;
109
+ break;
110
+ }
111
+ }
112
+
113
+ // 如果没找到,尝试直接使用 componentType 作为类型名
114
+ if (!scriptUuid) {
115
+ // 检查是否是有效的 UUID 格式
116
+ const uuidRegex = /^[a-f0-9-]{36}$/i;
117
+ if (uuidRegex.test(componentType)) {
118
+ scriptUuid = componentType;
119
+ }
120
+ }
121
+
122
+ componentData = createScriptComponent(scriptUuid || componentType, nodeIndex, scriptMap);
123
+ }
124
+
125
+ // 添加组件到数组
126
+ data.push(componentData);
127
+
128
+ // 更新节点的 _components
129
+ if (!node._components) node._components = [];
130
+ node._components.push({ "__id__": compIndex });
131
+
132
+ // 保存场景
133
+ saveScene(scenePath, data);
134
+
135
+ // 触发编辑器刷新(传入场景路径以重新打开场景)
136
+ refreshEditor(scenePath);
137
+
138
+ console.log(JSON.stringify({
139
+ success: true,
140
+ componentIndex: compIndex,
141
+ componentType: componentData.__type__,
142
+ nodeIndex,
143
+ nodeName: node._name,
144
+ message: `组件 "${componentData.__type__}" 已添加到节点 "${node._name}"`
145
+ }, null, 2));
146
+ } catch (err) {
147
+ console.log(JSON.stringify({ error: err.message }));
148
+ }
149
+ }
150
+
151
+ module.exports = { run };
@@ -0,0 +1,227 @@
1
+ /**
2
+ * add 命令 - 添加节点
3
+ * 按照 Cocos Creator 的深度优先遍历顺序插入节点
4
+ */
5
+
6
+ const { loadScene, saveScene, buildMaps, findNodeIndex, refreshEditor } = require('../lib/fire-utils');
7
+ const { Components, createNodeData } = require('../lib/components');
8
+
9
+ /**
10
+ * 获取节点及其所有子树的最后一个索引(用于确定插入位置)
11
+ * 按照深度优先遍历,返回该节点子树的结束位置
12
+ */
13
+ function getSubtreeEndIndex(data, nodeIndex) {
14
+ const node = data[nodeIndex];
15
+ if (!node) return nodeIndex;
16
+
17
+ let lastIndex = nodeIndex;
18
+
19
+ // 递归处理所有子节点
20
+ if (node._children) {
21
+ for (const childRef of node._children) {
22
+ const childEnd = getSubtreeEndIndex(data, childRef.__id__);
23
+ lastIndex = Math.max(lastIndex, childEnd);
24
+ }
25
+ }
26
+
27
+ // 处理组件(组件在子节点之后)
28
+ if (node._components) {
29
+ for (const compRef of node._components) {
30
+ lastIndex = Math.max(lastIndex, compRef.__id__);
31
+ }
32
+ }
33
+
34
+ return lastIndex;
35
+ }
36
+
37
+ /**
38
+ * 重建所有 __id__ 引用(插入元素后索引变化)
39
+ * 返回新旧索引的映射表
40
+ */
41
+ function rebuildReferencesForInsert(data, insertIndex) {
42
+ const indexMap = {};
43
+
44
+ // 构建映射:旧索引 -> 新索引
45
+ for (let oldIndex = 0; oldIndex < data.length; oldIndex++) {
46
+ if (oldIndex < insertIndex) {
47
+ indexMap[oldIndex] = oldIndex;
48
+ } else {
49
+ indexMap[oldIndex] = oldIndex + 1;
50
+ }
51
+ }
52
+
53
+ // 更新所有 __id__ 引用
54
+ function updateRef(obj) {
55
+ if (!obj || typeof obj !== 'object') return;
56
+
57
+ if (obj.__id__ !== undefined) {
58
+ const oldId = obj.__id__;
59
+ if (indexMap[oldId] !== undefined) {
60
+ obj.__id__ = indexMap[oldId];
61
+ }
62
+ } else {
63
+ for (const key of Object.keys(obj)) {
64
+ updateRef(obj[key]);
65
+ }
66
+ }
67
+ }
68
+
69
+ for (const item of data) {
70
+ updateRef(item);
71
+ }
72
+
73
+ return indexMap;
74
+ }
75
+
76
+ function run(args) {
77
+ if (args.length < 3) {
78
+ console.log(JSON.stringify({ error: '用法: cocos2.4 add <场景文件路径> <父节点> <节点名称> [选项]' }));
79
+ return;
80
+ }
81
+
82
+ const scenePath = args[0];
83
+ const parentRef = args[1];
84
+ const nodeName = args[2];
85
+
86
+ // 解析选项
87
+ const options = {};
88
+ args.slice(3).forEach(arg => {
89
+ if (arg.startsWith('--')) {
90
+ const [key, value] = arg.substring(2).split('=');
91
+ if (key === 'type') options.type = value;
92
+ else if (key === 'x') options.x = parseFloat(value) || 0;
93
+ else if (key === 'y') options.y = parseFloat(value) || 0;
94
+ else if (key === 'width') options.width = parseFloat(value) || 0;
95
+ else if (key === 'height') options.height = parseFloat(value) || 0;
96
+ else if (key === 'at') options.at = parseInt(value);
97
+ else if (key === 'active') options.active = value !== 'false';
98
+ }
99
+ });
100
+
101
+ try {
102
+ const data = loadScene(scenePath);
103
+ const { indexMap } = buildMaps(data);
104
+
105
+ // 查找父节点
106
+ const parentIndex = findNodeIndex(data, indexMap, parentRef);
107
+
108
+ if (parentIndex === null || !data[parentIndex]) {
109
+ console.log(JSON.stringify({ error: `找不到父节点: ${parentRef}` }));
110
+ return;
111
+ }
112
+
113
+ const parentNode = data[parentIndex];
114
+
115
+ // 确定插入位置
116
+ // 按照 _children 顺序,找到应该插入的位置
117
+ let insertIndex;
118
+ if (!parentNode._children || parentNode._children.length === 0) {
119
+ // 父节点没有子节点,新节点紧跟父节点之后
120
+ insertIndex = parentIndex + 1;
121
+ } else {
122
+ // 根据 --at 参数确定插入位置
123
+ const targetPosition = options.at >= 0 ? options.at : parentNode._children.length;
124
+
125
+ if (targetPosition === 0) {
126
+ // 插入到第一个子节点位置,紧跟父节点之后
127
+ insertIndex = parentIndex + 1;
128
+ } else if (targetPosition >= parentNode._children.length) {
129
+ // 插入到最后,在所有现有子节点之后
130
+ const lastChildRef = parentNode._children[parentNode._children.length - 1];
131
+ insertIndex = getSubtreeEndIndex(data, lastChildRef.__id__) + 1;
132
+ } else {
133
+ // 插入到中间某个位置
134
+ const beforeChildRef = parentNode._children[targetPosition - 1];
135
+ insertIndex = getSubtreeEndIndex(data, beforeChildRef.__id__) + 1;
136
+ }
137
+ }
138
+
139
+ // 创建新节点
140
+ const newNode = createNodeData(nodeName, parentIndex, options);
141
+
142
+ // 在正确位置插入新节点
143
+ data.splice(insertIndex, 0, newNode);
144
+
145
+ // 重建索引引用(因为插入了新元素,后续索引都+1)
146
+ const insertIndexMap = rebuildReferencesForInsert(data, insertIndex);
147
+
148
+ // 新节点的实际索引
149
+ const newNodeIndex = insertIndex;
150
+
151
+ // 更新新节点的 _parent 引用(使用映射后的父节点索引)
152
+ const newParentIndex = insertIndexMap[parentIndex] !== undefined ? insertIndexMap[parentIndex] : parentIndex;
153
+ newNode._parent = { "__id__": newParentIndex };
154
+
155
+ // 添加组件(如果有)
156
+ let componentIndex = -1;
157
+ if (options.type) {
158
+ const compData = Components[options.type]?.(newNodeIndex);
159
+ if (compData) {
160
+ // 组件应该放在新节点子树之后(这里新节点没有子节点,所以紧跟节点后)
161
+ const compInsertIndex = newNodeIndex + 1;
162
+ data.splice(compInsertIndex, 0, compData);
163
+
164
+ // 再次重建引用
165
+ rebuildReferencesForInsert(data, compInsertIndex);
166
+
167
+ componentIndex = compInsertIndex;
168
+ newNode._components.push({ "__id__": componentIndex });
169
+ }
170
+ }
171
+
172
+ // 更新父节点的 _children
173
+ if (!parentNode._children) parentNode._children = [];
174
+
175
+ const insertPosition = options.at >= 0 ? options.at : parentNode._children.length;
176
+ parentNode._children.splice(insertPosition, 0, { "__id__": newNodeIndex });
177
+
178
+ // 保存场景
179
+ saveScene(scenePath, data);
180
+
181
+ // 触发编辑器刷新(传入场景路径以重新打开场景)
182
+ refreshEditor(scenePath);
183
+
184
+ // 构建节点树(类似 tree 命令)
185
+ function buildTree(nodeIndex, prefix = '', isLast = true, isRoot = true) {
186
+ const node = data[nodeIndex];
187
+ if (!node) return '';
188
+
189
+ const nodeName = isRoot ? 'Root' : (node._name || '(unnamed)');
190
+ const active = node._active !== false ? '●' : '○';
191
+ const connector = isRoot ? '' : (isLast ? '└── ' : '├── ');
192
+
193
+ let result = prefix + (isRoot ? '' : active + ' ') + nodeName + ' #' + nodeIndex;
194
+
195
+ // 添加组件信息
196
+ if (node._components && node._components.length > 0) {
197
+ const comps = node._components.map(c => {
198
+ const comp = data[c.__id__];
199
+ if (!comp) return `? #${c.__id__}`;
200
+ return `${comp.__type__.replace('cc.', '')} #${c.__id__}`;
201
+ }).join(', ');
202
+ result += ` (${comps})`;
203
+ }
204
+
205
+ result += '\n';
206
+
207
+ // 处理子节点
208
+ if (node._children && node._children.length > 0) {
209
+ node._children.forEach((childRef, idx) => {
210
+ const childIsLast = idx === node._children.length - 1;
211
+ const childPrefix = prefix + (isRoot ? '' : (isLast ? ' ' : '│ '));
212
+ result += buildTree(childRef.__id__, childPrefix, childIsLast, false);
213
+ });
214
+ }
215
+
216
+ return result;
217
+ }
218
+
219
+ const treeStr = buildTree(1);
220
+
221
+ console.log(treeStr);
222
+ } catch (err) {
223
+ console.log(JSON.stringify({ error: err.message }));
224
+ }
225
+ }
226
+
227
+ module.exports = { run };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * build 命令 - 构建组件映射
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ function run(args) {
9
+ if (args.length < 1) {
10
+ console.log(JSON.stringify({ error: '用法: cocos-cli build <项目目录>' }));
11
+ return;
12
+ }
13
+
14
+ const projectDir = args[0];
15
+ const importsDir = path.join(projectDir, 'library', 'imports');
16
+ const outputFile = path.join(__dirname, '../../data/script_map.json');
17
+
18
+ if (!fs.existsSync(importsDir)) {
19
+ console.log(JSON.stringify({ error: `imports 目录不存在: ${importsDir}` }));
20
+ return;
21
+ }
22
+
23
+ const scriptMap = {};
24
+ let processedCount = 0;
25
+
26
+ function scanDirectory(dir) {
27
+ const items = fs.readdirSync(dir);
28
+
29
+ for (const item of items) {
30
+ const fullPath = path.join(dir, item);
31
+ const stat = fs.statSync(fullPath);
32
+
33
+ if (stat.isDirectory()) {
34
+ scanDirectory(fullPath);
35
+ } else if (item.endsWith('.js')) {
36
+ try {
37
+ const content = fs.readFileSync(fullPath, 'utf8');
38
+
39
+ // 查找 cc._RF.push 调用
40
+ const match = content.match(/cc\._RF\.push\(module,\s*['"]([^'"]+)['"],\s*['"]([^'"]+)['"]\)/);
41
+
42
+ if (match) {
43
+ const hash = match[1];
44
+ const className = match[2];
45
+
46
+ // 只存储脚本相关的哈希(不以 'cc.' 开头的)
47
+ if (!className.startsWith('cc.')) {
48
+ scriptMap[hash] = className;
49
+ processedCount++;
50
+ }
51
+ }
52
+ } catch (err) {
53
+ // 忽略读取错误
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ scanDirectory(importsDir);
60
+
61
+ // 确保输出目录存在
62
+ const outputDir = path.dirname(outputFile);
63
+ if (!fs.existsSync(outputDir)) {
64
+ fs.mkdirSync(outputDir, { recursive: true });
65
+ }
66
+
67
+ // 写入输出文件
68
+ fs.writeFileSync(outputFile, JSON.stringify(scriptMap, null, 2), 'utf8');
69
+
70
+ console.log(JSON.stringify({
71
+ success: true,
72
+ count: Object.keys(scriptMap).length,
73
+ outputFile,
74
+ message: `构建完成,共 ${Object.keys(scriptMap).length} 个脚本映射`
75
+ }, null, 2));
76
+ }
77
+
78
+ module.exports = { run };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * delete 命令 - 删除节点
3
+ */
4
+
5
+ const { loadScene, saveScene, buildMaps, collectNodeAndChildren, rebuildReferences, findNodeIndex, refreshEditor } = require('../lib/fire-utils');
6
+
7
+ function run(args) {
8
+ if (args.length < 2) {
9
+ console.log(JSON.stringify({ error: '用法: cocos2.4 delete <场景文件路径> <节点索引|名称>' }));
10
+ return;
11
+ }
12
+
13
+ const scenePath = args[0];
14
+ const nodeRef = args[1];
15
+
16
+ try {
17
+ const data = loadScene(scenePath);
18
+ const { indexMap } = buildMaps(data);
19
+
20
+ // 查找节点
21
+ const nodeIndex = findNodeIndex(data, indexMap, nodeRef);
22
+
23
+ if (nodeIndex === null || !data[nodeIndex]) {
24
+ console.log(JSON.stringify({ error: `找不到节点: ${nodeRef}` }));
25
+ return;
26
+ }
27
+
28
+ if (nodeIndex <= 1) {
29
+ console.log(JSON.stringify({ error: '不能删除根节点' }));
30
+ return;
31
+ }
32
+
33
+ const node = data[nodeIndex];
34
+ const nodeName = node._name;
35
+
36
+ // 收集所有需要删除的索引(节点 + 子节点 + 组件)
37
+ const indicesToDelete = collectNodeAndChildren(data, nodeIndex);
38
+
39
+ // 从父节点的 _children 中移除引用
40
+ if (node._parent) {
41
+ const parentIndex = node._parent.__id__;
42
+ const parent = data[parentIndex];
43
+ if (parent && parent._children) {
44
+ parent._children = parent._children.filter(c => c.__id__ !== nodeIndex);
45
+ }
46
+ }
47
+
48
+ // 重建引用(更新所有 __id__)
49
+ rebuildReferences(data, indicesToDelete);
50
+
51
+ // 真正删除元素(从大到小排序,避免索引错乱)
52
+ const sortedIndices = Array.from(indicesToDelete).sort((a, b) => b - a);
53
+ for (const idx of sortedIndices) {
54
+ data.splice(idx, 1);
55
+ }
56
+
57
+ // 保存场景
58
+ saveScene(scenePath, data);
59
+
60
+ // 触发编辑器刷新(传入场景路径以重新打开场景)
61
+ refreshEditor(scenePath);
62
+
63
+ console.log(JSON.stringify({
64
+ success: true,
65
+ message: `节点 "${nodeName}" 已删除`,
66
+ deletedCount: indicesToDelete.size
67
+ }, null, 2));
68
+ } catch (err) {
69
+ console.log(JSON.stringify({ error: err.message }));
70
+ }
71
+ }
72
+
73
+ module.exports = { run };