cocos2d-cli 1.6.2 → 1.6.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.
package/README.md CHANGED
@@ -1,142 +1,491 @@
1
- # cocos2d-cli
1
+ # cocos2d-cli
2
2
 
3
- Cocos Creator 场景/预制体命令行操作工具,支持通过 CLI AI 自动化创建和编辑游戏界面。
3
+ Cocos Creator 2.4.x 场景 / 预制体命令行工具。
4
4
 
5
- ## 功能
5
+ 它可以直接操作 `.fire` 和 `.prefab` 文件,也支持从简化 JSON 生成场景、预制体,以及通过截图方式快速预览 UI 结构。
6
6
 
7
- - 创建场景和预制体
8
- - 添加/删除节点和组件
9
- - 修改节点和组件属性
10
- - 查看节点树结构
11
- - 从简化 JSON 创建复杂界面
7
+ 适用场景:
8
+
9
+ - 批量修改 Cocos Creator 资源
10
+ - 通过脚本或 AI 自动生成 UI
11
+ - 在不打开编辑器的情况下预览 JSON 描述的界面
12
+
13
+ ## 特性
14
+
15
+ - 查看场景 / 预制体节点树
16
+ - 查询节点属性和组件属性
17
+ - 修改节点属性
18
+ - 修改组件属性
19
+ - 添加 / 删除节点
20
+ - 添加 / 删除组件
21
+ - 从 JSON 创建预制体
22
+ - 从 JSON 创建场景
23
+ - 构建脚本组件映射
24
+ - 将 JSON 渲染为截图
25
+
26
+ ## 环境要求
27
+
28
+ - Node.js >= 14
29
+ - Cocos Creator 2.4.x
30
+
31
+ > `screenshot` 命令依赖 `playwright`。
12
32
 
13
33
  ## 安装
14
34
 
35
+ 全局安装:
36
+
15
37
  ```bash
16
38
  npm install -g cocos2d-cli
17
39
  ```
18
40
 
