rl-rockcli 0.0.4 → 0.0.6
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/commands/log/core/constants.js +237 -0
- package/commands/log/core/display.js +370 -0
- package/commands/log/core/search.js +330 -0
- package/commands/log/core/tail.js +216 -0
- package/commands/log/core/utils.js +424 -0
- package/commands/log.js +298 -0
- package/commands/sandbox/core/log-bridge.js +119 -0
- package/commands/sandbox/core/replay/analyzer.js +311 -0
- package/commands/sandbox/core/replay/batch-orchestrator.js +536 -0
- package/commands/sandbox/core/replay/batch-task.js +369 -0
- package/commands/sandbox/core/replay/concurrent-display.js +70 -0
- package/commands/sandbox/core/replay/concurrent-orchestrator.js +170 -0
- package/commands/sandbox/core/replay/data-source.js +86 -0
- package/commands/sandbox/core/replay/display.js +231 -0
- package/commands/sandbox/core/replay/executor.js +634 -0
- package/commands/sandbox/core/replay/history-fetcher.js +124 -0
- package/commands/sandbox/core/replay/index.js +338 -0
- package/commands/sandbox/core/replay/loghouse-data-source.js +177 -0
- package/commands/sandbox/core/replay/pid-mapping.js +26 -0
- package/commands/sandbox/core/replay/request.js +109 -0
- package/commands/sandbox/core/replay/worker.js +166 -0
- package/commands/sandbox/core/session.js +346 -0
- package/commands/sandbox/log-bridge.js +2 -0
- package/commands/sandbox/ray.js +2 -0
- package/commands/sandbox/replay/analyzer.js +311 -0
- package/commands/sandbox/replay/batch-orchestrator.js +536 -0
- package/commands/sandbox/replay/batch-task.js +369 -0
- package/commands/sandbox/replay/concurrent-display.js +70 -0
- package/commands/sandbox/replay/concurrent-orchestrator.js +170 -0
- package/commands/sandbox/replay/display.js +231 -0
- package/commands/sandbox/replay/executor.js +634 -0
- package/commands/sandbox/replay/history-fetcher.js +118 -0
- package/commands/sandbox/replay/index.js +338 -0
- package/commands/sandbox/replay/pid-mapping.js +26 -0
- package/commands/sandbox/replay/request.js +109 -0
- package/commands/sandbox/replay/worker.js +166 -0
- package/commands/sandbox/replay.js +2 -0
- package/commands/sandbox/session.js +2 -0
- package/commands/sandbox-original.js +1393 -0
- package/commands/sandbox.js +499 -0
- package/help/help.json +1071 -0
- package/help/middleware.js +71 -0
- package/help/renderer.js +800 -0
- package/index.js +20 -51
- package/lib/plugin-context.js +40 -0
- package/package.json +1 -1
- package/sdks/sandbox/core/client.js +845 -0
- package/sdks/sandbox/core/config.js +70 -0
- package/sdks/sandbox/core/types.js +74 -0
- package/sdks/sandbox/httpLogger.js +251 -0
- package/sdks/sandbox/index.js +9 -0
- package/utils/asciiArt.js +138 -0
- package/utils/bun-compat.js +59 -0
- package/utils/ciPipelines.js +138 -0
- package/utils/cli.js +17 -0
- package/utils/command-router.js +79 -0
- package/utils/configManager.js +503 -0
- package/utils/dependency-resolver.js +135 -0
- package/utils/eagleeye_traceid.js +151 -0
- package/utils/envDetector.js +78 -0
- package/utils/execution_logger.js +415 -0
- package/utils/featureManager.js +68 -0
- package/utils/firstTimeTip.js +44 -0
- package/utils/hook-manager.js +125 -0
- package/utils/http-logger.js +264 -0
- package/utils/i18n.js +139 -0
- package/utils/image-progress.js +159 -0
- package/utils/logger.js +154 -0
- package/utils/plugin-loader.js +124 -0
- package/utils/plugin-manager.js +348 -0
- package/utils/ray_cli_wrapper.js +746 -0
- package/utils/sandbox-client.js +419 -0
- package/utils/terminal.js +32 -0
- package/utils/tips.js +106 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 首次使用提示工具
|
|
3
|
+
* 用于在用户首次使用时显示 rc 别名的提示信息
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs-extra');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { showAsciiArt } = require('./asciiArt');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 获取配置目录和标记文件路径
|
|
12
|
+
*/
|
|
13
|
+
function getPaths() {
|
|
14
|
+
const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.rock');
|
|
15
|
+
const FLAG_FILE = path.join(CONFIG_DIR, 'first_time_tip_shown');
|
|
16
|
+
return { CONFIG_DIR, FLAG_FILE };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 检查并显示首次使用提示
|
|
21
|
+
* @returns {string|null} 如果是首次使用返回提示信息,否则返回null
|
|
22
|
+
*/
|
|
23
|
+
function checkAndShowFirstTimeTip() {
|
|
24
|
+
const { CONFIG_DIR, FLAG_FILE } = getPaths();
|
|
25
|
+
|
|
26
|
+
// 如果标记文件已存在,说明不是首次使用
|
|
27
|
+
if (fs.existsSync(FLAG_FILE)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 确保配置目录存在
|
|
32
|
+
fs.ensureDirSync(CONFIG_DIR);
|
|
33
|
+
|
|
34
|
+
// 创建标记文件
|
|
35
|
+
fs.writeFileSync(FLAG_FILE, new Date().toISOString());
|
|
36
|
+
|
|
37
|
+
// 返回提示信息 + ASCII 艺术字
|
|
38
|
+
return showAsciiArt() + '\n💡 提示:您可以使用 rc 命令代替 rockcli 命令,更加简洁高效!\n';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
checkAndShowFirstTimeTip,
|
|
43
|
+
getPaths
|
|
44
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const logger = require('./logger');
|
|
2
|
+
const { createPluginContext } = require('../lib/plugin-context');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 钩子管理器
|
|
6
|
+
* 用于注册、排序和执行钩子
|
|
7
|
+
*/
|
|
8
|
+
class HookManager {
|
|
9
|
+
constructor() {
|
|
10
|
+
this._hooks = new Map(); // Map<hookName, Array<hookDef>>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 注册钩子
|
|
15
|
+
* @param {string} hookName - 钩子名称
|
|
16
|
+
* @param {Object} hookDef - 钩子定义
|
|
17
|
+
* @param {number} priority - 优先级
|
|
18
|
+
* @param {string} pluginName - 插件名称
|
|
19
|
+
*/
|
|
20
|
+
registerHook(hookName, hookDef, priority = 0, pluginName = 'unknown') {
|
|
21
|
+
if (!this._hooks.has(hookName)) {
|
|
22
|
+
this._hooks.set(hookName, []);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this._hooks.get(hookName).push({
|
|
26
|
+
...hookDef,
|
|
27
|
+
priority,
|
|
28
|
+
pluginName,
|
|
29
|
+
_loadOrder: this._hooks.get(hookName).length // 用于保持加载顺序
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 获取指定钩子的所有处理函数
|
|
35
|
+
* @param {string} hookName - 钩子名称
|
|
36
|
+
* @returns {Array} - 排序后的钩子数组
|
|
37
|
+
*/
|
|
38
|
+
getHooks(hookName) {
|
|
39
|
+
const hooks = this._hooks.get(hookName) || [];
|
|
40
|
+
|
|
41
|
+
// 按优先级排序,相同优先级按加载顺序排序
|
|
42
|
+
return [...hooks].sort((a, b) => {
|
|
43
|
+
if (a.priority !== b.priority) {
|
|
44
|
+
return a.priority - b.priority; // 优先级升序
|
|
45
|
+
}
|
|
46
|
+
return a._loadOrder - b._loadOrder; // 加载顺序
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 执行钩子
|
|
52
|
+
* @param {string} hookName - 钩子名称
|
|
53
|
+
* @param {Object} baseContext - 基础上下文
|
|
54
|
+
* @returns {Promise<{prevented: boolean, message?: string}>}
|
|
55
|
+
*/
|
|
56
|
+
async executeHooks(hookName, baseContext = {}) {
|
|
57
|
+
const hooks = this.getHooks(hookName);
|
|
58
|
+
|
|
59
|
+
if (hooks.length === 0) {
|
|
60
|
+
return { prevented: false };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 创建 context 对象
|
|
64
|
+
const context = createPluginContext(baseContext);
|
|
65
|
+
|
|
66
|
+
for (const hook of hooks) {
|
|
67
|
+
try {
|
|
68
|
+
await hook.handler(context);
|
|
69
|
+
|
|
70
|
+
// 检查是否阻止
|
|
71
|
+
if (context.isPrevented) {
|
|
72
|
+
return {
|
|
73
|
+
prevented: true,
|
|
74
|
+
message: context.preventMessage
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
logger.error(`Hook execution failed: ${hookName} (plugin: ${hook.pluginName})`);
|
|
79
|
+
logger.error(error);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { prevented: false };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 获取所有钩子信息
|
|
89
|
+
* @returns {Object} - 钩子信息对象
|
|
90
|
+
*/
|
|
91
|
+
getHooksInfo() {
|
|
92
|
+
const info = {};
|
|
93
|
+
for (const [hookName, hooks] of this._hooks.entries()) {
|
|
94
|
+
info[hookName] = hooks.map(h => ({
|
|
95
|
+
priority: h.priority,
|
|
96
|
+
pluginName: h.pluginName
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
return info;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 单例实例
|
|
104
|
+
HookManager._instance = null;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 获取单例实例
|
|
108
|
+
* @static
|
|
109
|
+
*/
|
|
110
|
+
HookManager.getInstance = function() {
|
|
111
|
+
if (!HookManager._instance) {
|
|
112
|
+
HookManager._instance = new HookManager();
|
|
113
|
+
}
|
|
114
|
+
return HookManager._instance;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 设置单例实例
|
|
119
|
+
* @static
|
|
120
|
+
*/
|
|
121
|
+
HookManager.setInstance = function(instance) {
|
|
122
|
+
HookManager._instance = instance;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
module.exports = HookManager;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Logger - Event emitter for HTTP request/response logging
|
|
3
|
+
*
|
|
4
|
+
* Log format: [HH:mm:ss.SSS] [LEVEL] [ACTION] [traceId] msg
|
|
5
|
+
*
|
|
6
|
+
* This module provides a centralized way to log HTTP requests and responses.
|
|
7
|
+
* InkREPL's console can subscribe to these events to display them.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const EventEmitter = require('events');
|
|
11
|
+
|
|
12
|
+
// Action types
|
|
13
|
+
const ACTION = {
|
|
14
|
+
SESSION: 'SESSION',
|
|
15
|
+
HTTP_REQ: 'HTTP_REQ',
|
|
16
|
+
HTTP_RES: 'HTTP_RES',
|
|
17
|
+
HTTP_ERR: 'HTTP_ERR',
|
|
18
|
+
CMD: 'CMD',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Log levels
|
|
22
|
+
const LEVEL = {
|
|
23
|
+
DEBUG: 'DEBUG',
|
|
24
|
+
INFO: 'INFO',
|
|
25
|
+
WARN: 'WARN',
|
|
26
|
+
ERROR: 'ERROR',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
class HttpLogger extends EventEmitter {
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
this.enabled = false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Enable HTTP logging
|
|
37
|
+
*/
|
|
38
|
+
enable() {
|
|
39
|
+
this.enabled = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Disable HTTP logging
|
|
44
|
+
*/
|
|
45
|
+
disable() {
|
|
46
|
+
this.enabled = false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Format timestamp with milliseconds
|
|
51
|
+
*/
|
|
52
|
+
_formatTime(date) {
|
|
53
|
+
const pad = (n, len = 2) => String(n).padStart(len, '0');
|
|
54
|
+
return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Format log message
|
|
59
|
+
* Format: [HH:mm:ss.SSS] [LEVEL] [ACTION] [traceId] msg
|
|
60
|
+
*/
|
|
61
|
+
_formatMessage(level, action, traceId, msg) {
|
|
62
|
+
const time = this._formatTime(new Date());
|
|
63
|
+
const tid = traceId || '-';
|
|
64
|
+
return `[${time}] [${level.padEnd(5)}] [${action.padEnd(8)}] [${tid}] ${msg}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Truncate and format JSON for logging
|
|
69
|
+
*/
|
|
70
|
+
_formatBody(data, maxLen = 200) {
|
|
71
|
+
if (!data) return null;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
let json = JSON.stringify(data);
|
|
75
|
+
if (json.length > maxLen) {
|
|
76
|
+
json = json.slice(0, maxLen) + '...';
|
|
77
|
+
}
|
|
78
|
+
return json;
|
|
79
|
+
} catch {
|
|
80
|
+
return String(data).slice(0, maxLen);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Log an HTTP request
|
|
86
|
+
* @param {string} method - HTTP method (GET, POST, etc.)
|
|
87
|
+
* @param {string} url - Request URL
|
|
88
|
+
* @param {Object} data - Request body (optional)
|
|
89
|
+
*/
|
|
90
|
+
logRequest(method, url, data = null) {
|
|
91
|
+
if (!this.enabled) return;
|
|
92
|
+
|
|
93
|
+
const urlPath = this._extractPath(url);
|
|
94
|
+
const msg = `${method} ${urlPath}`;
|
|
95
|
+
const message = this._formatMessage(LEVEL.DEBUG, ACTION.HTTP_REQ, null, msg);
|
|
96
|
+
|
|
97
|
+
const event = {
|
|
98
|
+
type: 'request',
|
|
99
|
+
level: LEVEL.DEBUG,
|
|
100
|
+
action: ACTION.HTTP_REQ,
|
|
101
|
+
method,
|
|
102
|
+
url: urlPath,
|
|
103
|
+
data,
|
|
104
|
+
message,
|
|
105
|
+
timestamp: new Date(),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
this.emit('request', event);
|
|
109
|
+
|
|
110
|
+
// Log request body as separate debug entry
|
|
111
|
+
if (data) {
|
|
112
|
+
const bodyJson = this._formatBody(data);
|
|
113
|
+
const fullBody = this._formatBody(data, Infinity); // Full body for copying
|
|
114
|
+
if (bodyJson) {
|
|
115
|
+
const bodyMsg = this._formatMessage(LEVEL.DEBUG, ACTION.HTTP_REQ, null, `body: ${bodyJson}`);
|
|
116
|
+
this.emit('request', {
|
|
117
|
+
...event,
|
|
118
|
+
message: bodyMsg,
|
|
119
|
+
fullMessage: `body: ${fullBody}`, // Store full message for copying
|
|
120
|
+
isBody: true,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Log an HTTP response
|
|
128
|
+
* @param {string} method - HTTP method
|
|
129
|
+
* @param {string} url - Request URL
|
|
130
|
+
* @param {number} status - HTTP status code
|
|
131
|
+
* @param {number} duration - Request duration in ms
|
|
132
|
+
* @param {Object} data - Response data (optional)
|
|
133
|
+
* @param {string} traceId - Trace ID from response headers (optional)
|
|
134
|
+
*/
|
|
135
|
+
logResponse(method, url, status, duration, data = null, traceId = null) {
|
|
136
|
+
if (!this.enabled) return;
|
|
137
|
+
|
|
138
|
+
const urlPath = this._extractPath(url);
|
|
139
|
+
const statusText = status >= 200 && status < 300 ? 'OK' : 'FAIL';
|
|
140
|
+
const level = status >= 400 ? LEVEL.WARN : LEVEL.INFO;
|
|
141
|
+
|
|
142
|
+
const msg = `${method} ${urlPath} ${status} ${statusText} ${duration}ms`;
|
|
143
|
+
const message = this._formatMessage(level, ACTION.HTTP_RES, traceId, msg);
|
|
144
|
+
|
|
145
|
+
const event = {
|
|
146
|
+
type: 'response',
|
|
147
|
+
level,
|
|
148
|
+
action: ACTION.HTTP_RES,
|
|
149
|
+
method,
|
|
150
|
+
url: urlPath,
|
|
151
|
+
status,
|
|
152
|
+
duration,
|
|
153
|
+
data,
|
|
154
|
+
traceId,
|
|
155
|
+
message,
|
|
156
|
+
timestamp: new Date(),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
this.emit('response', event);
|
|
160
|
+
|
|
161
|
+
// Log response body as separate debug entry
|
|
162
|
+
if (data) {
|
|
163
|
+
const bodyJson = this._formatBody(data);
|
|
164
|
+
const fullBody = this._formatBody(data, Infinity); // Full body for copying
|
|
165
|
+
if (bodyJson) {
|
|
166
|
+
const bodyMsg = this._formatMessage(LEVEL.DEBUG, ACTION.HTTP_RES, traceId, `body: ${bodyJson}`);
|
|
167
|
+
this.emit('response', {
|
|
168
|
+
...event,
|
|
169
|
+
level: LEVEL.DEBUG,
|
|
170
|
+
message: bodyMsg,
|
|
171
|
+
fullMessage: `body: ${fullBody}`, // Store full message for copying
|
|
172
|
+
isBody: true,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Log an HTTP error
|
|
180
|
+
* @param {string} method - HTTP method
|
|
181
|
+
* @param {string} url - Request URL
|
|
182
|
+
* @param {Error} error - Error object
|
|
183
|
+
* @param {number} duration - Request duration in ms
|
|
184
|
+
* @param {string} traceId - Trace ID from response headers (optional)
|
|
185
|
+
*/
|
|
186
|
+
logError(method, url, error, duration, traceId = null) {
|
|
187
|
+
if (!this.enabled) return;
|
|
188
|
+
|
|
189
|
+
const urlPath = this._extractPath(url);
|
|
190
|
+
// Safely extract error message - handle non-Error objects
|
|
191
|
+
let errorMessage;
|
|
192
|
+
if (error === null) {
|
|
193
|
+
errorMessage = 'null';
|
|
194
|
+
} else if (error === undefined) {
|
|
195
|
+
errorMessage = 'undefined';
|
|
196
|
+
} else if (error instanceof Error) {
|
|
197
|
+
errorMessage = error.message;
|
|
198
|
+
} else if (typeof error === 'object' && error.message) {
|
|
199
|
+
errorMessage = error.message;
|
|
200
|
+
} else {
|
|
201
|
+
errorMessage = String(error);
|
|
202
|
+
}
|
|
203
|
+
const msg = `${method} ${urlPath} ERROR: ${errorMessage} ${duration}ms`;
|
|
204
|
+
const message = this._formatMessage(LEVEL.ERROR, ACTION.HTTP_ERR, traceId, msg);
|
|
205
|
+
|
|
206
|
+
this.emit('error', {
|
|
207
|
+
type: 'error',
|
|
208
|
+
level: LEVEL.ERROR,
|
|
209
|
+
action: ACTION.HTTP_ERR,
|
|
210
|
+
method,
|
|
211
|
+
url: urlPath,
|
|
212
|
+
error: errorMessage,
|
|
213
|
+
duration,
|
|
214
|
+
traceId,
|
|
215
|
+
message,
|
|
216
|
+
timestamp: new Date(),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Log a generic message (for session, cmd, etc.)
|
|
222
|
+
* @param {string} level - Log level
|
|
223
|
+
* @param {string} action - Action type
|
|
224
|
+
* @param {string} msg - Message
|
|
225
|
+
* @param {string} traceId - Trace ID (optional)
|
|
226
|
+
*/
|
|
227
|
+
log(level, action, msg, traceId = null) {
|
|
228
|
+
if (!this.enabled) return;
|
|
229
|
+
|
|
230
|
+
const message = this._formatMessage(level, action, traceId, msg);
|
|
231
|
+
|
|
232
|
+
this.emit('log', {
|
|
233
|
+
type: 'log',
|
|
234
|
+
level,
|
|
235
|
+
action,
|
|
236
|
+
traceId,
|
|
237
|
+
message,
|
|
238
|
+
timestamp: new Date(),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Extract path from full URL
|
|
244
|
+
*/
|
|
245
|
+
_extractPath(url) {
|
|
246
|
+
try {
|
|
247
|
+
const urlObj = new URL(url);
|
|
248
|
+
return urlObj.pathname;
|
|
249
|
+
} catch {
|
|
250
|
+
// If URL parsing fails, try to extract path manually
|
|
251
|
+
const match = url.match(/\/apis\/.*$/);
|
|
252
|
+
return match ? match[0] : url;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Export constants
|
|
258
|
+
HttpLogger.ACTION = ACTION;
|
|
259
|
+
HttpLogger.LEVEL = LEVEL;
|
|
260
|
+
|
|
261
|
+
// Singleton instance
|
|
262
|
+
const httpLogger = new HttpLogger();
|
|
263
|
+
|
|
264
|
+
module.exports = { httpLogger, HttpLogger };
|
package/utils/i18n.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internationalization (i18n) utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detect system language
|
|
7
|
+
* @returns {string} Language code ('zh' for Chinese, 'en' for English)
|
|
8
|
+
*/
|
|
9
|
+
function detectLanguage() {
|
|
10
|
+
const lang = (
|
|
11
|
+
process.env.LC_ALL ||
|
|
12
|
+
process.env.LANG ||
|
|
13
|
+
process.env.LC_MESSAGES ||
|
|
14
|
+
'en'
|
|
15
|
+
).toLowerCase();
|
|
16
|
+
|
|
17
|
+
// Check for Chinese locale
|
|
18
|
+
if (lang.startsWith('zh')) {
|
|
19
|
+
return 'zh';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Default to English
|
|
23
|
+
return 'en';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Translations
|
|
28
|
+
*/
|
|
29
|
+
const translations = {
|
|
30
|
+
zh: {
|
|
31
|
+
// WelcomeBanner TIPS
|
|
32
|
+
'welcome.tip.commands': '使用 / 查看内置命令',
|
|
33
|
+
'welcome.tip.execute': '输入 shell 指令即可在远端执行',
|
|
34
|
+
'welcome.tip.console': '按 F12 打开调试控制台',
|
|
35
|
+
'welcome.tip.exit': '双按 Ctrl+C 退出',
|
|
36
|
+
|
|
37
|
+
// ConnectingScreen
|
|
38
|
+
'connecting.message': '🔄 正在连接沙箱...',
|
|
39
|
+
'connecting.attempt': '尝试 ${attempt}/${maxAttempts} 次...',
|
|
40
|
+
|
|
41
|
+
// replState welcome output
|
|
42
|
+
'welcome.connected': '已连接。准备就绪。',
|
|
43
|
+
|
|
44
|
+
// repl.js exit banner
|
|
45
|
+
'exit.session.duration': '✷ 会话持续时间',
|
|
46
|
+
'exit.new.session': '新建会话:',
|
|
47
|
+
'exit.resume.session': '恢复会话:',
|
|
48
|
+
|
|
49
|
+
// image mirror exit summary
|
|
50
|
+
'image.task.completed': '任务完成!',
|
|
51
|
+
'image.task.interrupted': '任务已中断。',
|
|
52
|
+
'image.task.exited': '任务已退出。',
|
|
53
|
+
'image.task.ended': '任务已结束。',
|
|
54
|
+
'image.task.error': '任务出错:',
|
|
55
|
+
'image.task.summary': '共处理 ${total} 个镜像,成功 ${completed} 个,跳过 ${skipped} 个,失败 ${failed} 个。',
|
|
56
|
+
'image.task.partial': '已完成 ${completed}/${total} 个镜像',
|
|
57
|
+
'image.task.failedCount': '(${failed} 个失败)',
|
|
58
|
+
'image.task.skippedCount': '(${skipped} 个跳过)',
|
|
59
|
+
'image.resume.command': '恢复命令:',
|
|
60
|
+
'image.log.saved': '日志已保存到: ${logDir}',
|
|
61
|
+
'image.password.placeholder': '<请填写密码>',
|
|
62
|
+
},
|
|
63
|
+
en: {
|
|
64
|
+
// WelcomeBanner TIPS
|
|
65
|
+
'welcome.tip.commands': 'Type / for available commands',
|
|
66
|
+
'welcome.tip.execute': 'Run shell commands remotely',
|
|
67
|
+
'welcome.tip.console': 'Press F12 to open Console',
|
|
68
|
+
'welcome.tip.exit': 'Double Ctrl+C to exit',
|
|
69
|
+
|
|
70
|
+
// ConnectingScreen
|
|
71
|
+
'connecting.message': '🔄 Connecting to sandbox...',
|
|
72
|
+
'connecting.attempt': 'Attempt ${attempt}/${maxAttempts}...',
|
|
73
|
+
|
|
74
|
+
// replState welcome output
|
|
75
|
+
'welcome.connected': 'Connected. Ready.',
|
|
76
|
+
|
|
77
|
+
// repl.js exit banner
|
|
78
|
+
'exit.session.duration': '✷ Session lasted',
|
|
79
|
+
'exit.new.session': 'New session:',
|
|
80
|
+
'exit.resume.session': 'Resume session:',
|
|
81
|
+
|
|
82
|
+
// image mirror exit summary
|
|
83
|
+
'image.task.completed': 'Task completed!',
|
|
84
|
+
'image.task.interrupted': 'Task interrupted.',
|
|
85
|
+
'image.task.exited': 'Task exited.',
|
|
86
|
+
'image.task.ended': 'Task ended.',
|
|
87
|
+
'image.task.error': 'Task error:',
|
|
88
|
+
'image.task.summary': 'Processed ${total} images: ${completed} succeeded, ${skipped} skipped, ${failed} failed.',
|
|
89
|
+
'image.task.partial': 'Completed ${completed}/${total} images',
|
|
90
|
+
'image.task.failedCount': ' (${failed} failed)',
|
|
91
|
+
'image.task.skippedCount': ' (${skipped} skipped)',
|
|
92
|
+
'image.resume.command': 'Resume command:',
|
|
93
|
+
'image.log.saved': 'Logs saved to: ${logDir}',
|
|
94
|
+
'image.password.placeholder': '<enter password>',
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get translation for a key
|
|
100
|
+
* @param {string} key - Translation key
|
|
101
|
+
* @param {string} [language] - Language code (optional, auto-detected if not provided)
|
|
102
|
+
* @param {Object} [vars] - Variables for interpolation (e.g., { total: 10, completed: 5 })
|
|
103
|
+
* @returns {string} Translated string with variables replaced
|
|
104
|
+
*/
|
|
105
|
+
function t(key, language = null, vars = null) {
|
|
106
|
+
const lang = language || detectLanguage();
|
|
107
|
+
const langTranslations = translations[lang] || translations.en;
|
|
108
|
+
let text = langTranslations[key] || translations.en[key] || key;
|
|
109
|
+
|
|
110
|
+
// Replace variables like ${varName}
|
|
111
|
+
if (vars && typeof vars === 'object') {
|
|
112
|
+
for (const [varName, value] of Object.entries(vars)) {
|
|
113
|
+
text = text.replace(new RegExp(`\\$\\{${varName}\\}`, 'g'), String(value));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return text;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get current language
|
|
122
|
+
* @returns {string} Current language code
|
|
123
|
+
*/
|
|
124
|
+
function getCurrentLanguage() {
|
|
125
|
+
return detectLanguage();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Export for both CommonJS and ES Module compatibility
|
|
129
|
+
const i18nExports = {
|
|
130
|
+
detectLanguage,
|
|
131
|
+
t,
|
|
132
|
+
getCurrentLanguage,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// CommonJS export
|
|
136
|
+
module.exports = i18nExports;
|
|
137
|
+
|
|
138
|
+
// ES Module default export (for Ink components)
|
|
139
|
+
module.exports.default = i18nExports;
|