rl-rockcli 0.0.1
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/LICENSE +21 -0
- package/bin/rockcli.js +40 -0
- package/package.json +62 -0
- package/src/core/commands/attach/index.js +242 -0
- package/src/core/commands/log/constants.js +20 -0
- package/src/core/commands/log/index.js +94 -0
- package/src/core/commands/log/search.js +106 -0
- package/src/core/commands/sandbox/index.js +428 -0
- package/src/core/config/index.js +77 -0
- package/src/core/display/constants.js +59 -0
- package/src/core/display/format.js +178 -0
- package/src/core/display/highlight.js +34 -0
- package/src/core/index.js +55 -0
- package/src/core/providers/index.js +9 -0
- package/src/core/providers/log-provider.js +79 -0
- package/src/core/sdks/sandbox/client.js +472 -0
- package/src/core/sdks/sandbox/config.js +57 -0
- package/src/core/sdks/sandbox/index.js +13 -0
- package/src/core/sdks/sandbox/types.js +5 -0
- package/src/core/utils/index.js +9 -0
- package/src/core/utils/logger.js +106 -0
- package/src/core/utils/time.js +52 -0
- package/src/plugins/oss-file-log/file-client.js +186 -0
- package/src/plugins/oss-file-log/index.js +18 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Client for managing sandbox environments
|
|
3
|
+
* 开源版 Sandbox SDK 客户端
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const axios = require('axios');
|
|
7
|
+
const { SandboxConfig } = require('./config');
|
|
8
|
+
const logger = require('../../utils/logger');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Simple HTTP logger for SDK
|
|
12
|
+
*/
|
|
13
|
+
const httpLogger = {
|
|
14
|
+
logRequest: (method, url, data) => {
|
|
15
|
+
logger.debug(`[HTTP] ${method} ${url}`);
|
|
16
|
+
if (data) logger.debug(`[HTTP] Request: ${JSON.stringify(data).substring(0, 200)}`);
|
|
17
|
+
},
|
|
18
|
+
logResponse: (method, url, status, duration, data, traceId) => {
|
|
19
|
+
logger.debug(`[HTTP] ${method} ${url} - ${status} (${duration}ms)`);
|
|
20
|
+
if (traceId) logger.debug(`[HTTP] TraceId: ${traceId}`);
|
|
21
|
+
},
|
|
22
|
+
logError: (method, url, error, duration, traceId) => {
|
|
23
|
+
logger.error(`[HTTP] ${method} ${url} - Error: ${error.message} (${duration}ms)`);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sandbox Client for managing sandbox environments
|
|
29
|
+
*/
|
|
30
|
+
class SandboxClient {
|
|
31
|
+
constructor(config, options = {}) {
|
|
32
|
+
if (config instanceof SandboxConfig) {
|
|
33
|
+
this.config = config;
|
|
34
|
+
} else {
|
|
35
|
+
this.config = new SandboxConfig(config);
|
|
36
|
+
}
|
|
37
|
+
this.config.validate({ requireImage: options.requireImage !== false });
|
|
38
|
+
|
|
39
|
+
this._sandboxId = null;
|
|
40
|
+
this._hostName = null;
|
|
41
|
+
this._hostIp = null;
|
|
42
|
+
this._url = `${this.config.baseUrl}/apis/envs/sandbox/v1`;
|
|
43
|
+
this._onLog = options.onLog || null;
|
|
44
|
+
|
|
45
|
+
// Create axios instance with interceptors for HTTP logging
|
|
46
|
+
this._axios = axios.create();
|
|
47
|
+
this._setupInterceptors();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_log(level, message) {
|
|
51
|
+
if (this._onLog) {
|
|
52
|
+
this._onLog(level, message);
|
|
53
|
+
} else {
|
|
54
|
+
logger[level](message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_setupInterceptors() {
|
|
59
|
+
this._axios.interceptors.request.use((config) => {
|
|
60
|
+
config._startTime = Date.now();
|
|
61
|
+
httpLogger.logRequest(
|
|
62
|
+
config.method?.toUpperCase() || 'GET',
|
|
63
|
+
config.url,
|
|
64
|
+
config.data
|
|
65
|
+
);
|
|
66
|
+
return config;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this._axios.interceptors.response.use(
|
|
70
|
+
(response) => {
|
|
71
|
+
const duration = Date.now() - (response.config._startTime || Date.now());
|
|
72
|
+
const traceId = this._extractTraceId(response.headers);
|
|
73
|
+
httpLogger.logResponse(
|
|
74
|
+
response.config.method?.toUpperCase() || 'GET',
|
|
75
|
+
response.config.url,
|
|
76
|
+
response.status,
|
|
77
|
+
duration,
|
|
78
|
+
response.data,
|
|
79
|
+
traceId
|
|
80
|
+
);
|
|
81
|
+
return response;
|
|
82
|
+
},
|
|
83
|
+
(error) => {
|
|
84
|
+
const duration = Date.now() - (error.config?._startTime || Date.now());
|
|
85
|
+
const traceId = error.response ? this._extractTraceId(error.response.headers) : null;
|
|
86
|
+
httpLogger.logError(
|
|
87
|
+
error.config?.method?.toUpperCase() || 'GET',
|
|
88
|
+
error.config?.url || 'unknown',
|
|
89
|
+
error,
|
|
90
|
+
duration,
|
|
91
|
+
traceId
|
|
92
|
+
);
|
|
93
|
+
return Promise.reject(error);
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_extractTraceId(headers) {
|
|
99
|
+
if (!headers) return null;
|
|
100
|
+
const traceIdHeaders = ['x-trace-id', 'x-request-id', 'traceid', 'trace-id', 'request-id'];
|
|
101
|
+
for (const headerName of traceIdHeaders) {
|
|
102
|
+
const value = headers[headerName];
|
|
103
|
+
if (value) return value;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
get sandboxId() {
|
|
109
|
+
return this._sandboxId;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
get hostName() {
|
|
113
|
+
return this._hostName;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get hostIp() {
|
|
117
|
+
return this._hostIp;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_buildHeaders() {
|
|
121
|
+
const headers = {
|
|
122
|
+
'Content-Type': 'application/json',
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (this.config.xrlAuthorization) {
|
|
126
|
+
headers['XRL-Authorization'] = `Bearer ${this.config.xrlAuthorization}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (this.config.extraHeaders) {
|
|
130
|
+
Object.assign(headers, this.config.extraHeaders);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return headers;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Start a new sandbox instance
|
|
138
|
+
*/
|
|
139
|
+
async start() {
|
|
140
|
+
const url = `${this._url}/start_async`;
|
|
141
|
+
const headers = this._buildHeaders();
|
|
142
|
+
const data = {
|
|
143
|
+
image: this.config.image,
|
|
144
|
+
auto_clear_time: Math.floor(this.config.autoClearSeconds / 60),
|
|
145
|
+
auto_clear_time_minutes: Math.floor(this.config.autoClearSeconds / 60),
|
|
146
|
+
startup_timeout: this.config.startupTimeout,
|
|
147
|
+
memory: this.config.memory,
|
|
148
|
+
cpus: this.config.cpus,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
logger.debug(`[Sandbox SDK] POST ${url}`);
|
|
152
|
+
const response = await this._axios.post(url, data, { headers });
|
|
153
|
+
|
|
154
|
+
if (response.data.status !== 'Success') {
|
|
155
|
+
const error = response.data.result || response.data;
|
|
156
|
+
throw new Error(`Failed to start sandbox: ${JSON.stringify(error)}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const result = response.data.result;
|
|
160
|
+
this._sandboxId = result.sandbox_id;
|
|
161
|
+
this._hostName = result.host_name;
|
|
162
|
+
this._hostIp = result.host_ip;
|
|
163
|
+
|
|
164
|
+
let isAlive = false;
|
|
165
|
+
if (this.config.waitForAlive) {
|
|
166
|
+
await this._waitForAlive();
|
|
167
|
+
isAlive = true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
sandboxId: this._sandboxId,
|
|
172
|
+
hostName: this._hostName,
|
|
173
|
+
hostIp: this._hostIp,
|
|
174
|
+
isAlive: isAlive,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async _waitForAlive() {
|
|
179
|
+
const startTime = Date.now();
|
|
180
|
+
const timeout = this.config.startupTimeout * 1000;
|
|
181
|
+
|
|
182
|
+
this._log('info', 'Waiting for sandbox to be ready...');
|
|
183
|
+
|
|
184
|
+
while (Date.now() - startTime < timeout) {
|
|
185
|
+
const status = await this.getStatus();
|
|
186
|
+
|
|
187
|
+
if (status.is_alive) {
|
|
188
|
+
this._log('info', `Sandbox is ready (ID: ${this._sandboxId})`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
throw new Error(`Sandbox did not become alive within ${this.config.startupTimeout}s`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get sandbox status
|
|
200
|
+
*/
|
|
201
|
+
async getStatus() {
|
|
202
|
+
const url = `${this._url}/get_status?sandbox_id=${this._sandboxId}`;
|
|
203
|
+
const headers = this._buildHeaders();
|
|
204
|
+
|
|
205
|
+
const response = await this._axios.get(url, { headers });
|
|
206
|
+
|
|
207
|
+
if (response.data.status !== 'Success') {
|
|
208
|
+
throw new Error(`Failed to get status: ${JSON.stringify(response.data)}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return response.data.result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Check if sandbox is alive
|
|
216
|
+
*/
|
|
217
|
+
async isAlive() {
|
|
218
|
+
try {
|
|
219
|
+
const status = await this.getStatus();
|
|
220
|
+
return {
|
|
221
|
+
isAlive: status.is_alive,
|
|
222
|
+
message: status.host_name || '',
|
|
223
|
+
};
|
|
224
|
+
} catch (error) {
|
|
225
|
+
throw new Error(`Failed to check isAlive: ${error.message}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Execute a command in the sandbox
|
|
231
|
+
*/
|
|
232
|
+
async execute(command) {
|
|
233
|
+
const url = `${this._url}/execute`;
|
|
234
|
+
const headers = this._buildHeaders();
|
|
235
|
+
|
|
236
|
+
let commandArray;
|
|
237
|
+
if (Array.isArray(command)) {
|
|
238
|
+
commandArray = command;
|
|
239
|
+
} else {
|
|
240
|
+
commandArray = ['sh', '-c', command];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const data = {
|
|
244
|
+
command: commandArray,
|
|
245
|
+
sandbox_id: this._sandboxId,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const response = await this._axios.post(url, data, { headers });
|
|
249
|
+
|
|
250
|
+
if (response.data.status !== 'Success') {
|
|
251
|
+
throw new Error(`Failed to execute command: ${JSON.stringify(response.data)}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return response.data.result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Stop the sandbox
|
|
259
|
+
*/
|
|
260
|
+
async stop() {
|
|
261
|
+
if (!this._sandboxId) {
|
|
262
|
+
logger.warn('No sandbox ID to stop');
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const url = `${this._url}/stop`;
|
|
268
|
+
const headers = this._buildHeaders();
|
|
269
|
+
const data = { sandbox_id: this._sandboxId };
|
|
270
|
+
|
|
271
|
+
await this._axios.post(url, data, { headers });
|
|
272
|
+
logger.info(`Sandbox ${this._sandboxId} stopped successfully`);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
logger.warn(`Failed to stop sandbox: ${error.message}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Create a bash session
|
|
280
|
+
*/
|
|
281
|
+
async createSession(options = {}) {
|
|
282
|
+
const url = `${this._url}/create_session`;
|
|
283
|
+
const headers = this._buildHeaders();
|
|
284
|
+
|
|
285
|
+
const data = {
|
|
286
|
+
sandbox_id: this._sandboxId,
|
|
287
|
+
session: options.session || 'default',
|
|
288
|
+
session_type: options.sessionType || 'bash',
|
|
289
|
+
startup_source: options.startupSource || [],
|
|
290
|
+
env_enable: options.envEnable || false,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const response = await this._axios.post(url, data, { headers });
|
|
294
|
+
|
|
295
|
+
if (response.data.status !== 'Success') {
|
|
296
|
+
throw new Error(`Failed to create session: ${JSON.stringify(response.data)}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return response.data.result;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Run a command in a session
|
|
304
|
+
*/
|
|
305
|
+
async runInSession(action) {
|
|
306
|
+
const url = `${this._url}/run_in_session`;
|
|
307
|
+
const headers = this._buildHeaders();
|
|
308
|
+
|
|
309
|
+
const data = {
|
|
310
|
+
sandbox_id: this._sandboxId,
|
|
311
|
+
command: action.command,
|
|
312
|
+
session: action.session || 'default',
|
|
313
|
+
timeout: action.timeout || null,
|
|
314
|
+
is_interactive_command: action.isInteractiveCommand || false,
|
|
315
|
+
check: action.check || 'raise',
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const axiosConfig = { headers };
|
|
319
|
+
|
|
320
|
+
if (action.axiosTimeout) {
|
|
321
|
+
axiosConfig.timeout = action.axiosTimeout;
|
|
322
|
+
} else if (action.timeout) {
|
|
323
|
+
axiosConfig.timeout = action.timeout * 1000;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (action.signal) {
|
|
327
|
+
axiosConfig.signal = action.signal;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const response = await this._axios.post(url, data, axiosConfig);
|
|
331
|
+
|
|
332
|
+
if (response.data.status !== 'Success') {
|
|
333
|
+
throw new Error(`Failed to run in session: ${JSON.stringify(response.data)}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return response.data.result;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Close a session
|
|
341
|
+
*/
|
|
342
|
+
async closeSession(session = 'default') {
|
|
343
|
+
const url = `${this._url}/close_session`;
|
|
344
|
+
const headers = this._buildHeaders();
|
|
345
|
+
|
|
346
|
+
const data = {
|
|
347
|
+
sandbox_id: this._sandboxId,
|
|
348
|
+
session: session,
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const response = await this._axios.post(url, data, { headers });
|
|
352
|
+
|
|
353
|
+
if (response.data.status !== 'Success') {
|
|
354
|
+
throw new Error(`Failed to close session: ${JSON.stringify(response.data)}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return response.data.result;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Upload a file to the sandbox
|
|
362
|
+
*/
|
|
363
|
+
async uploadFile(localPath, targetPath) {
|
|
364
|
+
const fs = require('fs');
|
|
365
|
+
const path = require('path');
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
if (!fs.existsSync(localPath)) {
|
|
369
|
+
return { success: false, message: `File not found: ${localPath}` };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const url = `${this._url}/upload`;
|
|
373
|
+
const headers = this._buildHeaders();
|
|
374
|
+
delete headers['Content-Type'];
|
|
375
|
+
|
|
376
|
+
const form = new FormData();
|
|
377
|
+
const fileBuffer = fs.readFileSync(localPath);
|
|
378
|
+
const blob = new Blob([fileBuffer], { type: 'application/octet-stream' });
|
|
379
|
+
|
|
380
|
+
form.append('file', blob, path.basename(localPath));
|
|
381
|
+
form.append('target_path', targetPath);
|
|
382
|
+
form.append('sandbox_id', this._sandboxId);
|
|
383
|
+
form.append('container_name', this._sandboxId);
|
|
384
|
+
|
|
385
|
+
const response = await fetch(url, {
|
|
386
|
+
method: 'POST',
|
|
387
|
+
headers,
|
|
388
|
+
body: form,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const data = await response.json();
|
|
392
|
+
|
|
393
|
+
if (data.status !== 'Success') {
|
|
394
|
+
return { success: false, message: `Failed to upload file: ${JSON.stringify(data)}` };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return { success: true, message: `Successfully uploaded ${path.basename(localPath)} to ${targetPath}` };
|
|
398
|
+
} catch (error) {
|
|
399
|
+
return { success: false, message: `Upload failed: ${error.message}` };
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Download a file from the sandbox
|
|
405
|
+
*/
|
|
406
|
+
async downloadFile(filePath) {
|
|
407
|
+
const url = `${this._url}/read_file`;
|
|
408
|
+
const headers = this._buildHeaders();
|
|
409
|
+
|
|
410
|
+
const data = {
|
|
411
|
+
path: filePath,
|
|
412
|
+
sandbox_id: this._sandboxId,
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const response = await this._axios.post(url, data, { headers });
|
|
416
|
+
|
|
417
|
+
if (response.data.status !== 'Success') {
|
|
418
|
+
throw new Error(`Failed to download file: ${JSON.stringify(response.data)}`);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return response.data.result;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Write content to a file in the sandbox
|
|
426
|
+
*/
|
|
427
|
+
async writeFile(content, path) {
|
|
428
|
+
const url = `${this._url}/write_file`;
|
|
429
|
+
const headers = this._buildHeaders();
|
|
430
|
+
|
|
431
|
+
const data = {
|
|
432
|
+
content,
|
|
433
|
+
path,
|
|
434
|
+
sandbox_id: this._sandboxId,
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const response = await this._axios.post(url, data, { headers });
|
|
438
|
+
|
|
439
|
+
if (response.data.status !== 'Success') {
|
|
440
|
+
return { success: false, message: `Failed to write file ${path}` };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return { success: true, message: `Successfully wrote content to file ${path}` };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Read a file from the sandbox
|
|
448
|
+
*/
|
|
449
|
+
async readFile(filePath, startLine, endLine) {
|
|
450
|
+
if (startLine < 1 || endLine < startLine) {
|
|
451
|
+
throw new Error(`Invalid line range: startLine=${startLine}, endLine=${endLine}`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const command = `sed -n ${startLine},${endLine}p ${filePath}`;
|
|
455
|
+
const result = await this.execute(command);
|
|
456
|
+
|
|
457
|
+
if (result.exit_code !== 0) {
|
|
458
|
+
throw new Error(`Failed to read file ${filePath}: ${result.stderr}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return { content: result.stdout };
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Close the sandbox
|
|
466
|
+
*/
|
|
467
|
+
async close() {
|
|
468
|
+
await this.stop();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
module.exports = { SandboxClient };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox SDK Configuration
|
|
3
|
+
* 开源版配置 - 默认 localhost:8080,支持环境变量配置
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// 开源版默认镜像(不放在配置文件中,直接定义常量)
|
|
7
|
+
const DEFAULT_IMAGE = 'python:3.11';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sandbox configuration class
|
|
11
|
+
* 开源版配置类 - 不包含环境特定的默认值
|
|
12
|
+
*/
|
|
13
|
+
class SandboxConfig {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
// Technical defaults (not environment-specific)
|
|
16
|
+
this.autoClearSeconds = options.autoClearSeconds || 300; // 5 minutes
|
|
17
|
+
this.startupTimeout = options.startupTimeout || 120; // 2 minutes
|
|
18
|
+
this.memory = options.memory || '8g';
|
|
19
|
+
this.cpus = options.cpus !== undefined ? options.cpus : 2.0;
|
|
20
|
+
|
|
21
|
+
// Environment-specific settings (no defaults, must be provided)
|
|
22
|
+
this.baseUrl = options.baseUrl || process.env.ROCK_BASE_URL || 'http://localhost:8080';
|
|
23
|
+
this.xrlAuthorization = options.xrlAuthorization || process.env.ROCK_API_KEY;
|
|
24
|
+
this.image = options.image;
|
|
25
|
+
this.cluster = options.cluster;
|
|
26
|
+
this.namespace = options.namespace || null;
|
|
27
|
+
this.userId = options.userId || null;
|
|
28
|
+
this.experimentId = options.experimentId || null;
|
|
29
|
+
this.extraHeaders = options.extraHeaders || {};
|
|
30
|
+
this.waitForAlive = options.waitForAlive !== undefined ? options.waitForAlive : false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
validate(options = {}) {
|
|
34
|
+
const { requireImage = true } = options;
|
|
35
|
+
|
|
36
|
+
if (!this.baseUrl) {
|
|
37
|
+
throw new Error('baseUrl is required');
|
|
38
|
+
}
|
|
39
|
+
if (requireImage && !this.image) {
|
|
40
|
+
throw new Error('image is required');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get sandbox configuration for opensource version
|
|
47
|
+
* 开源版配置获取函数 - 返回开源版默认值
|
|
48
|
+
* @returns {Object} Sandbox config with default values
|
|
49
|
+
*/
|
|
50
|
+
function getOpenSourceSandboxConfig() {
|
|
51
|
+
return {
|
|
52
|
+
baseUrl: process.env.ROCK_BASE_URL || 'http://localhost:8080',
|
|
53
|
+
image: DEFAULT_IMAGE,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { SandboxConfig, getOpenSourceSandboxConfig, DEFAULT_IMAGE };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox SDK
|
|
3
|
+
* 开源版 Sandbox SDK 入口
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { SandboxConfig, getOpenSourceSandboxConfig } = require('./config');
|
|
7
|
+
const { SandboxClient } = require('./client');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
SandboxConfig,
|
|
11
|
+
SandboxClient,
|
|
12
|
+
getOpenSourceSandboxConfig,
|
|
13
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志工具模块
|
|
3
|
+
* 基于 winston 实现,支持通过 verbose 级别控制日志输出
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const winston = require('winston');
|
|
7
|
+
|
|
8
|
+
// 日志级别枚举
|
|
9
|
+
const LogLevel = {
|
|
10
|
+
ERROR: 0,
|
|
11
|
+
WARNING: 1,
|
|
12
|
+
INFO: 2,
|
|
13
|
+
DEBUG: 3
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// 默认 verbose 级别 (0 = 只输出 error)
|
|
17
|
+
let verboseLevel = 0;
|
|
18
|
+
|
|
19
|
+
// TUI 模式标志:当启用时,抑制所有日志输出以避免干扰 TUI 渲染
|
|
20
|
+
let tuiModeEnabled = false;
|
|
21
|
+
|
|
22
|
+
// 创建 winston logger 实例
|
|
23
|
+
const logger = winston.createLogger({
|
|
24
|
+
levels: {
|
|
25
|
+
error: 0,
|
|
26
|
+
warn: 1,
|
|
27
|
+
info: 2,
|
|
28
|
+
debug: 3
|
|
29
|
+
},
|
|
30
|
+
transports: [
|
|
31
|
+
new winston.transports.Console({
|
|
32
|
+
stderrLevels: ['error', 'warn'],
|
|
33
|
+
consoleWarnLevels: ['warn'],
|
|
34
|
+
format: winston.format.combine(
|
|
35
|
+
winston.format.printf(({ level, message, ...meta }) => {
|
|
36
|
+
const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';
|
|
37
|
+
return `${message}${metaStr}`;
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 初始设置为 info 级别
|
|
45
|
+
logger.level = 'info';
|
|
46
|
+
|
|
47
|
+
function setVerboseLevel(level) {
|
|
48
|
+
verboseLevel = Math.min(Math.max(level, 0), 3);
|
|
49
|
+
const levelMap = ['info', 'info', 'debug', 'debug'];
|
|
50
|
+
logger.level = levelMap[verboseLevel];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getVerboseLevel() {
|
|
54
|
+
return verboseLevel;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function shouldLog(level) {
|
|
58
|
+
return level <= verboseLevel;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function setTUIMode(enabled) {
|
|
62
|
+
tuiModeEnabled = !!enabled;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getTUIMode() {
|
|
66
|
+
return tuiModeEnabled;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function error(...args) {
|
|
70
|
+
if (tuiModeEnabled) return;
|
|
71
|
+
logger.error(args.join(' '));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function warn(...args) {
|
|
75
|
+
if (tuiModeEnabled) return;
|
|
76
|
+
logger.warn(args.join(' '));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function info(...args) {
|
|
80
|
+
if (tuiModeEnabled) return;
|
|
81
|
+
logger.info(args.join(' '));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function debug(...args) {
|
|
85
|
+
if (tuiModeEnabled) return;
|
|
86
|
+
logger.debug(args.join(' '));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseVerboseLevel(argv) {
|
|
90
|
+
const vCount = argv.v || 0;
|
|
91
|
+
return Math.min(vCount, 3);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
LogLevel,
|
|
96
|
+
setVerboseLevel,
|
|
97
|
+
getVerboseLevel,
|
|
98
|
+
shouldLog,
|
|
99
|
+
error,
|
|
100
|
+
warn,
|
|
101
|
+
info,
|
|
102
|
+
debug,
|
|
103
|
+
parseVerboseLevel,
|
|
104
|
+
setTUIMode,
|
|
105
|
+
getTUIMode
|
|
106
|
+
};
|