19
- ## 使用
41
+ 或在项目中直接使用:
42
+
43
+ ```bash
44
+ npx cocos2d-cli --help
45
+ ```
46
+
47
+ ## 快速开始
48
+
49
+ 查看帮助:
50
+
51
+ ```bash
52
+ cocos2d-cli --help
53
+ ```
54
+
55
+ 查看节点树:
56
+
57
+ ```bash
58
+ cocos2d-cli tree assets/main.fire
59
+ cocos2d-cli tree assets/panel.prefab
60
+ ```
61
+
62
+ 从 JSON 预览并生成预制体:
63
+
64
+ ```bash
65
+ cocos2d-cli screenshot panel.json -o ./screenshots --width 750 --height 1334
66
+ cocos2d-cli create-prefab panel.json assets/Panel.prefab
67
+ ```
68
+
69
+ ## 命令说明
70
+
71
+ ### tree
72
+
73
+ 查看场景或预制体的节点树。
74
+
75
+ ```bash
76
+ cocos2d-cli tree <scene.fire | prefab.prefab>
77
+ ```
78
+
79
+ 示例:
80
+
81
+ ```bash
82
+ cocos2d-cli tree assets/main.fire
83
+ ```
84
+
85
+ ### get
86
+
87
+ 获取节点属性,或获取节点上某个组件的属性。
88
+
89
+ ```bash
90
+ cocos2d-cli get <文件> <节点路径> [属性名 | 组件类型]
91
+ ```
92
+
93
+ 说明:
94
+
95
+ - 不传第三个参数:返回该节点全部基础属性
96
+ - 第三个参数是组件类型:返回该组件全部属性
97
+ - 第三个参数是属性名:返回该节点的单个属性
98
+
99
+ 示例:
100
+
101
+ ```bash
102
+ cocos2d-cli get assets/main.fire Canvas
103
+ cocos2d-cli get assets/main.fire Canvas/MidNode x
104
+ cocos2d-cli get assets/main.fire Canvas/Label Label
105
+ ```
106
+
107
+ ### set
108
+
109
+ 修改节点属性。
110
+
111
+ ```bash
112
+ cocos2d-cli set <文件> <节点路径> <属性名> <值>
113
+ ```
114
+
115
+ 当前支持的常用节点属性:
116
+
117
+ - `name`
118
+ - `active`
119
+ - `x`
120
+ - `y`
121
+ - `width`
122
+ - `height`
123
+ - `anchorX`
124
+ - `anchorY`
125
+ - `opacity`
126
+ - `scaleX`
127
+ - `scaleY`
128
+ - `rotation`
129
+
130
+ 示例:
131
+
132
+ ```bash
133
+ cocos2d-cli set assets/main.fire Canvas/MidNode x 100
134
+ cocos2d-cli set assets/main.fire Canvas/MidNode width 200
135
+ cocos2d-cli set assets/main.fire Canvas/MidNode active false
136
+ ```
137
+
138
+ ### set-component
139
+
140
+ 修改节点上某个组件的属性。
141
+
142
+ ```bash
143
+ cocos2d-cli set-component <文件> <节点路径> <组件类型> <属性名> <值>
144
+ ```
145
+
146
+ 示例:
147
+
148
+ ```bash
149
+ cocos2d-cli set-component assets/main.fire Canvas/Label Label string "Hello"
150
+ cocos2d-cli set-component assets/main.fire Canvas/Label Label fontSize 32
151
+ cocos2d-cli set-component assets/main.fire Canvas/Sprite Sprite sizeMode 0
152
+ ```
153
+
154
+ > 不同组件是否支持修改,取决于该组件类是否实现了 `setProp`。
155
+
156
+ ### add
157
+
158
+ 在指定父节点下添加新节点。
159
+
160
+ ```bash
161
+ cocos2d-cli add <文件> <父节点路径> <节点名称> [选项]
162
+ ```
163
+
164
+ 支持选项:
165
+
166
+ - `--x=<数值>`
167
+ - `--y=<数值>`
168
+ - `--width=<数值>`
169
+ - `--height=<数值>`
170
+ - `--scaleX=<数值>`
171
+ - `--scaleY=<数值>`
172
+ - `--rotation=<角度>`
173
+ - `--active=true|false`
174
+ - `--type=sprite|label|button`
175
+ - `--string=<文本>`(配合 `--type=label`)
176
+ - `--fontSize=<数值>`(配合 `--type=label`)
177
+
178
+ 示例:
179
+
180
+ ```bash
181
+ cocos2d-cli add assets/main.fire Canvas NewSprite --type=sprite --x=100 --y=50 --width=120 --height=120
182
+ cocos2d-cli add assets/main.fire Canvas Title --type=label --string=Hello --fontSize=32
183
+ ```
184
+
185
+ ### remove
186
+
187
+ 删除指定节点。
188
+
189
+ ```bash
190
+ cocos2d-cli remove <文件> <节点路径>
191
+ ```
192
+
193
+ 示例:
194
+
195
+ ```bash
196
+ cocos2d-cli remove assets/main.fire Canvas/MidNode
197
+ ```
198
+
199
+ > 不能删除根节点。
200
+
201
+ ### add-component
202
+
203
+ 给节点添加组件。
204
+
205
+ ```bash
206
+ cocos2d-cli add-component <文件> <节点路径> <组件类型>
207
+ ```
208
+
209
+ 当前命令中支持的组件类型:
210
+
211
+ - `canvas`
212
+ - `widget`
213
+ - `sprite`
214
+ - `label`
215
+ - `button`
216
+ - `camera`
217
+
218
+ 示例:
20
219
 
21
220
  ```bash
22
- cocos2d-cli help # 查看帮助
23
- cocos2d-cli --help # 同上
221
+ cocos2d-cli add-component assets/main.fire Canvas/NewNode sprite
222
+ cocos2d-cli add-component assets/main.fire Canvas/Main Camera camera
24
223
  ```
25
224
 
26
- ### 创建预制体
225
+ ### remove-component
226
+
227
+ 删除节点上的组件。
27
228
 
28
- ### 创建预制体
229
+ ```bash
230
+ cocos2d-cli remove-component <文件> <节点路径> <组件类型>
231
+ ```
232
+
233
+ 示例:
29
234
 
30
235
  ```bash
31
- # 创建空预制体
32
- cocos2d-cli create-prefab ./MyPrefab.prefab
236
+ cocos2d-cli remove-component assets/main.fire Canvas/NewNode sprite
237
+ ```
238
+
239
+ ### create-prefab
33
240
 
