rl-rockcli 0.0.2 → 0.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.
Files changed (75) hide show
  1. package/README.md +400 -0
  2. package/index.js +51 -21
  3. package/package.json +1 -1
  4. package/commands/log/core/constants.js +0 -237
  5. package/commands/log/core/display.js +0 -370
  6. package/commands/log/core/search.js +0 -330
  7. package/commands/log/core/tail.js +0 -216
  8. package/commands/log/core/utils.js +0 -424
  9. package/commands/log.js +0 -298
  10. package/commands/sandbox/core/log-bridge.js +0 -119
  11. package/commands/sandbox/core/replay/analyzer.js +0 -311
  12. package/commands/sandbox/core/replay/batch-orchestrator.js +0 -536
  13. package/commands/sandbox/core/replay/batch-task.js +0 -369
  14. package/commands/sandbox/core/replay/concurrent-display.js +0 -70
  15. package/commands/sandbox/core/replay/concurrent-orchestrator.js +0 -170
  16. package/commands/sandbox/core/replay/data-source.js +0 -86
  17. package/commands/sandbox/core/replay/display.js +0 -231
  18. package/commands/sandbox/core/replay/executor.js +0 -634
  19. package/commands/sandbox/core/replay/history-fetcher.js +0 -124
  20. package/commands/sandbox/core/replay/index.js +0 -338
  21. package/commands/sandbox/core/replay/loghouse-data-source.js +0 -177
  22. package/commands/sandbox/core/replay/pid-mapping.js +0 -26
  23. package/commands/sandbox/core/replay/request.js +0 -109
  24. package/commands/sandbox/core/replay/worker.js +0 -166
  25. package/commands/sandbox/core/session.js +0 -346
  26. package/commands/sandbox/log-bridge.js +0 -2
  27. package/commands/sandbox/ray.js +0 -2
  28. package/commands/sandbox/replay/analyzer.js +0 -311
  29. package/commands/sandbox/replay/batch-orchestrator.js +0 -536
  30. package/commands/sandbox/replay/batch-task.js +0 -369
  31. package/commands/sandbox/replay/concurrent-display.js +0 -70
  32. package/commands/sandbox/replay/concurrent-orchestrator.js +0 -170
  33. package/commands/sandbox/replay/display.js +0 -231
  34. package/commands/sandbox/replay/executor.js +0 -634
  35. package/commands/sandbox/replay/history-fetcher.js +0 -118
  36. package/commands/sandbox/replay/index.js +0 -338
  37. package/commands/sandbox/replay/pid-mapping.js +0 -26
  38. package/commands/sandbox/replay/request.js +0 -109
  39. package/commands/sandbox/replay/worker.js +0 -166
  40. package/commands/sandbox/replay.js +0 -2
  41. package/commands/sandbox/session.js +0 -2
  42. package/commands/sandbox-original.js +0 -1393
  43. package/commands/sandbox.js +0 -499
  44. package/help/help.json +0 -1071
  45. package/help/middleware.js +0 -71
  46. package/help/renderer.js +0 -800
  47. package/lib/plugin-context.js +0 -40
  48. package/sdks/sandbox/core/client.js +0 -845
  49. package/sdks/sandbox/core/config.js +0 -70
  50. package/sdks/sandbox/core/types.js +0 -74
  51. package/sdks/sandbox/httpLogger.js +0 -251
  52. package/sdks/sandbox/index.js +0 -9
  53. package/utils/asciiArt.js +0 -138
  54. package/utils/bun-compat.js +0 -59
  55. package/utils/ciPipelines.js +0 -138
  56. package/utils/cli.js +0 -17
  57. package/utils/command-router.js +0 -79
  58. package/utils/configManager.js +0 -503
  59. package/utils/dependency-resolver.js +0 -135
  60. package/utils/eagleeye_traceid.js +0 -151
  61. package/utils/envDetector.js +0 -78
  62. package/utils/execution_logger.js +0 -415
  63. package/utils/featureManager.js +0 -68
  64. package/utils/firstTimeTip.js +0 -44
  65. package/utils/hook-manager.js +0 -125
  66. package/utils/http-logger.js +0 -264
  67. package/utils/i18n.js +0 -139
  68. package/utils/image-progress.js +0 -159
  69. package/utils/logger.js +0 -154
  70. package/utils/plugin-loader.js +0 -124
  71. package/utils/plugin-manager.js +0 -348
  72. package/utils/ray_cli_wrapper.js +0 -746
  73. package/utils/sandbox-client.js +0 -419
  74. package/utils/terminal.js +0 -32
  75. package/utils/tips.js +0 -106
