rl-rockcli 0.0.2 → 0.0.4
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 +400 -0
- package/index.js +51 -21
- package/package.json +3 -2
- package/commands/log/core/constants.js +0 -237
- package/commands/log/core/display.js +0 -370
- package/commands/log/core/search.js +0 -330
- package/commands/log/core/tail.js +0 -216
- package/commands/log/core/utils.js +0 -424
- package/commands/log.js +0 -298
- package/commands/sandbox/core/log-bridge.js +0 -119
- package/commands/sandbox/core/replay/analyzer.js +0 -311
- package/commands/sandbox/core/replay/batch-orchestrator.js +0 -536
- package/commands/sandbox/core/replay/batch-task.js +0 -369
- package/commands/sandbox/core/replay/concurrent-display.js +0 -70
- package/commands/sandbox/core/replay/concurrent-orchestrator.js +0 -170
- package/commands/sandbox/core/replay/data-source.js +0 -86
- package/commands/sandbox/core/replay/display.js +0 -231
- package/commands/sandbox/core/replay/executor.js +0 -634
- package/commands/sandbox/core/replay/history-fetcher.js +0 -124
- package/commands/sandbox/core/replay/index.js +0 -338
- package/commands/sandbox/core/replay/loghouse-data-source.js +0 -177
- package/commands/sandbox/core/replay/pid-mapping.js +0 -26
- package/commands/sandbox/core/replay/request.js +0 -109
- package/commands/sandbox/core/replay/worker.js +0 -166
- package/commands/sandbox/core/session.js +0 -346
- package/commands/sandbox/log-bridge.js +0 -2
- package/commands/sandbox/ray.js +0 -2
- package/commands/sandbox/replay/analyzer.js +0 -311
- package/commands/sandbox/replay/batch-orchestrator.js +0 -536
- package/commands/sandbox/replay/batch-task.js +0 -369
- package/commands/sandbox/replay/concurrent-display.js +0 -70
- package/commands/sandbox/replay/concurrent-orchestrator.js +0 -170
- package/commands/sandbox/replay/display.js +0 -231
- package/commands/sandbox/replay/executor.js +0 -634
- package/commands/sandbox/replay/history-fetcher.js +0 -118
- package/commands/sandbox/replay/index.js +0 -338
- package/commands/sandbox/replay/pid-mapping.js +0 -26
- package/commands/sandbox/replay/request.js +0 -109
- package/commands/sandbox/replay/worker.js +0 -166
- package/commands/sandbox/replay.js +0 -2
- package/commands/sandbox/session.js +0 -2
- package/commands/sandbox-original.js +0 -1393
- package/commands/sandbox.js +0 -499
- package/help/help.json +0 -1071
- package/help/middleware.js +0 -71
- package/help/renderer.js +0 -800
- package/lib/plugin-context.js +0 -40
- package/sdks/sandbox/core/client.js +0 -845
- package/sdks/sandbox/core/config.js +0 -70
- package/sdks/sandbox/core/types.js +0 -74
- package/sdks/sandbox/httpLogger.js +0 -251
- package/sdks/sandbox/index.js +0 -9
- package/utils/asciiArt.js +0 -138
- package/utils/bun-compat.js +0 -59
- package/utils/ciPipelines.js +0 -138
- package/utils/cli.js +0 -17
- package/utils/command-router.js +0 -79
- package/utils/configManager.js +0 -503
- package/utils/dependency-resolver.js +0 -135
- package/utils/eagleeye_traceid.js +0 -151
- package/utils/envDetector.js +0 -78
- package/utils/execution_logger.js +0 -415
- package/utils/featureManager.js +0 -68
- package/utils/firstTimeTip.js +0 -44
- package/utils/hook-manager.js +0 -125
- package/utils/http-logger.js +0 -264
- package/utils/i18n.js +0 -139
- package/utils/image-progress.js +0 -159
- package/utils/logger.js +0 -154
- package/utils/plugin-loader.js +0 -124
- package/utils/plugin-manager.js +0 -348
- package/utils/ray_cli_wrapper.js +0 -746
- package/utils/sandbox-client.js +0 -419
- package/utils/terminal.js +0 -32
- package/utils/tips.js +0 -106
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
const { fetchHistoryData } = require('../../log/history');
|
|
2
|
-
const logger = require('../../../utils/logger');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 历史获取器
|
|
6
|
-
* 封装 log history 的调用逻辑,支持批量并发获取
|
|
7
|
-
*/
|
|
8
|
-
class HistoryFetcher {
|
|
9
|
-
/**
|
|
10
|
-
* 获取单个沙箱的历史
|
|
11
|
-
* @param {string} sandboxId - 沙箱 ID
|
|
12
|
-
* @param {Object} options - 选项
|
|
13
|
-
* @param {string} options.cluster - 集群名称
|
|
14
|
-
* @param {Array<string>} options.uris - URI 过滤器
|
|
15
|
-
* @param {boolean} options.debug - 是否开启调试
|
|
16
|
-
* @returns {Promise<Object>} - 历史数据(与 --dump 模式相同的格式)
|
|
17
|
-
*/
|
|
18
|
-
async fetch(sandboxId, options = {}) {
|
|
19
|
-
const { cluster, uris, debug } = options;
|
|
20
|
-
|
|
21
|
-
const result = await fetchHistoryData(sandboxId, {
|
|
22
|
-
cluster,
|
|
23
|
-
uris,
|
|
24
|
-
debug
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* 批量获取历史(并发控制)
|
|
32
|
-
* @param {SandboxTask[]} tasks - 任务列表
|
|
33
|
-
* @param {number} concurrency - 并发数
|
|
34
|
-
* @param {Object} options - 选项
|
|
35
|
-
* @param {boolean} options.quiet - 静默模式
|
|
36
|
-
* @param {Function} options.onProgress - 进度回调
|
|
37
|
-
* @returns {Promise<void>}
|
|
38
|
-
*/
|
|
39
|
-
async fetchBatch(tasks, concurrency, options = {}) {
|
|
40
|
-
const { quiet, onProgress } = options;
|
|
41
|
-
|
|
42
|
-
// 过滤出需要获取历史的任务(没有预加载 requests 的)
|
|
43
|
-
const tasksToFetch = tasks.filter(task => !task.hasRequests());
|
|
44
|
-
|
|
45
|
-
if (tasksToFetch.length === 0) {
|
|
46
|
-
if (!quiet) {
|
|
47
|
-
logger.info('All sandboxes have pre-loaded requests, skipping history fetch.');
|
|
48
|
-
}
|
|
49
|
-
// 标记所有任务为 ready
|
|
50
|
-
tasks.forEach(task => {
|
|
51
|
-
if (task.hasRequests()) {
|
|
52
|
-
task.status = 'ready';
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!quiet) {
|
|
59
|
-
logger.info(`Fetching history for ${tasksToFetch.length} sandboxes (concurrency: ${concurrency})...`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// 创建任务队列
|
|
63
|
-
const queue = [...tasksToFetch];
|
|
64
|
-
let completed = 0;
|
|
65
|
-
|
|
66
|
-
// Worker 函数
|
|
67
|
-
const worker = async () => {
|
|
68
|
-
while (queue.length > 0) {
|
|
69
|
-
const task = queue.shift();
|
|
70
|
-
if (!task) break;
|
|
71
|
-
|
|
72
|
-
task.startHistoryFetch();
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const historyData = await this.fetch(task.sandboxId, {
|
|
76
|
-
cluster: task.cluster,
|
|
77
|
-
uris: task.uris
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
task.completeHistoryFetch(historyData);
|
|
81
|
-
} catch (error) {
|
|
82
|
-
task.failHistoryFetch(error);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
completed++;
|
|
86
|
-
onProgress?.(completed, tasksToFetch.length, task);
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// 启动并发 workers
|
|
91
|
-
const workers = [];
|
|
92
|
-
const actualConcurrency = Math.min(concurrency, tasksToFetch.length);
|
|
93
|
-
|
|
94
|
-
for (let i = 0; i < actualConcurrency; i++) {
|
|
95
|
-
workers.push(worker());
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 等待所有 workers 完成
|
|
99
|
-
await Promise.allSettled(workers);
|
|
100
|
-
|
|
101
|
-
// 标记预加载的任务为 ready
|
|
102
|
-
tasks.forEach(task => {
|
|
103
|
-
if (task.hasRequests() && task.status === 'pending') {
|
|
104
|
-
task.status = 'ready';
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// 汇总结果
|
|
109
|
-
const succeeded = tasks.filter(t => t.status === 'ready').length;
|
|
110
|
-
const failed = tasks.filter(t => t.status === 'failed').length;
|
|
111
|
-
|
|
112
|
-
if (!quiet) {
|
|
113
|
-
logger.info(`History fetch completed: ${succeeded} succeeded, ${failed} failed.`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
module.exports = { HistoryFetcher };
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const logger = require('../../../utils/logger');
|
|
4
|
-
const { RequestAnalyzer } = require('./analyzer');
|
|
5
|
-
const { ReplayExecutor } = require('./executor');
|
|
6
|
-
const {
|
|
7
|
-
displayAnalysisSummary,
|
|
8
|
-
displayResultsSummary,
|
|
9
|
-
displayDryRunSummary
|
|
10
|
-
} = require('./display');
|
|
11
|
-
const { ConcurrentReplayOrchestrator } = require('./concurrent-orchestrator');
|
|
12
|
-
const { displayConcurrentResultsSummary } = require('./concurrent-display');
|
|
13
|
-
const { BatchReplayOrchestrator } = require('./batch-orchestrator');
|
|
14
|
-
const { gracefulExit } = require('../../../utils/execution_logger');
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 从 stdin 读取数据
|
|
18
|
-
* @returns {Promise<string>}
|
|
19
|
-
*/
|
|
20
|
-
function readStdin() {
|
|
21
|
-
return new Promise((resolve, reject) => {
|
|
22
|
-
let data = '';
|
|
23
|
-
process.stdin.setEncoding('utf8');
|
|
24
|
-
|
|
25
|
-
process.stdin.on('data', (chunk) => {
|
|
26
|
-
data += chunk;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
process.stdin.on('end', () => {
|
|
30
|
-
resolve(data);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
process.stdin.on('error', (error) => {
|
|
34
|
-
reject(new Error(`Error reading from stdin: ${error.message}`));
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 解析请求数据
|
|
41
|
-
* @param {string} inputData - JSON 字符串
|
|
42
|
-
* @returns {Object[]} 请求列表
|
|
43
|
-
*/
|
|
44
|
-
function parseRequests(inputData) {
|
|
45
|
-
let data;
|
|
46
|
-
try {
|
|
47
|
-
data = JSON.parse(inputData);
|
|
48
|
-
} catch (parseError) {
|
|
49
|
-
throw new Error(`Failed to parse JSON from stdin: ${parseError.message}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!data.requests || !Array.isArray(data.requests)) {
|
|
53
|
-
throw new Error('Invalid input format: "requests" array is required');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return data.requests;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 处理 replay 命令
|
|
61
|
-
* @param {Object} argv - 命令行参数
|
|
62
|
-
* @param {Object} client - SandboxClient 实例
|
|
63
|
-
*/
|
|
64
|
-
async function handleSmartReplay(argv, client) {
|
|
65
|
-
// 检查是否为批量模式
|
|
66
|
-
if (argv.fromFile) {
|
|
67
|
-
return handleBatchReplay(argv, client);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 检查是否为目录模式
|
|
71
|
-
if (argv.fromDir) {
|
|
72
|
-
return handleDirectoryReplay(argv, client);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 1. 读取并解析输入
|
|
76
|
-
const inputData = await readStdin();
|
|
77
|
-
const requests = parseRequests(inputData);
|
|
78
|
-
|
|
79
|
-
if (requests.length === 0) {
|
|
80
|
-
logger.info('No requests to replay');
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 2. 分析请求
|
|
85
|
-
const analyzer = new RequestAnalyzer({
|
|
86
|
-
mode: argv.mode || 'smart',
|
|
87
|
-
filters: argv.filter || [],
|
|
88
|
-
executeStop: argv.stop || false
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const plan = analyzer.analyze(requests);
|
|
92
|
-
|
|
93
|
-
// 3. 显示分析摘要
|
|
94
|
-
displayAnalysisSummary(plan, {
|
|
95
|
-
mode: argv.mode || 'smart',
|
|
96
|
-
quiet: argv.quiet || false
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// 4. 预演模式
|
|
100
|
-
if (argv.dryRun) {
|
|
101
|
-
displayDryRunSummary(plan);
|
|
102
|
-
logger.info('Dry run completed - no requests were executed');
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 5. 检查是否有请求需要执行
|
|
107
|
-
if (plan.executeCount === 0) {
|
|
108
|
-
logger.info('No requests to execute after filtering');
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 6. 检查并发模式
|
|
113
|
-
const concurrency = argv.concurrency || 1;
|
|
114
|
-
|
|
115
|
-
if (concurrency > 1) {
|
|
116
|
-
// === 并发回放路径 ===
|
|
117
|
-
const orchestrator = new ConcurrentReplayOrchestrator({
|
|
118
|
-
concurrency: concurrency,
|
|
119
|
-
config: client.config,
|
|
120
|
-
quiet: argv.quiet || false,
|
|
121
|
-
logFile: argv.logFile || 'replay.log',
|
|
122
|
-
failureStrategy: argv.failureStrategy || 'continue'
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const result = await orchestrator.execute(plan, {
|
|
126
|
-
timing: argv.timing || 'none',
|
|
127
|
-
interval: argv.interval || 5,
|
|
128
|
-
quiet: argv.quiet || false,
|
|
129
|
-
logFile: argv.logFile || 'replay.log'
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// 显示并发结果摘要
|
|
133
|
-
displayConcurrentResultsSummary(result, {
|
|
134
|
-
quiet: argv.quiet || false
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// 设置退出码
|
|
138
|
-
if (!result.allSucceeded || result.aggregated.failed > 0) {
|
|
139
|
-
process.exitCode = 1;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
} else {
|
|
143
|
-
// === 原有单沙箱回放路径 ===
|
|
144
|
-
const executor = new ReplayExecutor(client, {
|
|
145
|
-
timing: argv.timing || 'none',
|
|
146
|
-
interval: argv.interval || 5,
|
|
147
|
-
quiet: argv.quiet || false,
|
|
148
|
-
logFile: argv.logFile || 'replay.log',
|
|
149
|
-
verboseLog: argv.verboseLog || false,
|
|
150
|
-
plan: plan
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const results = await executor.execute(plan);
|
|
154
|
-
|
|
155
|
-
// 显示结果摘要
|
|
156
|
-
displayResultsSummary(results, [], {
|
|
157
|
-
quiet: argv.quiet || false,
|
|
158
|
-
logFile: argv.logFile || 'replay.log',
|
|
159
|
-
sandboxId: executor.currentSandboxId
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// 如果有失败,设置退出码
|
|
163
|
-
if (results.failed > 0) {
|
|
164
|
-
process.exitCode = 1;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* 处理批量回放命令
|
|
171
|
-
* @param {Object} argv - 命令行参数
|
|
172
|
-
* @param {Object} client - SandboxClient 实例
|
|
173
|
-
*/
|
|
174
|
-
async function handleBatchReplay(argv, client) {
|
|
175
|
-
const orchestrator = new BatchReplayOrchestrator({
|
|
176
|
-
concurrency: argv.concurrency || 1,
|
|
177
|
-
baseConfig: client.config,
|
|
178
|
-
mode: argv.mode || 'smart',
|
|
179
|
-
timing: argv.timing || 'none',
|
|
180
|
-
interval: argv.interval || 5,
|
|
181
|
-
filters: argv.filter || [],
|
|
182
|
-
autoFetchHistory: argv.autoFetchHistory !== false,
|
|
183
|
-
continueOnError: argv.continueOnSandboxError !== false,
|
|
184
|
-
quiet: argv.quiet || false,
|
|
185
|
-
dryRun: argv.dryRun || false,
|
|
186
|
-
executeStop: argv.stop || false,
|
|
187
|
-
reportFile: argv.reportFile || 'batch-replay-report.json',
|
|
188
|
-
logFileBase: argv.logFile || 'replay.log',
|
|
189
|
-
verboseLog: argv.verboseLog || false
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const result = await orchestrator.execute(argv.fromFile);
|
|
193
|
-
|
|
194
|
-
// 设置退出码
|
|
195
|
-
if (result.summary.failed > 0) {
|
|
196
|
-
process.exitCode = 1;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return result;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* 处理目录模式批量回放
|
|
204
|
-
* @param {Object} argv - 命令行参数
|
|
205
|
-
* @param {Object} client - SandboxClient 实例
|
|
206
|
-
*/
|
|
207
|
-
async function handleDirectoryReplay(argv, client) {
|
|
208
|
-
const sourceDir = argv.fromDir;
|
|
209
|
-
|
|
210
|
-
// 1. 验证目录存在
|
|
211
|
-
if (!fs.existsSync(sourceDir)) {
|
|
212
|
-
logger.error(`Directory not found: ${sourceDir}`);
|
|
213
|
-
gracefulExit(1);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// 2. 读取 manifest.json(如果存在)
|
|
217
|
-
const manifestPath = path.join(sourceDir, 'manifest.json');
|
|
218
|
-
let manifest = null;
|
|
219
|
-
let historyFiles = [];
|
|
220
|
-
|
|
221
|
-
if (fs.existsSync(manifestPath)) {
|
|
222
|
-
try {
|
|
223
|
-
manifest = fs.readJsonSync(manifestPath);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
logger.warn(`Failed to read manifest.json: ${error.message}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// 3. 扫描目录中的 JSON 文件
|
|
230
|
-
const files = fs.readdirSync(sourceDir);
|
|
231
|
-
historyFiles = files.filter(f =>
|
|
232
|
-
f.endsWith('.json') &&
|
|
233
|
-
f !== 'manifest.json' &&
|
|
234
|
-
!f.startsWith('.')
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
if (historyFiles.length === 0) {
|
|
238
|
-
logger.error(`No history files found in ${sourceDir}`);
|
|
239
|
-
gracefulExit(1);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// 4. 构建输入数据(模拟 --from-file 格式)
|
|
243
|
-
const sandboxes = [];
|
|
244
|
-
const loadErrors = [];
|
|
245
|
-
|
|
246
|
-
for (const file of historyFiles) {
|
|
247
|
-
const filePath = path.join(sourceDir, file);
|
|
248
|
-
try {
|
|
249
|
-
const data = fs.readJsonSync(filePath);
|
|
250
|
-
|
|
251
|
-
if (!data.sandboxId) {
|
|
252
|
-
loadErrors.push({ file, error: 'Missing sandboxId' });
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (!data.requests || !Array.isArray(data.requests)) {
|
|
257
|
-
loadErrors.push({ file, error: 'Missing or invalid requests array' });
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
sandboxes.push(data);
|
|
262
|
-
} catch (error) {
|
|
263
|
-
loadErrors.push({ file, error: error.message });
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (!argv.quiet) {
|
|
268
|
-
console.log('\n' + '═'.repeat(70));
|
|
269
|
-
console.log('📂 Directory Replay Mode');
|
|
270
|
-
console.log('═'.repeat(70));
|
|
271
|
-
console.log(` Source Dir: ${sourceDir}`);
|
|
272
|
-
console.log(` Files Found: ${historyFiles.length}`);
|
|
273
|
-
console.log(` Valid: ${sandboxes.length}`);
|
|
274
|
-
if (loadErrors.length > 0) {
|
|
275
|
-
console.log(` Errors: ${loadErrors.length}`);
|
|
276
|
-
loadErrors.slice(0, 5).forEach(e => {
|
|
277
|
-
console.log(` - ${e.file}: ${e.error}`);
|
|
278
|
-
});
|
|
279
|
-
if (loadErrors.length > 5) {
|
|
280
|
-
console.log(` ... and ${loadErrors.length - 5} more`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
console.log('═'.repeat(70) + '\n');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (sandboxes.length === 0) {
|
|
287
|
-
logger.error('No valid sandbox histories found');
|
|
288
|
-
gracefulExit(1);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// 5. 构建虚拟输入文件并调用批量回放
|
|
292
|
-
const inputData = {
|
|
293
|
-
metadata: {
|
|
294
|
-
...(manifest?.metadata || {}),
|
|
295
|
-
sourceDir: sourceDir,
|
|
296
|
-
loadedAt: new Date().toISOString()
|
|
297
|
-
},
|
|
298
|
-
defaults: manifest?.defaults || {},
|
|
299
|
-
sandboxes: sandboxes
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
// 创建临时文件
|
|
303
|
-
const tempFile = path.join(sourceDir, '.replay-input-temp.json');
|
|
304
|
-
try {
|
|
305
|
-
await fs.writeJson(tempFile, inputData, { spaces: 2 });
|
|
306
|
-
|
|
307
|
-
// 使用批量回放处理(强制禁用自动获取历史)
|
|
308
|
-
const modifiedArgv = {
|
|
309
|
-
...argv,
|
|
310
|
-
fromFile: tempFile,
|
|
311
|
-
autoFetchHistory: false // 已经有预加载的 requests
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const result = await handleBatchReplay(modifiedArgv, client);
|
|
315
|
-
|
|
316
|
-
// 清理临时文件
|
|
317
|
-
await fs.remove(tempFile);
|
|
318
|
-
|
|
319
|
-
return result;
|
|
320
|
-
} catch (error) {
|
|
321
|
-
// 确保清理临时文件
|
|
322
|
-
if (fs.existsSync(tempFile)) {
|
|
323
|
-
await fs.remove(tempFile);
|
|
324
|
-
}
|
|
325
|
-
throw error;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
module.exports = {
|
|
330
|
-
handleSmartReplay,
|
|
331
|
-
handleBatchReplay,
|
|
332
|
-
handleDirectoryReplay,
|
|
333
|
-
readStdin,
|
|
334
|
-
parseRequests,
|
|
335
|
-
RequestAnalyzer,
|
|
336
|
-
ReplayExecutor,
|
|
337
|
-
BatchReplayOrchestrator
|
|
338
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 从 nohup 输出中提取 PID
|
|
3
|
-
* SDK 使用 PIDSTART{pid}PIDEND 格式
|
|
4
|
-
* @param {string} output - 命令输出
|
|
5
|
-
* @returns {string|null} 提取的 PID,如果未找到则返回 null
|
|
6
|
-
*/
|
|
7
|
-
function extractPidFromOutput(output) {
|
|
8
|
-
if (!output) return null;
|
|
9
|
-
|
|
10
|
-
const match = output.match(/PIDSTART(\d+)PIDEND/);
|
|
11
|
-
if (match) {
|
|
12
|
-
return match[1];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// 备用方案:尝试匹配 [1] 12345 格式
|
|
16
|
-
const bgMatch = output.match(/\[\d+\]\s+(\d+)/);
|
|
17
|
-
if (bgMatch) {
|
|
18
|
-
return bgMatch[1];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
module.exports = {
|
|
25
|
-
extractPidFromOutput
|
|
26
|
-
};
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 请求分类常量
|
|
3
|
-
*/
|
|
4
|
-
const RequestCategory = {
|
|
5
|
-
STARTUP: 'startup', // start_async
|
|
6
|
-
STARTUP_POLLING: 'startup_polling', // get_status after start
|
|
7
|
-
SESSION: 'session', // create_session, close_session
|
|
8
|
-
NOHUP_COMMAND: 'nohup_command', // nohup xxx &
|
|
9
|
-
PROCESS_CHECK: 'process_check', // kill -0 {pid}
|
|
10
|
-
LOG_QUERY: 'log_query', // head -c xxx file
|
|
11
|
-
UPLOAD: 'upload', // upload file
|
|
12
|
-
STOP: 'stop', // stop sandbox
|
|
13
|
-
OTHER: 'other' // other commands
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 处理动作常量
|
|
18
|
-
*/
|
|
19
|
-
const RequestAction = {
|
|
20
|
-
EXECUTE: 'execute', // 执行
|
|
21
|
-
SKIP: 'skip', // 跳过
|
|
22
|
-
MERGE: 'merge', // 合并到等待逻辑
|
|
23
|
-
EXECUTE_WITH_PROCESS_WAIT: 'execute_with_process_wait' // 执行 nohup 并等待进程结束
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 单个回放请求
|
|
28
|
-
*/
|
|
29
|
-
class ReplayRequest {
|
|
30
|
-
constructor(raw, index) {
|
|
31
|
-
this.index = index;
|
|
32
|
-
this.method = raw.method;
|
|
33
|
-
this.uri = raw.uri;
|
|
34
|
-
this.requestBody = raw.requestBody || {};
|
|
35
|
-
this.headers = raw.headers || {};
|
|
36
|
-
this.responseTime = raw.responseTime ? new Date(raw.responseTime) : null;
|
|
37
|
-
|
|
38
|
-
// 分析后填充的字段
|
|
39
|
-
this.category = null; // 请求分类
|
|
40
|
-
this.action = null; // 处理动作
|
|
41
|
-
this.skipReason = null; // 跳过原因
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 获取命令(如果是 run_in_session 请求)
|
|
46
|
-
*/
|
|
47
|
-
getCommand() {
|
|
48
|
-
return this.requestBody?.command || '';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 获取简短描述
|
|
53
|
-
*/
|
|
54
|
-
getShortDescription(maxLen = 60) {
|
|
55
|
-
const command = this.getCommand();
|
|
56
|
-
if (command) {
|
|
57
|
-
if (command.length > maxLen) {
|
|
58
|
-
return command.substring(0, maxLen - 3) + '...';
|
|
59
|
-
}
|
|
60
|
-
return command;
|
|
61
|
-
}
|
|
62
|
-
return this.uri.split('?')[0].split('/').pop();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* 回放计划
|
|
68
|
-
*/
|
|
69
|
-
class ReplayPlan {
|
|
70
|
-
constructor() {
|
|
71
|
-
this.originalCount = 0; // 原始请求数
|
|
72
|
-
this.requests = []; // 待执行的请求列表
|
|
73
|
-
this.skippedRequests = []; // 被跳过的请求列表
|
|
74
|
-
this.mergedRequests = []; // 被合并的请求列表
|
|
75
|
-
|
|
76
|
-
// 统计信息
|
|
77
|
-
this.stats = {
|
|
78
|
-
startup: 0,
|
|
79
|
-
startupPolling: 0,
|
|
80
|
-
session: 0,
|
|
81
|
-
nohupCommand: 0,
|
|
82
|
-
processCheck: 0,
|
|
83
|
-
logQuery: 0,
|
|
84
|
-
upload: 0,
|
|
85
|
-
stop: 0,
|
|
86
|
-
other: 0
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
get skippedCount() {
|
|
92
|
-
return this.skippedRequests.length;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
get mergedCount() {
|
|
96
|
-
return this.mergedRequests.length;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
get executeCount() {
|
|
100
|
-
return this.requests.length;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
module.exports = {
|
|
105
|
-
RequestCategory,
|
|
106
|
-
RequestAction,
|
|
107
|
-
ReplayRequest,
|
|
108
|
-
ReplayPlan
|
|
109
|
-
};
|