34
- # 从 JSON 创建预制体
35
- cocos2d-cli create-prefab ./ui.json ./MyPrefab.prefab
241
+ 创建预制体。
242
+
243
+ ```bash
244
+ cocos2d-cli create-prefab [JSON文件路径] <输出路径.prefab>
36
245
  ```
37
246
 
38
- ### 创建场景
247
+ 行为说明:
248
+
249
+ - 只传输出路径:创建默认预制体
250
+ - 传入 JSON 文件:根据 JSON 创建预制体
251
+ - 会自动生成对应 `.meta` 文件
252
+
253
+ 示例:
39
254
 
40
255
  ```bash
41
- # 创建默认场景(含 Canvas 和 Main Camera)
42
- cocos2d-cli create-scene ./MyScene.fire
256
+ cocos2d-cli create-prefab assets/NewNode.prefab
257
+ cocos2d-cli create-prefab panel.json assets/Panel.prefab
258
+ ```
43
259
 
44
- # 从 JSON 创建场景
45
- cocos2d-cli create-scene ./ui.json ./MyScene.fire
260
+ ### create-scene
261
+
262
+ 创建场景。
263
+
264
+ ```bash
265
+ cocos2d-cli create-scene [JSON文件路径] <输出路径.fire>
46
266
  ```
47
267
 
48
- ### 节点操作
268
+ 行为说明:
269
+
270
+ - 只传输出路径:创建默认场景
271
+ - 默认场景包含 `Canvas` 和 `Main Camera`
272
+ - 若能在上级目录找到 `settings/project.json`,会读取设计分辨率
273
+ - 会自动生成对应 `.meta` 文件
274
+
275
+ 示例:
49
276
 
50
277
  ```bash
51
- # 添加节点
52
- cocos2d-cli add ./MyPrefab.prefab Root NewNode --type=sprite --width=100 --height=100
278
+ cocos2d-cli create-scene assets/Main.fire
279
+ cocos2d-cli create-scene scene.json assets/Scene.fire
280
+ ```
281
+
282
+ ### build
53
283
 
54
- # 删除节点
55
- cocos2d-cli remove ./MyPrefab.prefab Root/NewNode
284
+ 扫描 Cocos Creator 项目的 `library/imports`,构建脚本哈希到类名的映射文件,用于更好地识别自定义脚本组件。
56
285
 
57
- # 查看节点树
58
- cocos2d-cli tree ./MyPrefab.prefab
286
+ ```bash
287
+ cocos2d-cli build <项目目录>
59
288
  ```
60
289
 
61
- ### 组件操作
290
+ 示例:
62
291
 
63
292
  ```bash
64
- # 添加组件
65
- cocos2d-cli add-component ./MyPrefab.prefab Root label
293
+ cocos2d-cli build D:/my-cocos-project
294
+ ```
295
+
296
+ 输出文件默认写到:
66
297
 
67
- # 删除组件
68
- cocos2d-cli remove-component ./MyPrefab.prefab Root label
298
+ ```text
299
+ cocos2d-cli/data/script_map.json
300
+ ```
301
+
302
+ ### screenshot
303
+
304
+ 将 JSON 渲染为页面并截图。
305
+
306
+ ```bash
307
+ cocos2d-cli screenshot <json文件> [选项]
69
308
  ```
70
309
 
71
- ### 属性操作
310
+ 支持选项:
311
+
312
+ - `-o, --output <目录>` 输出目录,默认当前目录
313
+ - `--width <数值>` 视口宽度,默认 `750`
314
+ - `--height <数值>` 视口高度,默认 `1334`
315
+ - `--full-page` 全页截图(默认)
316
+ - `--no-full-page` 仅视口截图
317
+ - `--debug-bounds` 叠加节点边界和名称
318
+ - `--timeout <毫秒>` 默认 `30000`
319
+ - `--wait <毫秒>` 默认 `1000`
320
+
321
+ 示例:
72
322
 
73
323
  ```bash
74
- # 获取属性
75
- cocos2d-cli get ./MyPrefab.prefab Root width
76
- cocos2d-cli get ./MyPrefab.prefab Root label.string
324
+ cocos2d-cli screenshot data.json
325
+ cocos2d-cli screenshot data.json -o ./screenshots
326
+ cocos2d-cli screenshot data.json --width 1080 --height 1920
327
+ cocos2d-cli screenshot data.json --debug-bounds
328
+ ```
329
+
330
+ > `-o` / `--output` 是输出目录,不是输出图片文件名。
331
+
332
+ ## 节点路径格式
77
333
 
