rl-rockcli 0.0.7 → 0.0.8

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 (74) hide show
  1. package/index.js +15 -5
  2. package/package.json +2 -2
  3. package/commands/log/core/constants.js +0 -237
  4. package/commands/log/core/display.js +0 -370
  5. package/commands/log/core/search.js +0 -330
  6. package/commands/log/core/tail.js +0 -216
  7. package/commands/log/core/utils.js +0 -424
  8. package/commands/log.js +0 -298
  9. package/commands/sandbox/core/log-bridge.js +0 -119
  10. package/commands/sandbox/core/replay/analyzer.js +0 -311
  11. package/commands/sandbox/core/replay/batch-orchestrator.js +0 -536
  12. package/commands/sandbox/core/replay/batch-task.js +0 -369
  13. package/commands/sandbox/core/replay/concurrent-display.js +0 -70
  14. package/commands/sandbox/core/replay/concurrent-orchestrator.js +0 -170
  15. package/commands/sandbox/core/replay/data-source.js +0 -86
  16. package/commands/sandbox/core/replay/display.js +0 -231
  17. package/commands/sandbox/core/replay/executor.js +0 -634
  18. package/commands/sandbox/core/replay/history-fetcher.js +0 -124
  19. package/commands/sandbox/core/replay/index.js +0 -338
  20. package/commands/sandbox/core/replay/loghouse-data-source.js +0 -177
  21. package/commands/sandbox/core/replay/pid-mapping.js +0 -26
  22. package/commands/sandbox/core/replay/request.js +0 -109
  23. package/commands/sandbox/core/replay/worker.js +0 -166
  24. package/commands/sandbox/core/session.js +0 -346
  25. package/commands/sandbox/log-bridge.js +0 -2
  26. package/commands/sandbox/ray.js +0 -2
  27. package/commands/sandbox/replay/analyzer.js +0 -311
  28. package/commands/sandbox/replay/batch-orchestrator.js +0 -536
  29. package/commands/sandbox/replay/batch-task.js +0 -369
  30. package/commands/sandbox/replay/concurrent-display.js +0 -70
  31. package/commands/sandbox/replay/concurrent-orchestrator.js +0 -170
  32. package/commands/sandbox/replay/display.js +0 -231
  33. package/commands/sandbox/replay/executor.js +0 -634
  34. package/commands/sandbox/replay/history-fetcher.js +0 -118
  35. package/commands/sandbox/replay/index.js +0 -338
  36. package/commands/sandbox/replay/pid-mapping.js +0 -26
  37. package/commands/sandbox/replay/request.js +0 -109
  38. package/commands/sandbox/replay/worker.js +0 -166
  39. package/commands/sandbox/replay.js +0 -2
  40. package/commands/sandbox/session.js +0 -2
  41. package/commands/sandbox-original.js +0 -1393
  42. package/commands/sandbox.js +0 -499
  43. package/help/help.json +0 -1071
  44. package/help/middleware.js +0 -71
  45. package/help/renderer.js +0 -800
  46. package/lib/plugin-context.js +0 -40
  47. package/sdks/sandbox/core/client.js +0 -845
  48. package/sdks/sandbox/core/config.js +0 -70
  49. package/sdks/sandbox/core/types.js +0 -74
  50. package/sdks/sandbox/httpLogger.js +0 -251
  51. package/sdks/sandbox/index.js +0 -9
  52. package/utils/asciiArt.js +0 -138
  53. package/utils/bun-compat.js +0 -59
  54. package/utils/ciPipelines.js +0 -138
  55. package/utils/cli.js +0 -17
  56. package/utils/command-router.js +0 -79
  57. package/utils/configManager.js +0 -503
  58. package/utils/dependency-resolver.js +0 -135
  59. package/utils/eagleeye_traceid.js +0 -151
  60. package/utils/envDetector.js +0 -78
  61. package/utils/execution_logger.js +0 -415
  62. package/utils/featureManager.js +0 -68
  63. package/utils/firstTimeTip.js +0 -44
  64. package/utils/hook-manager.js +0 -125
  65. package/utils/http-logger.js +0 -264
  66. package/utils/i18n.js +0 -139
  67. package/utils/image-progress.js +0 -159
  68. package/utils/logger.js +0 -154
  69. package/utils/plugin-loader.js +0 -124
  70. package/utils/plugin-manager.js +0 -348
  71. package/utils/ray_cli_wrapper.js +0 -746
  72. package/utils/sandbox-client.js +0 -419
  73. package/utils/terminal.js +0 -32
  74. package/utils/tips.js +0 -106