@@ -1,536 +0,0 @@
1
- const path = require('path');
2
- const fs = require('fs-extra');
3
- const logger = require('../../../../utils/logger');
4
- const { SandboxClient, SandboxConfig } = require('../../../../sdks/sandbox');
5
- const { RequestAnalyzer } = require('./analyzer');
6
- const { ReplayExecutor } = require('./executor');
7
- const { SandboxTask, BatchReplayResult, TaskStatus, formatDuration } = require('./batch-task');
8
- const { HistoryFetcher } = require('./history-fetcher');
9
-
10
- /**
11
- * 批量回放协调器
12
- * 管理多个沙箱的批量回放流程
13
- */
14
- class BatchReplayOrchestrator {
15
- /**
16
- * @param {Object} options - 配置选项
17
- * @param {number} options.concurrency - 并发数
18
- * @param {Object} options.baseConfig - 基础沙箱配置
19
- * @param {string} options.mode - 回放模式
20
- * @param {string} options.timing - 时间控制
21
- * @param {Array} options.filters - 过滤器
22
- * @param {boolean} options.autoFetchHistory - 是否自动获取历史
23
- * @param {boolean} options.continueOnError - 错误时是否继续
24
- * @param {boolean} options.quiet - 静默模式
25
- * @param {boolean} options.dryRun - 预演模式
26
- * @param {boolean} options.executeStop - 是否执行 stop 请求
27
- * @param {string} options.reportFile - 报告文件路径
28
- * @param {string} options.logFileBase - 日志文件基础路径
29
- */
30
- constructor(options) {
31
- this.concurrency = options.concurrency || 1;
32
- this.baseConfig = options.baseConfig;
33
- this.mode = options.mode || 'smart';
34
- this.timing = options.timing || 'none';
35
- this.interval = options.interval || 5;
36
- this.filters = options.filters || [];
37
- this.autoFetchHistory = options.autoFetchHistory !== false;
38
- this.continueOnError = options.continueOnError !== false;
39
- this.quiet = options.quiet || false;
40
- this.dryRun = options.dryRun || false;
41
- this.executeStop = options.executeStop || false;
42
- this.reportFile = options.reportFile || 'batch-replay-report.json';
43
- this.logFileBase = options.logFileBase || 'replay.log';
44
- this.verboseLog = options.verboseLog || false;
45
-
46
- this.historyFetcher = new HistoryFetcher();
47
- this.tasks = [];
48
- this.result = null;
49
- this.progressIntervalId = null;
50
- }
51
-
52
- /**
53
- * 解析输入文件
54
- * @param {string} filePath - 输入文件路径
55
- * @returns {Object} - 解析后的输入数据
56
- */
57
- parseInputFile(filePath) {
58
- if (!fs.existsSync(filePath)) {
59
- throw new Error(`Input file not found: ${filePath}`);
60
- }
61
-
62
- const content = fs.readFileSync(filePath, 'utf8');
63
- let data;
64
-
65
- try {
66
- data = JSON.parse(content);
67
- } catch (error) {
68
- throw new Error(`Failed to parse JSON from ${filePath}: ${error.message}`);
69
- }
70
-
71
- // 支持单个 history 文件格式 (有 sandboxId 和 requests)
72
- if (data.sandboxId && data.requests && Array.isArray(data.requests)) {
73
- // 转换为标准批量格式
74
- return {
75
- metadata: {
76
- description: `Single sandbox replay: ${data.name || data.sandboxId}`,
77
- sourceFile: filePath
78
- },
79
- defaults: {},
80
- sandboxes: [data]
81
- };
82
- }
83
-
84
- // 验证标准批量格式
85
- if (!data.sandboxes || !Array.isArray(data.sandboxes)) {
86
- throw new Error('Invalid input format: "sandboxes" array is required, or provide a single sandbox history file with "sandboxId" and "requests"');
87
- }
88
-
89
- if (data.sandboxes.length === 0) {
90
- throw new Error('No sandboxes specified in input file');
91
- }
92
-
93
- // 验证每个沙箱配置
94
- data.sandboxes.forEach((sandbox, index) => {
95
- if (!sandbox.sandboxId) {
96
- throw new Error(`Invalid sandbox at index ${index}: "sandboxId" is required`);
97
- }
98
- });
99
-
100
- return data;
101
- }
102
-
103
- /**
104
- * 从输入数据创建任务列表
105
- * @param {Object} inputData - 解析后的输入数据
106
- * @returns {SandboxTask[]}
107
- */
108
- createTasks(inputData) {
109
- const defaults = inputData.defaults || {};
110
- const tasks = [];
111
-
112
- inputData.sandboxes.forEach((sandboxConfig, index) => {
113
- const task = new SandboxTask(index, sandboxConfig, defaults);
114
- tasks.push(task);
115
- });
116
-
117
- return tasks;
118
- }
119
-
120
- /**
121
- * 执行批量回放
122
- * @param {string} inputFilePath - 输入文件路径
123
- * @returns {Promise<BatchReplayResult>}
124
- */
125
- async execute(inputFilePath) {
126
- // 1. 解析输入文件
127
- const inputData = this.parseInputFile(inputFilePath);
128
-
129
- // 2. 初始化结果对象
130
- this.result = new BatchReplayResult({
131
- inputFile: inputFilePath,
132
- inputMetadata: inputData.metadata || null,
133
- concurrency: this.concurrency,
134
- mode: this.mode,
135
- timing: this.timing
136
- });
137
- this.result.start();
138
-
139
- // 3. 创建任务列表
140
- this.tasks = this.createTasks(inputData);
141
-
142
- if (!this.quiet) {
143
- this.displayBatchSummary();
144
- }
145
-
146
- // 4. 预演模式 - 只显示输入文件摘要,不获取历史
147
- if (this.dryRun) {
148
- this.displayDryRunSummaryFromInput();
149
- this.result.end();
150
- return this.result;
151
- }
152
-
153
- // 5. 获取历史(如果需要)
154
- if (this.autoFetchHistory) {
155
- await this.fetchHistories();
156
- } else {
157
- // 标记所有预加载的任务为 ready,并显示进度
158
- if (!this.quiet) {
159
- console.log('\n📥 Phase 1/2: Validating Pre-loaded Histories');
160
- console.log('-'.repeat(40));
161
- }
162
-
163
- let completedCount = 0;
164
- this.tasks.forEach(task => {
165
- completedCount++;
166
- if (task.hasRequests()) {
167
- task.status = TaskStatus.READY;
168
- if (!this.quiet) {
169
- console.log(` [${completedCount}/${this.tasks.length}] ✅ ${task.name} (${task.requests.length} requests)`);
170
- }
171
- } else {
172
- task.status = TaskStatus.FAILED;
173
- task.error = 'No requests provided and auto-fetch-history is disabled';
174
- task.errorPhase = 'parse';
175
- if (!this.quiet) {
176
- console.log(` [${completedCount}/${this.tasks.length}] ❌ ${task.name} (no requests)`);
177
- }
178
- }
179
- });
180
- }
181
-
182
- // 6. 过滤出可执行的任务
183
- const validTasks = this.tasks.filter(t => t.status === TaskStatus.READY);
184
-
185
- if (validTasks.length === 0) {
186
- if (!this.quiet) {
187
- logger.warn('No valid tasks to execute after history fetch.');
188
- }
189
- this.result.end();
190
- this.result.generateFromTasks(this.tasks);
191
- await this.saveReport();
192
- return this.result;
193
- }
194
-
195
- // 7. 执行回放
196
- await this.executeReplays(validTasks);
197
-
198
- // 8. 生成结果
199
- this.result.end();
200
- this.result.generateFromTasks(this.tasks);
201
-
202
- // 9. 保存报告
203
- await this.saveReport();
204
-
205
- // 10. 显示摘要
206
- if (!this.quiet) {
207
- this.displayFinalSummary();
208
- }
209
-
210
- return this.result;
211
- }
212
-
213
- /**
214
- * 显示批量回放摘要
215
- */
216
- displayBatchSummary() {
217
- console.log('\n' + '═'.repeat(70));
218
- console.log('📋 Batch Replay Mode');
219
- console.log('═'.repeat(70));
220
- console.log(` Total Sandboxes: ${this.tasks.length}`);
221
- console.log(` Concurrency: ${this.concurrency}`);
222
- console.log(` Mode: ${this.mode}`);
223
- console.log(` Timing: ${this.timing}`);
224
- console.log(` Auto Fetch History: ${this.autoFetchHistory}`);
225
- console.log('═'.repeat(70) + '\n');
226
- }
227
-
228
- /**
229
- * 获取所有沙箱的历史
230
- */
231
- async fetchHistories() {
232
- if (!this.quiet) {
233
- console.log('\n📥 Phase 1/2: Fetching Histories');
234
- console.log('-'.repeat(40));
235
- }
236
-
237
- await this.historyFetcher.fetchBatch(
238
- this.tasks,
239
- this.concurrency,
240
- {
241
- quiet: this.quiet,
242
- onProgress: (completed, total, task) => {
243
- if (!this.quiet) {
244
- const status = task.status === TaskStatus.READY ? '✅' : '❌';
245
- const requestCount = task.requests ? task.requests.length : 0;
246
- console.log(` [${completed}/${total}] ${status} ${task.name} (${requestCount} requests)`);
247
- }
248
- }
249
- }
250
- );
251
- }
252
-
253
- /**
254
- * 执行回放任务
255
- * @param {SandboxTask[]} tasks - 待执行的任务
256
- */
257
- async executeReplays(tasks) {
258
- if (!this.quiet) {
259
- console.log('\n🔄 Phase 2/2: Executing Replays');
260
- console.log('-'.repeat(40));
261
- }
262
-
263
- // 创建任务队列
264
- const queue = [...tasks];
265
- let completed = 0;
266
-
267
- // 启动进度监控
268
- if (!this.quiet) {
269
- this.startProgressMonitor(tasks);
270
- }
271
-
272
- // Worker 函数
273
- const worker = async (workerId) => {
274
- while (queue.length > 0) {
275
- const task = queue.shift();
276
- if (!task) break;
277
-
278
- await this.executeReplayTask(task, workerId);
279
- completed++;
280
-
281
- if (!this.quiet) {
282
- const status = task.status === TaskStatus.COMPLETED ? '✅' :
283
- task.status === TaskStatus.FAILED ? '❌' : '⏸️';
284
- const successCount = task.replayResult ? task.replayResult.success : 0;
285
- const failedCount = task.replayResult ? task.replayResult.failed : 0;
286
- console.log(` [${completed}/${tasks.length}] ${status} ${task.name} (success: ${successCount}, failed: ${failedCount})`);
287
- }
288
- }
289
- };
290
-
291
- // 启动并发 workers
292
- const workers = [];
293
- const actualConcurrency = Math.min(this.concurrency, tasks.length);
294
-
295
- for (let i = 0; i < actualConcurrency; i++) {
296
- workers.push(worker(i + 1));
297
- }
298
-
299
- // 等待所有 workers 完成
300
- await Promise.allSettled(workers);
301
-
302
- // 停止进度监控
303
- this.stopProgressMonitor();
304
- }
305
-
306
- /**
307
- * 执行单个回放任务
308
- * @param {SandboxTask} task - 任务
309
- * @param {number} workerId - Worker ID
310
- */
311
- async executeReplayTask(task, workerId) {
312
- task.startReplay();
313
- task.logFile = this.getTaskLogFileName(task.index);
314
-
315
- try {
316
- // 创建沙箱配置
317
- const config = new SandboxConfig({
318
- baseUrl: this.baseConfig.baseUrl,
319
- xrlAuthorization: this.baseConfig.xrlAuthorization,
320
- cluster: task.cluster || this.baseConfig.cluster,
321
- userId: task.userId || this.baseConfig.userId,
322
- experimentId: task.experimentId || this.baseConfig.experimentId,
323
- extraHeaders: this.baseConfig.extraHeaders || {},
324
- });
325
-
326
- // 创建客户端
327
- const client = new SandboxClient(config);
328
-
329
- // 分析请求
330
- const analyzer = new RequestAnalyzer({
331
- mode: task.mode || this.mode,
332
- filters: [...this.filters, ...task.filters],
333
- executeStop: this.executeStop
334
- });
335
-
336
- const plan = analyzer.analyze(task.requests);
337
-
338
- if (plan.executeCount === 0) {
339
- task.completeReplay({
340
- total: 0,
341
- success: 0,
342
- failed: 0,
343
- skipped: plan.skippedCount
344
- }, null, []);
345
- return;
346
- }
347
-
348
- // 创建执行器
349
- const executor = new ReplayExecutor(client, {
350
- timing: task.timing || this.timing,
351
- interval: this.interval,
352
- quiet: true, // 批量模式下强制静默
353
- logFile: task.logFile,
354
- verboseLog: this.verboseLog,
355
- plan: plan,
356
- workerId: workerId
357
- });
358
-
359
- // 执行回放
360
- const results = await executor.execute(plan);
361
-
362
- // 完成任务
363
- task.completeReplay(
364
- results,
365
- executor.currentSandboxId
366
- );
367
-
368
- } catch (error) {
369
- task.failReplay(error);
370
-
371
- if (!this.continueOnError) {
372
- throw error;
373
- }
374
- }
375
- }
376
-
377
- /**
378
- * 获取任务专属的日志文件名
379
- * @param {number} taskIndex - 任务索引
380
- * @returns {string}
381
- */
382
- getTaskLogFileName(taskIndex) {
383
- const ext = path.extname(this.logFileBase);
384
- const base = path.basename(this.logFileBase, ext);
385
- const dir = path.dirname(this.logFileBase);
386
- return path.join(dir, `${base}-sandbox-${taskIndex}${ext}`);
387
- }
388
-
389
- /**
390
- * 启动进度监控
391
- * @param {SandboxTask[]} tasks - 任务列表
392
- */
393
- startProgressMonitor(tasks) {
394
- this.progressIntervalId = setInterval(() => {
395
- const running = tasks.filter(t => t.status === TaskStatus.RUNNING);
396
- const completed = tasks.filter(t => t.status === TaskStatus.COMPLETED || t.status === TaskStatus.FAILED);
397
-
398
- console.log('\n--- Progress Update ---');
399
- console.log(` Completed: ${completed.length}/${tasks.length}`);
400
- running.forEach(task => {
401
- console.log(` ⏳ ${task.name}... running`);
402
- });
403
- console.log('-'.repeat(23) + '\n');
404
- }, 30000); // 每 30 秒更新一次
405
- }
406
-
407
- /**
408
- * 停止进度监控
409
- */
410
- stopProgressMonitor() {
411
- if (this.progressIntervalId) {
412
- clearInterval(this.progressIntervalId);
413
- this.progressIntervalId = null;
414
- }
415
- }
416
-
417
- /**
418
- * 显示预演摘要(从输入文件,不获取历史)
419
- */
420
- displayDryRunSummaryFromInput() {
421
- console.log('\n' + '═'.repeat(70));
422
- console.log('🔍 DRY RUN - Preview Only (no history fetched, no requests executed)');
423
- console.log('═'.repeat(70));
424
-
425
- console.log(`\n📊 Configuration:`);
426
- console.log(` Total Sandboxes: ${this.tasks.length}`);
427
- console.log(` Concurrency: ${this.concurrency}`);
428
- console.log(` Mode: ${this.mode}`);
429
- console.log(` Timing: ${this.timing}`);
430
- console.log(` Auto Fetch History: ${this.autoFetchHistory}`);
431
-
432
- const preloadedCount = this.tasks.filter(t => t.hasRequests()).length;
433
- console.log(`\n📋 Sandboxes:`);
434
- console.log(` With Pre-loaded Requests: ${preloadedCount}`);
435
- console.log(` Need History Fetch: ${this.tasks.length - preloadedCount}`);
436
-
437
- console.log(`\n📝 Sandbox List:`);
438
- this.tasks.forEach((task, index) => {
439
- const hasRequests = task.hasRequests();
440
- const requestInfo = hasRequests ? `${task.requests.length} requests (pre-loaded)` : 'will fetch history';
441
- console.log(` ${index + 1}. ${task.name} (${task.sandboxId.substring(0, 8)}...) - ${requestInfo}`);
442
- });
443
-
444
- console.log('\n' + '═'.repeat(70));
445
- console.log('Dry run completed - no history fetched, no requests executed');
446
- console.log('═'.repeat(70) + '\n');
447
- }
448
-
449
- /**
450
- * 显示预演摘要(获取历史后)
451
- * @param {SandboxTask[]} validTasks - 有效任务
452
- */
453
- displayDryRunSummary(validTasks) {
454
- console.log('\n' + '═'.repeat(70));
455
- console.log('🔍 DRY RUN - Preview Only (no requests will be executed)');
456
- console.log('═'.repeat(70));
457
-
458
- console.log(`\n📊 Summary:`);
459
- console.log(` Total Sandboxes: ${this.tasks.length}`);
460
- console.log(` Valid for Replay: ${validTasks.length}`);
461
- console.log(` Failed History Fetch: ${this.tasks.filter(t => t.status === TaskStatus.FAILED).length}`);
462
-
463
- console.log(`\n📋 Sandboxes to Replay:`);
464
- validTasks.forEach((task, index) => {
465
- const requestCount = task.requests ? task.requests.length : 0;
466
- console.log(` ${index + 1}. ${task.name} (${task.sandboxId.substring(0, 8)}...) - ${requestCount} requests`);
467
- });
468
-
469
- console.log('\n' + '═'.repeat(70));
470
- console.log('Dry run completed - no requests were executed');
471
- console.log('═'.repeat(70) + '\n');
472
- }
473
-
474
- /**
475
- * 显示最终摘要
476
- */
477
- displayFinalSummary() {
478
- const summary = this.result.summary;
479
-
480
- console.log('\n' + '═'.repeat(70));
481
- console.log('📊 Batch Replay Summary');
482
- console.log('═'.repeat(70));
483
-
484
- console.log(`\nTotal Sandboxes: ${summary.totalSandboxes}`);
485
- console.log(`├── ✅ Successful: ${summary.successful}`);
486
- console.log(`├── ❌ Failed: ${summary.failed}`);
487
- console.log(`└── ⏭️ Skipped: ${summary.skipped}`);
488
-
489
- console.log(`\nDuration: ${summary.totalDuration}`);
490
-
491
- // 显示每个沙箱的结果
492
- if (this.result.sandboxResults.length > 0) {
493
- console.log('\nPer-Sandbox Results:');
494
- console.log('┌' + '─'.repeat(8) + '┬' + '─'.repeat(24) + '┬' + '─'.repeat(10) + '┬' + '─'.repeat(10) + '┬' + '─'.repeat(12) + '┐');
495
- console.log('│ Index │ Name │ Status │ Success │ Duration │');
496
- console.log('├' + '─'.repeat(8) + '┼' + '─'.repeat(24) + '┼' + '─'.repeat(10) + '┼' + '─'.repeat(10) + '┼' + '─'.repeat(12) + '┤');
497
-
498
- this.result.sandboxResults.forEach(result => {
499
- const status = result.status === TaskStatus.COMPLETED ? '✅' :
500
- result.status === TaskStatus.FAILED ? '❌' : '⏸️';
501
- const success = result.replay ? result.replay.success.toString() : '-';
502
- const duration = result.replay && result.replay.duration ?
503
- formatDuration(result.replay.duration) : '-';
504
- const name = result.name.length > 22 ? result.name.substring(0, 19) + '...' : result.name;
505
-
506
- console.log(`│ ${result.index.toString().padEnd(6)} │ ${name.padEnd(22)} │ ${status.padEnd(8)} │ ${success.padEnd(8)} │ ${duration.padEnd(10)} │`);
507
- });
508
-
509
- console.log('└' + '─'.repeat(8) + '┴' + '─'.repeat(24) + '┴' + '─'.repeat(10) + '┴' + '─'.repeat(10) + '┴' + '─'.repeat(12) + '┘');
510
- }
511
-
512
- // 显示失败的沙箱
513
- if (this.result.failedSandboxes.length > 0) {
514
- console.log('\nFailed Sandboxes:');
515
- this.result.failedSandboxes.forEach((failed, index) => {
516
- console.log(`├── [${failed.name}] ${failed.error} (phase: ${failed.phase})`);
517
- });
518
- }
519
-
520
- console.log(`\n📝 Report saved to: ${this.reportFile}`);
521
- console.log('═'.repeat(70) + '\n');
522
- }
523
-
524
- /**
525
- * 保存报告
526
- */
527
- async saveReport() {
528
- try {
529
- await fs.writeJson(this.reportFile, this.result.toJSON(), { spaces: 2 });
530
- } catch (error) {
531
- logger.warn(`Failed to save report to ${this.reportFile}: ${error.message}`);
532
- }
533
- }
534
- }
535
-
536
- module.exports = { BatchReplayOrchestrator };