78
- # 设置属性
79
- cocos2d-cli set ./MyPrefab.prefab Root width 200
80
- cocos2d-cli set ./MyPrefab.prefab Root label.string "Hello"
334
+ 节点路径使用 `/` 分隔:
335
+
336
+ ```text
337
+ Canvas
338
+ Canvas/MidNode
339
+ Canvas/GameScene/NodeA
81
340
  ```
82
341
 
83
- ## 简化 JSON 格式
342
+ 如果路径首段和根节点同名,工具会自动兼容。
343
+
344
+ ## JSON 格式
84
345
 
85
- AI 可以通过简化 JSON 描述界面结构:
346
+ ### 基本示例
86
347
 
87
348
  ```json
88
349
  {
89
- "name": "MyButton",
90
- "width": 200,
91
- "height": 60,
92
- "components": ["sprite", "button"],
350
+ "name": "Panel",
351
+ "width": 400,
352
+ "height": 300,
353
+ "x": 0,
354
+ "y": 0,
355
+ "anchorX": 0.5,
356
+ "anchorY": 0.5,
357
+ "color": "#336699",
358
+ "opacity": 255,
359
+ "components": [
360
+ "sprite",
361
+ { "type": "label", "string": "Hello", "fontSize": 28, "horizontalAlign": "center" }
362
+ ],
93
363
  "children": [
94
364
  {
95
- "name": "Label",
96
- "components": [
97
- {
98
- "type": "label",
99
- "string": "Click Me",
100
- "fontSize": 24
101
- }
102
- ]
365
+ "name": "Btn",
366
+ "width": 200,
367
+ "height": 60,
368
+ "components": ["sprite", "button"]
103
369
  }
104
370
  ]
105
371
  }
106
372
  ```
107
373
 
108
- ### 支持的组件类型
374
+ ### 常用节点字段
375
+
376
+ - `name`
377
+ - `x`, `y`
378
+ - `width`, `height`
379
+ - `anchorX`, `anchorY`
380
+ - `scaleX`, `scaleY`
381
+ - `rotation`
382
+ - `opacity`
383
+ - `active`
384
+ - `color`
385
+ - `components`
386
+ - `children`
387
+
388
+ ### JSON 中支持的组件类型
389
+
390
+ - `canvas`
391
+ - `widget`
392
+ - `sprite`
393
+ - `label`
394
+ - `button`
395
+ - `camera`
396
+ - `richText`
397
+
398
+ ### 组件示例
399
+
400
+ `label`:
401
+
402
+ ```json
403
+ {
404
+ "type": "label",
405
+ "string": "文本内容",
406
+ "fontSize": 28,
407
+ "lineHeight": 40,
408
+ "horizontalAlign": "left",
409
+ "verticalAlign": "center",
410
+ "color": "#ffffff"
411
+ }
412
+ ```
413
+
414
+ `richText`:
415
+
416
+ ```json
417
+ {
418
+ "type": "richText",
419
+ "string": "普通<color=#ff0000>红色</color>文字<br/>第二行",
420
+ "fontSize": 28,
421
+ "lineHeight": 40,
422
+ "maxWidth": 600,
423
+ "horizontalAlign": "left"
424
+ }
425
+ ```
426
+
427
+ `widget`:
428
+
429
+ ```json
430
+ {
431
+ "type": "widget",
432
+ "isAlignLeft": true,
433
+ "isAlignRight": true,
434
+ "isAlignTop": true,
435
+ "isAlignBottom": true,
436
+ "left": 0,
437
+ "right": 0,
438
+ "top": 0,
439
+ "bottom": 0
440
+ }
441
+ ```
442
+
443
+ ## 坐标与布局说明
109
444
 
110
- - `canvas` - Canvas
111
- - `widget` - Widget
112
- - `sprite` - Sprite
113
- - `label` - Label
114
- - `button` - Button
115
- - `camera` - Camera
445
+ Cocos 默认锚点在节点中心:
116
446
 