@@ -1,746 +0,0 @@
1
- const { spawn } = require('child_process');
2
- const fs = require('fs-extra');
3
- const path = require('path');
4
- const https = require('https');
5
- const http = require('http');
6
- const logger = require('./logger');
7
-
8
- /**
9
- * Ray CLI 封装类
10
- * 负责调用系统中的 ray-cli 并进行参数转换
11
- */
12
- class RayCliWrapper {
13
- constructor(options = {}) {
14
- this.verbose = options.verbose || 0;
15
- this.configPath = path.join(__dirname, '../config/ray_cluster_mapping.json');
16
- this.clusterConfig = null;
17
- }
18
-
19
- /**
20
- * 检查 ray-cli 是否已安装
21
- * @returns {Promise<boolean>}
22
- */
23
- async checkRayCliInstalled() {
24
- // 如果设置了 ROCK_CLI_MOCK_RAY 环境变量,跳过检查(用于测试)
25
- if (process.env.ROCK_CLI_MOCK_RAY === '1') {
26
- return Promise.resolve(true);
27
- }
28
-
29
- return new Promise((resolve, reject) => {
30
- const checkProcess = spawn('which', ['ray'], {
31
- shell: true
32
- });
33
-
34
- checkProcess.on('close', (code) => {
35
- if (code === 0) {
36
- resolve(true);
37
- } else {
38
- reject(new Error(
39
- 'ray-cli not found. Please install it: pip install -U "ray[default]"'
40
- ));
41
- }
42
- });
43
-
44
- checkProcess.on('error', () => {
45
- reject(new Error(
46
- 'ray-cli not found. Please install it: pip install -U "ray[default]"'
47
- ));
48
- });
49
- });
50
- }
51
-
52
- /**
53
- * 加载集群配置
54
- * @returns {Object} 集群配置对象
55
- */
56
- loadClusterConfig() {
57
- try {
58
- if (!fs.existsSync(this.configPath)) {
59
- throw new Error(
60
- `Config file not found: ${this.configPath}`
61
- );
62
- }
63
-
64
- const content = fs.readFileSync(this.configPath, 'utf8');
65
- this.clusterConfig = JSON.parse(content);
66
-
67
- if (!this.clusterConfig.clusters) {
68
- throw new Error('Invalid config: missing "clusters" field');
69
- }
70
-
71
- return this.clusterConfig;
72
-
73
- } catch (error) {
74
- throw new Error(`Failed to load cluster config: ${error.message}`);
75
- }
76
- }
77
-
78
- /**
79
- * 获取集群地址
80
- * @param {string} clusterName - 集群名称
81
- * @returns {string} Ray Dashboard 地址
82
- */
83
- getClusterAddress(clusterName) {
84
- if (!this.clusterConfig) {
85
- this.loadClusterConfig();
86
- }
87
-
88
- const cluster = this.clusterConfig.clusters[clusterName];
89
- if (!cluster) {
90
- const available = Object.keys(this.clusterConfig.clusters).join(', ');
91
- throw new Error(
92
- `Cluster '${clusterName}' not found in config. Available: ${available}`
93
- );
94
- }
95
-
96
- return cluster.address;
97
- }
98
-
99
- /**
100
- * 处理自定义参数并构建 ray-cli 参数
101
- * @param {Object} argv - yargs 参数对象
102
- * @returns {Array<string>} 命令参数数组
103
- */
104
- buildRayArgs(argv) {
105
- const args = [];
106
-
107
- // 1. 收集位置参数(action...)
108
- if (argv.action && Array.isArray(argv.action)) {
109
- // yargs 收集的多个位置参数
110
- args.push(...argv.action);
111
- } else if (argv._ && argv._.length > 1) {
112
- // 从 argv._ 中提取(兼容)
113
- args.push(...argv._.slice(1));
114
- }
115
-
116
- // 2. 处理 --cluster (优先级高)
117
- if (argv.cluster) {
118
- const address = this.getClusterAddress(argv.cluster);
119
- args.push('--address', address);
120
- } else if (argv.address) {
121
- args.push('--address', argv.address);
122
- }
123
-
124
- // 3. 处理 --sandbox-id (优先级高)
125
- if (argv.sandboxId) {
126
- args.push('--filter', `name=sandbox-${argv.sandboxId}`);
127
- } else if (argv.filter) {
128
- args.push('--filter', argv.filter);
129
- }
130
-
131
- // 4. 透传其他参数(仅透传 ray-cli 支持的参数)
132
- // 不透传 rock-cli 全局参数
133
- const skipParams = [
134
- 'cluster', 'sandbox-id', 'sandboxId', 'address', 'filter',
135
- 'verbose', 'v', '_', '$0',
136
- 'base-url', 'baseUrl', 'api-key', 'apiKey',
137
- 'help', 'h', 'version', 'action'
138
- ];
139
-
140
- for (const [key, value] of Object.entries(argv)) {
141
- if (skipParams.includes(key)) continue;
142
- if (value === undefined || value === null) continue;
143
-
144
- const argName = key.length === 1 ? `-${key}` : `--${key}`;
145
-
146
- if (typeof value === 'boolean') {
147
- if (value) args.push(argName);
148
- } else if (Array.isArray(value)) {
149
- value.forEach(v => args.push(argName, String(v)));
150
- } else {
151
- args.push(argName, String(value));
152
- }
153
- }
154
-
155
- return args;
156
- }
157
-
158
- /**
159
- * 执行自定义命令(通过 HTTP API)
160
- * @param {string} command - 命令名称
161
- * @param {Object} argv - yargs 参数对象
162
- * @returns {Promise<Object>} 执行结果
163
- */
164
- async executeCustomCommand(command, argv) {
165
- // get 命令有自己的参数验证逻辑
166
- if (command === 'get') {
167
- return this.executeGetCommand(argv);
168
- }
169
-
170
- // 其他命令需要验证必需参数
171
- if (!argv.cluster && !argv.address) {
172
- throw new Error('Either --cluster or --address is required');
173
- }
174
-
175
- // 获取集群地址
176
- let baseUrl;
177
- if (argv.cluster) {
178
- baseUrl = this.getClusterAddress(argv.cluster);
179
- } else {
180
- baseUrl = argv.address;
181
- }
182
-
183
- if (command === 'status') {
184
- return this.fetchClusterStatus(baseUrl, argv.format);
185
- }
186
-
187
- throw new Error(`Unknown custom command: ${command}`);
188
- }
189
-
190
- /**
191
- * 处理 get 命令
192
- * @param {Object} argv - 包含 action: ['get', 'sandbox', '<id>']
193
- * @returns {Promise<Object>} 执行结果
194
- */
195
- async executeGetCommand(argv) {
196
- const resource = argv.action && argv.action[1];
197
- const id = argv.action && argv.action[2];
198
-
199
- if (resource === 'sandbox') {
200
- return this.executeGetSandbox(id, argv);
201
- }
202
-
203
- return {
204
- success: false,
205
- exitCode: 1,
206
- stdout: '',
207
- stderr: `Error: Unknown resource type: ${resource || '(none)'}\nSupported: sandbox\nUsage: rock-cli ray get sandbox <sandbox-id> --cluster=<cluster>`
208
- };
209
- }
210
-
211
- /**
212
- * 获取 sandbox 信息
213
- * @param {string} sandboxId - Sandbox ID
214
- * @param {Object} argv - yargs 参数对象
215
- * @returns {Promise<Object>} 执行结果
216
- */
217
- async executeGetSandbox(sandboxId, argv) {
218
- // 1. 验证必需参数
219
- if (!sandboxId) {
220
- return {
221
- success: false,
222
- exitCode: 1,
223
- stdout: '',
224
- stderr: 'Error: Missing required argument: sandbox-id\nUsage: rock-cli ray get sandbox <sandbox-id> --cluster=<cluster>'
225
- };
226
- }
227
-
228
- if (!argv.cluster && !argv.address) {
229
- return {
230
- success: false,
231
- exitCode: 1,
232
- stdout: '',
233
- stderr: 'Error: Missing required option: --cluster\nUsage: rock-cli ray get sandbox <sandbox-id> --cluster=<cluster>'
234
- };
235
- }
236
-
237
- const clusterName = argv.cluster || argv.address;
238
-
239
- // 2. 查询 actor 信息
240
- if (this.verbose >= 2) {
241
- logger.debug(`Querying actor for sandbox: ${sandboxId}`);
242
- }
243
-
244
- const actorResult = await this.execute({
245
- action: ['list', 'actors'],
246
- cluster: argv.cluster,
247
- address: argv.address,
248
- format: 'json',
249
- sandboxId: sandboxId
250
- });
251
-
252
- if (!actorResult.success) {
253
- return {
254
- success: false,
255
- exitCode: 1,
256
- stdout: '',
257
- stderr: `Error: Failed to query actor: ${actorResult.stderr}`
258
- };
259
- }
260
-
261
- // 3. 解析 actor 响应
262
- let actors;
263
- try {
264
- actors = JSON.parse(actorResult.stdout);
265
- } catch (e) {
266
- // ray-cli 返回 "No resource in the cluster" 等非 JSON 格式表示资源不存在
267
- if (actorResult.stdout.includes('No resource')) {
268
- return {
269
- success: false,
270
- exitCode: 1,
271
- stdout: '',
272
- stderr: `Error: Sandbox '${sandboxId}' not found in cluster '${clusterName}'`
273
- };
274
- }
275
- return {
276
- success: false,
277
- exitCode: 1,
278
- stdout: '',
279
- stderr: `Error: Failed to parse actor response: ${e.message}`
280
- };
281
- }
282
-
283
- if (!actors || actors.length === 0) {
284
- return {
285
- success: false,
286
- exitCode: 1,
287
- stdout: '',
288
- stderr: `Error: Sandbox '${sandboxId}' not found in cluster '${clusterName}'`
289
- };
290
- }
291
-
292
- const actor = actors[0];
293
- const nodeId = actor.node_id;
294
-
295
- if (!nodeId) {
296
- return {
297
- success: false,
298
- exitCode: 1,
299
- stdout: '',
300
- stderr: `Error: Actor found but node_id is missing for sandbox '${sandboxId}'`
301
- };
302
- }
303
-
304
- // 4. 查询 node 信息(使用 filter 精确查询)
305
- if (this.verbose >= 2) {
306
- logger.debug(`Querying node: ${nodeId}`);
307
- }
308
-
309
- const nodeResult = await this.executeWithFilter({
310
- action: ['list', 'nodes'],
311
- cluster: argv.cluster,
312
- address: argv.address,
313
- format: 'json',
314
- filter: `node_id=${nodeId}`
315
- });
316
-
317
- if (!nodeResult.success) {
318
- return {
319
- success: false,
320
- exitCode: 1,
321
- stdout: '',
322
- stderr: `Error: Failed to query node: ${nodeResult.stderr}`
323
- };
324
- }
325
-
326
- // 5. 解析 node 响应
327
- let nodes;
328
- try {
329
- nodes = JSON.parse(nodeResult.stdout);
330
- } catch (e) {
331
- return {
332
- success: false,
333
- exitCode: 1,
334
- stdout: '',
335
- stderr: `Error: Failed to parse node response: ${e.message}`
336
- };
337
- }
338
-
339
- if (!nodes || nodes.length === 0) {
340
- return {
341
- success: false,
342
- exitCode: 1,
343
- stdout: '',
344
- stderr: `Error: Node '${nodeId}' not found for sandbox '${sandboxId}'`
345
- };
346
- }
347
-
348
- const node = nodes[0];
349
-
350
- // 6. 格式化输出
351
- const outputFormat = argv.format || 'table';
352
- if (outputFormat === 'json') {
353
- return this.formatSandboxJsonOutput(sandboxId, clusterName, actor, node);
354
- } else if (argv.detail) {
355
- return this.formatSandboxDetailOutput(sandboxId, clusterName, actor, node);
356
- } else {
357
- return this.formatSandboxTableOutput(sandboxId, clusterName, actor, node);
358
- }
359
- }
360
-
361
- /**
362
- * 执行带有自定义 filter 的 ray-cli 命令
363
- * 与 execute() 不同,此方法直接使用传入的 filter,不做 sandboxId 转换
364
- * @param {Object} argv - 参数对象
365
- * @returns {Promise<Object>} 执行结果
366
- */
367
- async executeWithFilter(argv) {
368
- // 验证必需参数
369
- if (!argv.cluster && !argv.address) {
370
- throw new Error('Either --cluster or --address is required');
371
- }
372
-
373
- // 1. 检查 ray-cli
374
- await this.checkRayCliInstalled();
375
-
376
- // 2. 构建命令参数
377
- const args = [];
378
-
379
- // 添加 action
380
- if (argv.action && Array.isArray(argv.action)) {
381
- args.push(...argv.action);
382
- }
383
-
384
- // 添加 address
385
- if (argv.cluster) {
386
- const address = this.getClusterAddress(argv.cluster);
387
- args.push('--address', address);
388
- } else if (argv.address) {
389
- args.push('--address', argv.address);
390
- }
391
-
392
- // 添加 filter(直接使用)
393
- if (argv.filter) {
394
- args.push('--filter', argv.filter);
395
- }
396
-
397
- // 添加 format
398
- if (argv.format) {
399
- args.push('--format', argv.format);
400
- }
401
-
402
- // 3. 输出调试信息
403
- if (this.verbose >= 3) {
404
- logger.debug(`Executing: ray ${args.join(' ')}`);
405
- }
406
-
407
- // 4. 执行命令
408
- return new Promise((resolve, reject) => {
409
- let stdout = '';
410
- let stderr = '';
411
-
412
- const rayProcess = spawn('ray', args, {
413
- shell: false,
414
- env: process.env
415
- });
416
-
417
- rayProcess.stdout.on('data', (data) => {
418
- stdout += data.toString();
419
- });
420
-
421
- rayProcess.stderr.on('data', (data) => {
422
- stderr += data.toString();
423
- });
424
-
425
- rayProcess.on('close', (code) => {
426
- resolve({
427
- success: code === 0,
428
- exitCode: code,
429
- stdout: stdout.trim(),
430
- stderr: stderr.trim()
431
- });
432
- });
433
-
434
- rayProcess.on('error', (error) => {
435
- reject(new Error(`Failed to execute ray-cli: ${error.message}`));
436
- });
437
- });
438
- }
439
-
440
- /**
441
- * 格式化 JSON 输出(完整信息)
442
- */
443
- formatSandboxJsonOutput(sandboxId, cluster, actor, node) {
444
- // 过滤掉 null 值
445
- const filterNull = (obj) => {
446
- return Object.fromEntries(
447
- Object.entries(obj).filter(([_, v]) => v !== null)
448
- );
449
- };
450
-
451
- const output = {
452
- sandbox_id: sandboxId,
453
- cluster: cluster,
454
- actor: filterNull(actor),
455
- node: filterNull(node)
456
- };
457
-
458
- return {
459
- success: true,
460
- exitCode: 0,
461
- stdout: JSON.stringify(output, null, 2),
462
- stderr: ''
463
- };
464
- }
465
-
466
- /**
467
- * 格式化精简表格输出
468
- */
469
- formatSandboxTableOutput(sandboxId, cluster, actor, node) {
470
- const lines = [
471
- `Sandbox: ${sandboxId}`,
472
- `Cluster: ${cluster}`,
473
- '',
474
- 'sandbox_id actor_state node_ip node_state',
475
- '-------------------------------- ----------- ------------ ----------',
476
- `${sandboxId.substring(0, 32).padEnd(32)} ${(actor.state || 'N/A').padEnd(11)} ${(node.node_ip || 'N/A').padEnd(12)} ${node.state || 'N/A'}`
477
- ];
478
-
479
- return {
480
- success: true,
481
- exitCode: 0,
482
- stdout: lines.join('\n'),
483
- stderr: ''
484
- };
485
- }
486
-
487
- /**
488
- * 格式化详细输出(--detail)
489
- */
490
- formatSandboxDetailOutput(sandboxId, cluster, actor, node) {
491
- // 格式化资源信息
492
- const formatResources = (resources) => {
493
- if (!resources) return 'N/A';
494
- const parts = [];
495
- if (resources.CPU) parts.push(`CPU: ${resources.CPU}`);
496
- if (resources.memory) parts.push(`memory: ${resources.memory}`);
497
- if (resources.object_store_memory) parts.push(`object_store: ${resources.object_store_memory}`);
498
- if (resources['xrl-sandbox']) parts.push(`xrl-sandbox: ${resources['xrl-sandbox']}`);
499
- return parts.join(', ') || 'N/A';
500
- };
501
-
502
- // 格式化标签信息
503
- const formatLabels = (labels) => {
504
- if (!labels) return 'N/A';
505
- const entries = Object.entries(labels);
506
- if (entries.length === 0) return 'N/A';
507
- return entries.map(([k, v]) => `${k}=${v}`).join(', ');
508
- };
509
-
510
- // 格式化资源请求
511
- const formatRequiredResources = (resources) => {
512
- if (!resources) return null;
513
- const parts = [];
514
- for (const [k, v] of Object.entries(resources)) {
515
- parts.push(`${k}: ${v}`);
516
- }
517
- return parts.join(', ') || null;
518
- };
519
-
520
- // 构建 Actor 信息行
521
- const actorLines = [
522
- `actor_id: ${actor.actor_id || 'N/A'}`,
523
- `name: ${actor.name || 'N/A'}`,
524
- `class_name: ${actor.class_name || 'N/A'}`,
525
- `state: ${actor.state || 'N/A'}`,
526
- `pid: ${actor.pid || 'N/A'}`,
527
- `ray_namespace: ${actor.ray_namespace || 'N/A'}`,
528
- `job_id: ${actor.job_id || 'N/A'}`,
529
- `node_id: ${actor.node_id || 'N/A'}`
530
- ];
531
-
532
- // 添加可选字段(仅非 null 时显示)
533
- if (actor.is_detached !== null && actor.is_detached !== undefined) {
534
- actorLines.push(`is_detached: ${actor.is_detached}`);
535
- }
536
- if (actor.repr_name) {
537
- actorLines.push(`repr_name: ${actor.repr_name}`);
538
- }
539
- if (actor.placement_group_id) {
540
- actorLines.push(`placement_group_id: ${actor.placement_group_id}`);
541
- }
542
- const requiredRes = formatRequiredResources(actor.required_resources);
543
- if (requiredRes) {
544
- actorLines.push(`resources: ${requiredRes}`);
545
- }
546
- if (actor.num_restarts !== null && actor.num_restarts !== undefined) {
547
- actorLines.push(`num_restarts: ${actor.num_restarts}`);
548
- }
549
- if (actor.death_cause) {
550
- actorLines.push(`death_cause: ${actor.death_cause}`);
551
- }
552
-
553
- const lines = [
554
- `Sandbox: ${sandboxId}`,
555
- `Cluster: ${cluster}`,
556
- '',
557
- '=== Actor Information ===',
558
- ...actorLines,
559
- '',
560
- '=== Node Information ===',
561
- `node_id: ${node.node_id || 'N/A'}`,
562
- `node_ip: ${node.node_ip || 'N/A'}`,
563
- `node_name: ${node.node_name || 'N/A'}`,
564
- `state: ${node.state || 'N/A'}`,
565
- `is_head_node: ${node.is_head_node !== undefined ? node.is_head_node : 'N/A'}`,
566
- `resources: ${formatResources(node.resources_total)}`,
567
- `labels: ${formatLabels(node.labels)}`
568
- ];
569
-
570
- return {
571
- success: true,
572
- exitCode: 0,
573
- stdout: lines.join('\n'),
574
- stderr: ''
575
- };
576
- }
577
-
578
- /**
579
- * 获取集群状态(通过 HTTP API)
580
- * @param {string} baseUrl - Ray Dashboard 地址
581
- * @param {string} format - 输出格式(默认为文本格式)
582
- * @returns {Promise<Object>} 执行结果
583
- */
584
- async fetchClusterStatus(baseUrl, format) {
585
- // format: 默认或任何非json值 -> format=1(文本格式)
586
- // format: json -> format=0(JSON格式)
587
- const formatParam = format === 'json' ? '0' : '1';
588
-
589
- // 处理 baseUrl ,移除尾部斜杠
590
- const cleanBaseUrl = baseUrl.replace(/\/$/, '');
591
- const url = `${cleanBaseUrl}/api/cluster_status?format=${formatParam}`;
592
-
593
- if (this.verbose >= 3) {
594
- logger.debug(`Fetching cluster status from: ${url}`);
595
- }
596
-
597
- return new Promise((resolve, reject) => {
598
- const urlObj = new URL(url);
599
- const protocol = urlObj.protocol === 'https:' ? https : http;
600
-
601
- const options = {
602
- hostname: urlObj.hostname,
603
- port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
604
- path: urlObj.pathname + urlObj.search,
605
- method: 'GET',
606
- headers: {
607
- 'User-Agent': 'rock-cli'
608
- },
609
- rejectUnauthorized: false // 忽略 SSL 证书验证
610
- };
611
-
612
- protocol.get(options, (res) => {
613
- // 检查 HTTP 状态码
614
- if (res.statusCode !== 200) {
615
- let errorData = '';
616
- res.on('data', (chunk) => {
617
- errorData += chunk;
618
- });
619
- res.on('end', () => {
620
- reject(new Error(`HTTP ${res.statusCode}: ${errorData || res.statusMessage}`));
621
- });
622
- return;
623
- }
624
-
625
- let data = '';
626
-
627
- // 设置编码
628
- res.setEncoding('utf8');
629
-
630
- res.on('data', (chunk) => {
631
- data += chunk;
632
- });
633
-
634
- res.on('end', () => {
635
- try {
636
- if (this.verbose >= 3) {
637
- logger.debug(`Raw response: ${data.substring(0, 200)}...`);
638
- }
639
-
640
- const response = JSON.parse(data);
641
-
642
- if (!response.result) {
643
- return resolve({
644
- success: false,
645
- exitCode: 1,
646
- stdout: '',
647
- stderr: response.msg || 'Failed to fetch cluster status'
648
- });
649
- }
650
-
651
- // format=1: 返回格式化的文本
652
- if (formatParam === '1') {
653
- resolve({
654
- success: true,
655
- exitCode: 0,
656
- stdout: response.data.clusterStatus || '',
657
- stderr: ''
658
- });
659
- } else {
660
- // format=0: 返回 JSON
661
- resolve({
662
- success: true,
663
- exitCode: 0,
664
- stdout: JSON.stringify(response.data, null, 2),
665
- stderr: ''
666
- });
667
- }
668
- } catch (error) {
669
- reject(new Error(`Failed to parse cluster status response: ${error.message}`));
670
- }
671
- });
672
- }).on('error', (error) => {
673
- reject(new Error(`Failed to fetch cluster status: ${error.message}`));
674
- });
675
- });
676
- }
677
-
678
- /**
679
- * 执行 ray-cli 命令
680
- * @param {Object} argv - yargs 参数对象
681
- * @returns {Promise<Object>} 执行结果
682
- */
683
- async execute(argv) {
684
- // 验证必需参数
685
- if (!argv.cluster && !argv.address) {
686
- throw new Error('Either --cluster or --address is required');
687
- }
688
-
689
- // 1. 检查 ray-cli
690
- await this.checkRayCliInstalled();
691
-
692
- // 2. 构建命令参数
693
- const args = this.buildRayArgs(argv);
694
-
695
- // 3. 输出调试信息
696
- if (this.verbose >= 3) {
697
- logger.debug(`Executing: ray ${args.join(' ')}`);
698
- }
699
-
700
- // 4. 如果是 mock 模式,直接返回成功(用于测试)
701
- if (process.env.ROCK_CLI_MOCK_RAY === '1') {
702
- return Promise.resolve({
703
- success: true,
704
- exitCode: 0,
705
- stdout: 'Mock ray output',
706
- stderr: ''
707
- });
708
- }
709
-
710
- // 5. 执行命令
711
- return new Promise((resolve, reject) => {
712
- let stdout = '';
713
- let stderr = '';
714
-
715
- const rayProcess = spawn('ray', args, {
716
- shell: false,
717
- env: process.env
718
- });
719
-
720
- // 捕获输出
721
- rayProcess.stdout.on('data', (data) => {
722
- stdout += data.toString();
723
- });
724
-
725
- rayProcess.stderr.on('data', (data) => {
726
- stderr += data.toString();
727
- });
728
-
729
- // 处理退出
730
- rayProcess.on('close', (code) => {
731
- resolve({
732
- success: code === 0,
733
- exitCode: code,
734
- stdout: stdout.trim(),
735
- stderr: stderr.trim()
736
- });
737
- });
738
-
739
- rayProcess.on('error', (error) => {
740
- reject(new Error(`Failed to execute ray-cli: ${error.message}`));
741
- });
742
- });
743
- }
744
- }
745
-
746
- module.exports = { RayCliWrapper };