117
- ### 节点属性
447
+ - `anchorX = 0.5`
448
+ - `anchorY = 0.5`
118
449
 
119
- | 属性 | 说明 |
120
- |------|------|
121
- | `name` | 节点名称 |
122
- | `active` | 是否激活 |
123
- | `x`, `y` | 位置 |
124
- | `width`, `height` | 尺寸 |
125
- | `scaleX`, `scaleY` | 缩放 |
126
- | `rotation` | 旋转角度 |
127
- | `anchorX`, `anchorY` | 锚点 |
128
- | `opacity` | 透明度 |
129
- | `color` | 颜色 (十六进制如 "#ff0000") |
450
+ 因此 `x / y` 通常是相对父节点中心点的偏移。
130
451
 
131
- ## 架构
452
+ 对于左对齐 / 右对齐文本,更推荐:
132
453
 
454
+ - `anchorX: 0` + `horizontalAlign: "left"`
455
+ - `anchorX: 1` + `horizontalAlign: "right"`
456
+
457
+ 示例:
458
+
459
+ ```json
460
+ {
461
+ "name": "Amount",
462
+ "anchorX": 1,
463
+ "x": 330,
464
+ "width": 300,
465
+ "components": [
466
+ { "type": "label", "string": "¥40.00", "horizontalAlign": "right" }
467
+ ]
468
+ }
133
469
  ```
134
- 输入 内存模型 输出
135
- ───── ──────── ─────
136
- 简化JSON ──→ json-parser ──→ CCNode树 ─┬─→ CCSceneAsset ──→ .fire
137
- 编辑器文件 ──→ fromJSON ──→ CC对象树 ──┘─→ CCPrefab ──→ .prefab
470
+
471
+ ## 注意事项
472
+
473
+ 1. JSON 输入必须是文件路径,不支持直接传 JSON 字符串
474
+ 2. `screenshot` 的输出参数是目录,不是图片路径
475
+ 3. `set-component` 是否可修改,取决于组件类是否实现 `setProp`
476
+ 4. `create-scene` 不传 JSON 时,会创建默认场景(含 `Canvas` 和 `Main Camera`)
477
+ 5. `create-prefab` / `create-scene` 会自动写入 `.meta` 文件
478
+ 6. `build` 生成的脚本映射会写入项目内的 `data/script_map.json`
479
+
480
+ ## 项目结构
481
+
482
+ ```text
483
+ bin/cocos2d-cli.js # CLI 入口
484
+ src/commands/ # 各命令实现
485
+ src/lib/ # 场景/节点/组件处理逻辑
486
+ data/script_map.json # 脚本组件映射
138
487
  ```
139
488
 
140
489
  ## License
141
490
 
142
- MIT
491
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cocos2d-cli",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "Command-line tools for AI to read and manipulate Cocos Creator 2.4.x project scenes",
5
5
  "main": "bin/cocos2d-cli.js",
6
6
  "bin": {
@@ -89,10 +89,13 @@ async function run(args) {
89
89
  options.outputDir = path.resolve(options.outputDir);
90
90
 
91
91
  try {
92
- const result = await takeScreenshot(options);
93
- console.log(`\n截图成功: ${result.screenshotPath}`);
92
+ await takeScreenshot(options);
93
+ console.log('成功');
94
94
  } catch (error) {
95
- console.error(`\n截图失败: ${error.message}`);
95
+ console.error('失败');
96
+ if (error.logDir) {
97
+ console.error(`日志目录: ${error.logDir}`);
98
+ }
96
99
  process.exit(1);
97
100
  }
98
101
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Screenshot Core Module
2
+ * Screenshot Core Module
3
3
  * 渲染 JSON 数据并使用 Playwright 截图
4
4
  */
5
5
 
@@ -36,7 +36,7 @@ async function createTempWorkDir() {
36
36
  }
37
37
 
38
38
  // 复制内置资源到临时目录
39
- async function copyBuiltInAssets(tempDir) {
39
+ async function copyBuiltInAssets(tempDir, logs) {
40
40
  const assetsDir = getAssetsDir();
41
41
  const assets = ['index.html', 'favicon.ico'];
42
42
 
@@ -46,13 +46,17 @@ async function copyBuiltInAssets(tempDir) {
46
46
  try {
47
47
  await fs.copyFile(src, dest);
48
48
  } catch (err) {
49
- console.log(`Warning: Could not copy ${asset}: ${err.message}`);
49
+ logs.push({
50
+ timestamp: new Date().toISOString(),
51
+ type: 'warning',
52
+ text: `Could not copy ${asset}: ${err.message}`
53
+ });
50
54
  }
51
55
  }
52
56
  }
53
57
 
54
58
  // 静态文件服务器
55
- async function startServer(staticDir) {
59
+ async function startServer(staticDir, logs) {
56
60
  return new Promise((resolve, reject) => {
57
61
  const server = http.createServer(async (req, res) => {
58
62
  try {
@@ -94,7 +98,11 @@ async function startServer(staticDir) {
94
98
  res.writeHead(404);
95
99
  res.end();
96
100
  } else {
97
- console.error(`[Server Error] ${req.url}:`, err.message);
101
+ logs.push({
102
+ timestamp: new Date().toISOString(),
103
+ type: 'server-error',
104
+ text: `${req.url}: ${err.message}`
105
+ });
98
106
  res.writeHead(500);
99
107
  res.end();
100
108
  }
@@ -106,11 +114,17 @@ async function startServer(staticDir) {
106
114
  }
107
115
 
108
116
  // 递归删除目录
109
- async function removeDir(dirPath) {
117
+ async function removeDir(dirPath, logs) {
110
118
  try {
111
119
  await fs.rm(dirPath, { recursive: true, force: true });
112
120
  } catch (err) {
113
- console.log(`Warning: Could not remove temp dir: ${err.message}`);
121
+ if (logs) {
122
+ logs.push({
123
+ timestamp: new Date().toISOString(),
124
+ type: 'warning',
125
+ text: `Could not remove temp dir: ${err.message}`
126
+ });
127
+ }
114
128
  }
115
129
  }
116
130
 
@@ -137,6 +151,16 @@ async function takeScreenshot(userConfig = {}) {
137
151
  let tempDir = null;
138
152
  const logs = [];
139
153
  let screenshotPath = null;
154
+ let logDir = null;
155
+
156
+ const addLog = (type, text, extra = {}) => {
157
+ logs.push({
158
+ timestamp: new Date().toISOString(),
159
+ type,
160
+ text,
161
+ ...extra
162
+ });
163
+ };
140
164
 
141
165
  try {
142
166
  const timestamp = Date.now();
@@ -153,32 +177,32 @@ async function takeScreenshot(userConfig = {}) {
153
177
 
154
178
  // 创建临时工作目录
155
179
  tempDir = await createTempWorkDir();
156
- console.log(`Temp dir: ${tempDir}`);
180
+ addLog('info', `Temp dir: ${tempDir}`);
157
181
 
158
182
  // 复制内置资源到临时目录
159
- await copyBuiltInAssets(tempDir);
160
-
183
+ await copyBuiltInAssets(tempDir, logs);
184
+
161
185
  // 复制用户的 JSON 文件到临时目录
162
186
  const destJsonPath = path.join(tempDir, 'data.json');
163
187
  await fs.copyFile(config.jsonPath, destJsonPath);
164
- console.log(`JSON: ${config.jsonPath}`);
188
+ addLog('info', `JSON: ${config.jsonPath}`);
165
189
 
166
190
  // 截图输出路径
167
191
  screenshotPath = path.join(config.outputDir, `screenshot-${timestamp}.png`);
168
192
 
169
193
  // 启动HTTP服务器
170
- console.log('\n=== Starting Server ===');
171
- server = await startServer(tempDir);
194
+ addLog('info', 'Starting Server');
195
+ server = await startServer(tempDir, logs);
172
196
  const serverUrl = `http://127.0.0.1:${server.address().port}`;
173
- console.log(`Server: ${serverUrl}`);
197
+ addLog('info', `Server: ${serverUrl}`);
174
198
 
175
199
  // 启动浏览器
176
- console.log('\n=== Launching Browser ===');
200
+ addLog('info', 'Launching Browser');
177
201
  browser = await chromium.launch({
178
202
  headless: true,
179
203
  channel: 'chrome'
180
204
  });
181
- console.log('Browser launched');
205
+ addLog('info', 'Browser launched');
182
206
 
183
207
  const page = await browser.newPage({
184
208
  viewport: config.viewport
@@ -187,44 +211,21 @@ async function takeScreenshot(userConfig = {}) {
187
211
  // 监听浏览器控制台日志
188
212
  page.on('console', msg => {
189
213
  const text = msg.text();
190
- logs.push({
191
- timestamp: new Date().toISOString(),
192
- type: msg.type(),
193
- text
194
- });
195
-
196
- if (msg.type() === 'error') {
197
- console.error(`[Browser Error] ${text}`);
198
- } else if (msg.type() === 'warning') {
199
- console.warn(`[Browser Warning] ${text}`);
200
- } else {
201
- console.log(`[Browser] ${text}`);
202
- }
214
+ addLog(msg.type(), text);
203
215
  });
204
216
 
205
217
  page.on('pageerror', error => {
206
- logs.push({
207
- timestamp: new Date().toISOString(),
208
- type: 'pageerror',
209
- text: error.message
210
- });
211
- console.error('[Page Error]', error.message);
218
+ addLog('pageerror', error.message);
212
219
  });
213
220
 
214
221
  page.on('requestfailed', request => {
215
222
  const failure = request.failure();
216
223
  const errorText = failure ? failure.errorText : 'Unknown error';
217
- logs.push({
218
- timestamp: new Date().toISOString(),
219
- type: 'requestfailed',
220
- url: request.url(),
221
- error: errorText
222
- });
223
- console.error(`[Request Failed] ${request.url()}`);
224
+ addLog('requestfailed', request.url(), { error: errorText });
224
225
  });
225
226
 
226
227
  // 加载页面
227
- console.log('\n=== Loading Page ===');
228
+ addLog('info', 'Loading Page');
228
229
  const pageUrl = config.debugBounds
229
230
  ? `${serverUrl}/index.html?debugBounds=true`
230
231
  : `${serverUrl}/index.html`;
@@ -232,33 +233,53 @@ async function takeScreenshot(userConfig = {}) {
232
233
  waitUntil: 'networkidle',
233
234
  timeout: config.timeout
234
235
  });
235
- console.log('Page loaded');
236
+ addLog('info', 'Page loaded');
236
237
 
237
238
  // 等待渲染
238
- console.log('\n=== Waiting for Render ===');
239
+ addLog('info', 'Waiting for Render');
239
240
  await page.waitForTimeout(config.waitTime);
240
- console.log('Wait complete');
241
+ addLog('info', 'Wait complete');
241
242
 
242
243
  // 截图
243
- console.log('\n=== Taking Screenshot ===');
244
+ addLog('info', 'Taking Screenshot');
244
245
  await page.screenshot({
245
246
  path: screenshotPath,
246
247
  fullPage: config.fullPage
247
248
  });
248
- console.log(`Screenshot saved: ${screenshotPath}`);
249
-
250
- console.log('\n=== Done ===');
249
+ addLog('info', `Screenshot saved: ${screenshotPath}`);
250
+ addLog('info', 'Done');
251
251
 
252
252
  return { screenshotPath, logs };
253
253
 
254
254
  } catch (error) {
255
- console.error('\nError:', error.message);
255
+ addLog('error', error.message);
256
+ try {
257
+ logDir = path.join(config.outputDir, `screenshot-logs-${Date.now()}`);
258
+ await fs.mkdir(logDir, { recursive: true });
259
+ await fs.writeFile(
260
+ path.join(logDir, 'logs.json'),
261
+ JSON.stringify({
262
+ error: error.message,
263
+ jsonPath: config.jsonPath,
264
+ outputDir: config.outputDir,
265
+ viewport: config.viewport,
266
+ fullPage: config.fullPage,
267
+ debugBounds: config.debugBounds,
268
+ timeout: config.timeout,
269
+ waitTime: config.waitTime,
270
+ screenshotPath,
271
+ logs
272
+ }, null, 2),
273
+ 'utf8'
274
+ );
275
+ } catch (_) {}
276
+ error.logDir = logDir;
256
277
  throw error;
257
278
  } finally {
258
279
  // 清理资源
259
280
  if (browser) await browser.close().catch(() => {});
260
281
  if (server) server.close();
261
- if (tempDir) await removeDir(tempDir);
282
+ if (tempDir) await removeDir(tempDir, logs);
262
283
  }
263
284
  }
264
285