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,151 +0,0 @@
1
- /**
2
- * EagleEye TraceID 生成器
3
- *
4
- * TraceID 格式(共30位):
5
- * - IPv4 (8位十六进制): 本地IP对应的十六进制数
6
- * - 毫秒时间 (13位): 当前时间戳(毫秒)
7
- * - 顺序号 (4位): 自增计数器,范围 1000~9999
8
- * - 标志位 (1位): 随机生成,范围 a~f 或 0~9
9
- * - PID (4位): 进程ID的十六进制表示
10
- *
11
- * 示例: 0ab5847914463715406577630e0f56
12
- */
13
-
14
- const os = require('os');
15
- const process = require('process');
16
-
17
- // 顺序号计数器(范围 1000~9999)
18
- let sequenceCounter = 1000;
19
-
20
- /**
21
- * 获取本地 IPv4 地址并转换为8位十六进制字符串
22
- * @returns {string} 8位十六进制字符串
23
- */
24
- function getLocalIpHex() {
25
- const interfaces = os.networkInterfaces();
26
-
27
- for (const name of Object.keys(interfaces)) {
28
- for (const iface of interfaces[name]) {
29
- // 跳过内部地址和非IPv4地址
30
- if (iface.family === 'IPv4' && !iface.internal) {
31
- const parts = iface.address.split('.');
32
- if (parts.length === 4) {
33
- // 将IP的4个部分转换为2位十六进制并拼接
34
- return parts
35
- .map(part => parseInt(part, 10).toString(16).padStart(2, '0'))
36
- .join('')
37
- .toLowerCase();
38
- }
39
- }
40
- }
41
- }
42
-
43
- // 如果没有找到有效IP,使用回环地址 127.0.0.1
44
- return '7f000001';
45
- }
46
-
47
- /**
48
- * 获取当前毫秒时间戳(13位)
49
- * @returns {string} 13位时间戳字符串
50
- */
51
- function getTimestampMillis() {
52
- return Date.now().toString();
53
- }
54
-
55
- /**
56
- * 获取下一个顺序号(4位,范围 1000~9999)
57
- * @returns {string} 4位顺序号字符串
58
- */
59
- function getSequenceNumber() {
60
- const seq = sequenceCounter;
61
- // 自增并在达到9999时回绕到1000
62
- sequenceCounter = sequenceCounter >= 9999 ? 1000 : sequenceCounter + 1;
63
- return seq.toString();
64
- }
65
-
66
- /**
67
- * 生成随机标志位(1位,范围 a~f 或 0~9)
68
- * @returns {string} 1位十六进制字符
69
- */
70
- function generateFlag() {
71
- const chars = '0123456789abcdef';
72
- return chars[Math.floor(Math.random() * chars.length)];
73
- }
74
-
75
- /**
76
- * 获取当前进程ID的4位十六进制表示
77
- * @returns {string} 4位十六进制PID字符串
78
- */
79
- function getPidHex() {
80
- return process.pid.toString(16).padStart(4, '0').toLowerCase();
81
- }
82
-
83
- /**
84
- * 生成 EagleEye TraceID
85
- *
86
- * 格式: [IPv4(8)][毫秒时间(13)][顺序号(4)][标志位(1)][PID(4)]
87
- * 总长度: 30位十六进制字符
88
- *
89
- * @returns {string} 30位 EagleEye TraceID
90
- */
91
- function generateEagleEyeTraceId() {
92
- const ipHex = getLocalIpHex(); // 8位
93
- const timestamp = getTimestampMillis(); // 13位
94
- const sequence = getSequenceNumber(); // 4位
95
- const flag = generateFlag(); // 1位
96
- const pidHex = getPidHex(); // 4位
97
-
98
- return `${ipHex}${timestamp}${sequence}${flag}${pidHex}`;
99
- }
100
-
101
- /**
102
- * 生成 EagleEye RpcID
103
- *
104
- * RpcID 用于区别同一个调用链下多个网络调用的顺序和嵌套层次
105
- * 格式: 0.X1.X2.X3...Xi,Xi 都是非负整数
106
- * 根节点的 RpcId 固定从 0 开始
107
- *
108
- * @param {string} parentRpcId - 父级 RpcID(可选,默认为 '0')
109
- * @param {number} index - 当前层级索引(可选,默认为 0)
110
- * @returns {string} RpcID
111
- */
112
- function generateRpcId(parentRpcId = '0', index = 0) {
113
- return `${parentRpcId}.${index}`;
114
- }
115
-
116
- /**
117
- * 创建 HTTP 请求头对象,包含 EagleEye TraceID 和 RpcID
118
- *
119
- * @param {Object} options - 配置选项
120
- * @param {string} options.parentRpcId - 父级 RpcID(用于嵌套调用)
121
- * @param {number} options.rpcIndex - 当前 RPC 调用索引
122
- * @param {Object} options.extraHeaders - 额外的请求头
123
- * @returns {Object} HTTP 请求头对象
124
- */
125
- function createEagleEyeHeaders(options = {}) {
126
- const {
127
- parentRpcId = '0',
128
- rpcIndex = 0,
129
- extraHeaders = {}
130
- } = options;
131
-
132
- const traceId = generateEagleEyeTraceId();
133
- const rpcId = generateRpcId(parentRpcId, rpcIndex);
134
-
135
- return {
136
- 'EagleEye-TraceId': traceId,
137
- 'EagleEye-RpcId': rpcId,
138
- ...extraHeaders
139
- };
140
- }
141
-
142
- module.exports = {
143
- generateEagleEyeTraceId,
144
- generateRpcId,
145
- createEagleEyeHeaders,
146
- // 内部方法导出(用于测试)
147
- _getLocalIpHex: getLocalIpHex,
148
- _getSequenceNumber: getSequenceNumber,
149
- _generateFlag: generateFlag,
150
- _getPidHex: getPidHex
151
- };
@@ -1,78 +0,0 @@
1
- const path = require('path');
2
- const fs = require('fs');
3
-
4
- /**
5
- * 环境检测模块
6
- * 负责判断当前 CLI 是运行在开发模式还是生产模式
7
- *
8
- * 开发模式:开发者在本地通过 `node <entry-file>` 运行
9
- * 生产模式:用户通过 npm 全局安装后,通过 `rock-cli` 命令运行
10
- */
11
-
12
- /**
13
- * 检测是否处于开发模式
14
- *
15
- * 判断逻辑:
16
- * 1. 通过环境变量 ROCK_CLI_DEV_MODE 强制指定(便于测试和特殊场景)
17
- * 2. 检查是否从 node_modules 路径运行(npm 安装的包)
18
- * 3. 检查脚本文件路径是否在 rock-cli 项目目录下(开发环境特征)
19
- *
20
- * @returns {boolean} 如果处于开发模式返回 true,生产模式返回 false
21
- */
22
- function isDevMode() {
23
- // 1. 环境变量强制指定(最高优先级)
24
- const envFlag = process.env.ROCK_CLI_DEV_MODE;
25
- if (envFlag !== undefined) {
26
- const normalized = envFlag.toLowerCase();
27
- return normalized === '1' || normalized === 'true' || normalized === 'yes';
28
- }
29
-
30
- // 2. 如果从 npm 全局安装路径运行,肯定是生产模式
31
- const scriptPath = process.argv[1] || '';
32
- if (scriptPath.includes('node_modules')) {
33
- return false;
34
- }
35
-
36
- // 3. 检查脚本文件路径是否在 rock-cli 项目目录下
37
- // 注意:这里检查的是脚本路径,而不是当前工作目录(cwd)
38
- // 这样可以避免用户在项目目录下运行已安装的 rock-cli 时被误判为开发模式
39
- const scriptDir = path.dirname(scriptPath);
40
- const scriptName = path.basename(scriptPath);
41
-
42
- // 常见的 CLI 入口文件名
43
- const commonEntryFiles = ['all.js', 'index.js', 'cli.js', 'main.js'];
44
-
45
- if (commonEntryFiles.includes(scriptName)) {
46
- // 检查同目录是否有 package.json,且名称为 @ali/rockcli
47
- try {
48
- const packageJsonPath = path.join(scriptDir, 'package.json');
49
- if (fs.existsSync(packageJsonPath)) {
50
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
51
- if (packageJson.name === '@ali/rockcli') {
52
- return true;
53
- }
54
- }
55
- } catch (e) {
56
- // 读取失败,继续
57
- }
58
- }
59
-
60
- // 4. 默认为生产模式(保守策略)
61
- return false;
62
- }
63
-
64
- /**
65
- * 获取环境模式的描述信息(用于日志和调试)
66
- * @returns {string} 环境模式描述
67
- */
68
- function getEnvModeDescription() {
69
- if (isDevMode()) {
70
- return 'development';
71
- }
72
- return 'production';
73
- }
74
-
75
- module.exports = {
76
- isDevMode,
77
- getEnvModeDescription,
78
- };
@@ -1,415 +0,0 @@
1
- const fs = require('fs-extra');
2
- const os = require('os');
3
- const path = require('path');
4
- const crypto = require('crypto');
5
-
6
- const configManager = require('./configManager');
7
- const pkg = require('../package.json');
8
- const { isDevMode } = require('./envDetector');
9
- const HookManager = require('./hook-manager');
10
- const { createPluginContext } = require('../lib/plugin-context');
11
-
12
- /**
13
- * @typedef {Object} ExecutionContext
14
- * @property {boolean} initialized
15
- * @property {boolean} logged
16
- * @property {string | undefined} traceId
17
- * @property {string | undefined} userApiKey
18
- * @property {string | undefined} command
19
- * @property {string[] | undefined} args
20
- * @property {string} rockCliVersion
21
- * @property {string} osInfo
22
- * @property {Date | undefined} startTime
23
- * @property {string | undefined} errorMessage
24
- * @property {string | undefined} errorStack
25
- */
26
-
27
- // 全局执行上下文,用于在进程生命周期内累积信息
28
- /** @type {ExecutionContext} */
29
- const executionContext = {
30
- initialized: false,
31
- logged: false,
32
- traceId: undefined,
33
- userApiKey: undefined,
34
- command: undefined,
35
- args: undefined,
36
- rockCliVersion: pkg.version,
37
- osInfo: `${os.platform()} ${os.release()} ${os.arch()}`,
38
- startTime: undefined,
39
- errorMessage: undefined,
40
- errorStack: undefined
41
- };
42
-
43
- /**
44
- * 生成符合 OpenTelemetry 规范的 trace id(16 字节 / 32 位十六进制字符串)
45
- */
46
- function generateTraceId() {
47
- return crypto.randomBytes(16).toString('hex');
48
- }
49
-
50
- // 缓存设备 ID,在同一进程内保持一致
51
- let cachedDeviceID = null;
52
-
53
- const DEVICE_ID_FILE = path.join(os.homedir(), '.rock', 'device_id');
54
-
55
- /**
56
- * 从本地文件读取已生成的设备 ID
57
- */
58
- function loadDeviceIdFromFile() {
59
- try {
60
- if (fs.existsSync(DEVICE_ID_FILE)) {
61
- const content = fs.readFileSync(DEVICE_ID_FILE, 'utf8').trim();
62
- if (content) {
63
- return content;
64
- }
65
- }
66
- } catch (e) {
67
- // 读取失败时忽略,后续重新生成
68
- }
69
- return null;
70
- }
71
-
72
- /**
73
- * 将设备 ID 持久化到本地文件
74
- */
75
- function saveDeviceIdToFile(deviceId) {
76
- try {
77
- const dir = path.dirname(DEVICE_ID_FILE);
78
- fs.ensureDirSync(dir);
79
- fs.writeFileSync(DEVICE_ID_FILE, deviceId, 'utf8');
80
- } catch (e) {
81
- // 写入失败不影响命令执行
82
- }
83
- }
84
-
85
- /**
86
- * 获取本机设备 ID(基于 MAC 地址的不可逆哈希)
87
- */
88
- function getDeviceID() {
89
- if (cachedDeviceID) {
90
- return cachedDeviceID;
91
- }
92
-
93
- const cachedFromFile = loadDeviceIdFromFile();
94
- if (cachedFromFile) {
95
- cachedDeviceID = cachedFromFile;
96
- return cachedDeviceID;
97
- }
98
-
99
- let rawIdentifier = null;
100
-
101
- try {
102
- const networkInterfaces = os.networkInterfaces();
103
- for (const name of Object.keys(networkInterfaces)) {
104
- const interfaces = networkInterfaces[name];
105
- if (!interfaces) continue;
106
- for (const iface of interfaces) {
107
- // 跳过内部接口和虚拟接口
108
- if (!iface.internal && iface.mac && iface.mac !== '00:00:00:00:00:00') {
109
- rawIdentifier = iface.mac.toLowerCase();
110
- break;
111
- }
112
- }
113
- if (rawIdentifier) {
114
- break;
115
- }
116
- }
117
- } catch (e) {
118
- // 忽略获取失败,使用主机名作为备用标识
119
- }
120
-
121
- if (!rawIdentifier) {
122
- // 如果没有找到 MAC 地址,使用主机名作为固定标识
123
- rawIdentifier = os.hostname();
124
- }
125
-
126
- const hash = crypto.createHash('sha256').update(rawIdentifier).digest('hex');
127
- cachedDeviceID = hash.substring(0, 32);
128
-
129
- saveDeviceIdToFile(cachedDeviceID);
130
-
131
- return cachedDeviceID;
132
- }
133
-
134
- /**
135
- * 获取本地 IP 地址
136
- */
137
- function getLocalIP() {
138
- try {
139
- const networkInterfaces = os.networkInterfaces();
140
- for (const name of Object.keys(networkInterfaces)) {
141
- const interfaces = networkInterfaces[name];
142
- if (!interfaces) continue;
143
- for (const iface of interfaces) {
144
- // 跳过内部接口,优先返回 IPv4
145
- if (!iface.internal && iface.family === 'IPv4') {
146
- return iface.address;
147
- }
148
- }
149
- }
150
- // 如果没有 IPv4,尝试返回 IPv6
151
- for (const name of Object.keys(networkInterfaces)) {
152
- const interfaces = networkInterfaces[name];
153
- if (!interfaces) continue;
154
- for (const iface of interfaces) {
155
- if (!iface.internal && iface.family === 'IPv6') {
156
- return iface.address;
157
- }
158
- }
159
- }
160
- } catch (e) {
161
- // 如果获取失败,返回回环地址
162
- }
163
- return '127.0.0.1';
164
- }
165
-
166
- /**
167
- * 获取执行日志文件路径
168
- * 默认:~/.rock/logs/rock-cli-YYYY-MM-DD.log(每天一个文件)
169
- * 支持通过环境变量 ROCK_CLI_LOG_FILE 覆盖
170
- */
171
- function getLogFilePath() {
172
- if (process.env.ROCK_CLI_LOG_FILE) {
173
- return process.env.ROCK_CLI_LOG_FILE;
174
- }
175
-
176
- // 生成基于当前日期的文件名
177
- const today = new Date();
178
- const year = today.getFullYear();
179
- const month = String(today.getMonth() + 1).padStart(2, '0');
180
- const day = String(today.getDate()).padStart(2, '0');
181
- const dateStr = `${year}-${month}-${day}`;
182
-
183
- const dir = path.join(os.homedir(), '.rock', 'logs');
184
- return path.join(dir, `rock-cli-${dateStr}.log`);
185
- }
186
-
187
- /**
188
- * 追加写入一条 JSON 日志(每条一行)
189
- */
190
- function appendLogEntry(entry) {
191
- try {
192
- const logFile = getLogFilePath();
193
- const logDir = path.dirname(logFile);
194
- fs.ensureDirSync(logDir);
195
- const line = JSON.stringify(entry);
196
- fs.appendFileSync(logFile, line + '\n', 'utf8');
197
- } catch (e) {
198
- // 日志写入失败不应影响正常命令执行
199
- // 这里静默失败,避免循环错误
200
- }
201
- }
202
-
203
- /**
204
- * 初始化当前进程的执行上下文
205
- */
206
- function initExecutionContext() {
207
- if (executionContext.initialized) {
208
- return;
209
- }
210
-
211
- executionContext.initialized = true;
212
- executionContext.startTime = new Date();
213
- executionContext.traceId = generateTraceId();
214
-
215
- // 完整命令行参数(不包含 node 与脚本路径)
216
- executionContext.args = process.argv.slice(2);
217
- executionContext.command = executionContext.args.join(' ');
218
-
219
- // 尝试从 ~/.rock/settings.json 读取 api_key 作为用户标识
220
- try {
221
- executionContext.userApiKey = configManager.getSandboxConfig('api_key') || 'unknown';
222
- } catch (e) {
223
- executionContext.userApiKey = 'unknown';
224
- }
225
- }
226
-
227
- /**
228
- * 根据当前执行上下文构造标准日志结构
229
- */
230
- function buildLogEntry(success, exitCode) {
231
- const endTime = new Date();
232
- const durationMs = executionContext.startTime
233
- ? endTime.getTime() - executionContext.startTime.getTime()
234
- : null;
235
-
236
- let errorMessage = executionContext.errorMessage;
237
- if (!success && !errorMessage) {
238
- errorMessage = typeof exitCode === 'number'
239
- ? `Process exited with code ${exitCode}`
240
- : 'Command execution failed';
241
- }
242
-
243
- return {
244
- event_type: 'cli_execution',
245
- event_version: '1.0',
246
- trace_id: executionContext.traceId,
247
- user_api_key: executionContext.userApiKey,
248
- command: executionContext.command,
249
- args: executionContext.args,
250
- success,
251
- timestamp: endTime.toISOString(),
252
- node_version: process.version,
253
- os_info: executionContext.osInfo,
254
- rock_cli_version: executionContext.rockCliVersion,
255
- local_ip: getLocalIP(),
256
- device_id: getDeviceID(),
257
- error_message: errorMessage,
258
- error_stack: executionContext.errorStack || null,
259
- duration_ms: durationMs
260
- };
261
- }
262
-
263
- /**
264
- * 记录错误信息(供 yargs fail 及业务代码显式调用)
265
- */
266
- function recordError(error, message) {
267
- if (message && !executionContext.errorMessage) {
268
- executionContext.errorMessage = message;
269
- }
270
- if (error) {
271
- if (error.message && !executionContext.errorMessage) {
272
- executionContext.errorMessage = error.message;
273
- }
274
- if (error.stack) {
275
- executionContext.errorStack = error.stack;
276
- }
277
- }
278
- }
279
-
280
- /**
281
- * 判断是否禁用本地日志记录
282
- */
283
- function isLocalLoggingDisabled() {
284
- const flag = process.env.ROCK_CLI_DISABLE_LOCAL_LOGGING;
285
- if (!flag) {
286
- return false;
287
- }
288
- const normalized = flag.toLowerCase();
289
- return normalized === '1' || normalized === 'true' || normalized === 'yes';
290
- }
291
-
292
- /**
293
- * 注册全局执行日志采集:
294
- * - 进程退出时输出一条执行日志
295
- * - 捕获未处理异常和 Promise 拒绝
296
- */
297
- function setupGlobalExecutionLogging() {
298
- if (executionContext.initialized) {
299
- return;
300
- }
301
-
302
- if (isLocalLoggingDisabled()) {
303
- executionContext.initialized = true;
304
- return;
305
- }
306
-
307
- initExecutionContext();
308
-
309
- // 捕获未处理的异常
310
- process.on('uncaughtException', (err) => {
311
- recordError(err);
312
- // 标记非正常退出,但避免在这里写日志,统一在 exit 事件中写
313
- process.exitCode = process.exitCode || 1;
314
- });
315
-
316
- // 捕获未处理的 Promise 拒绝
317
- process.on('unhandledRejection', (reason) => {
318
- const error = reason instanceof Error ? reason : new Error(String(reason));
319
- recordError(error);
320
- process.exitCode = process.exitCode || 1;
321
- });
322
-
323
- // 使用 beforeExit 进行日志记录
324
- // beforeExit 在事件循环清空但进程还未退出时触发,可以执行异步操作
325
- process.on('beforeExit', async (code) => {
326
- if (executionContext.logged) {
327
- return;
328
- }
329
- executionContext.logged = true;
330
-
331
- const success = code === 0;
332
- const entry = buildLogEntry(success, code);
333
-
334
- // 1. 先写入本地日志文件(同步,确保数据落盘)
335
- appendLogEntry(entry);
336
-
337
- // 2. 触发 pre-exit 钩子(传递 executionContext 供插件使用)
338
- // 注意:此时本地日志已写入,插件可以安全执行远程上报
339
- try {
340
- const hookManager = HookManager.getInstance();
341
- if (hookManager) {
342
- const exitContext = createPluginContext({
343
- reason: code === 0 ? 'normal' : 'error',
344
- error: code !== 0 ? new Error(`Exit code: ${code}`) : undefined,
345
- executionContext: entry
346
- });
347
- await hookManager.executeHooks('pre-exit', exitContext);
348
- }
349
- } catch (error) {
350
- // 钩子执行失败不影响退出
351
- }
352
-
353
- process.exit(code);
354
- });
355
-
356
- // exit 事件作为兜底(如果 beforeExit 未触发)
357
- process.on('exit', (code) => {
358
- if (!executionContext.logged) {
359
- executionContext.logged = true;
360
- const success = code === 0;
361
- const entry = buildLogEntry(success, code);
362
- appendLogEntry(entry);
363
- }
364
- });
365
- }
366
-
367
- /**
368
- * 优雅退出函数 - 确保在退出前完成日志记录
369
- * @param {number} code - 退出码
370
- */
371
- async function gracefulExit(code = 0) {
372
- if (executionContext.logged) {
373
- process.exit(code);
374
- return;
375
- }
376
-
377
- executionContext.logged = true;
378
- const success = code === 0;
379
- const entry = buildLogEntry(success, code);
380
-
381
- // 1. 先写入本地日志文件(同步,确保数据落盘)
382
- appendLogEntry(entry);
383
-
384
- // 2. 触发 pre-exit 钩子(传递 executionContext 供插件使用)
385
- // 注意:此时本地日志已写入,插件可以安全执行远程上报
386
- try {
387
- const hookManager = HookManager.getInstance();
388
- if (hookManager) {
389
- const exitContext = createPluginContext({
390
- reason: code === 0 ? 'normal' : 'error',
391
- error: code !== 0 ? new Error(`Exit code: ${code}`) : undefined,
392
- executionContext: entry
393
- });
394
- await hookManager.executeHooks('pre-exit', exitContext);
395
- }
396
- } catch (error) {
397
- // 钩子执行失败不影响退出
398
- console.error('Failed to execute pre-exit hooks:', error.message);
399
- }
400
-
401
- process.exit(code);
402
- }
403
-
404
- module.exports = {
405
- setupGlobalExecutionLogging,
406
- recordError,
407
- gracefulExit,
408
- generateTraceId,
409
- getLogFilePath,
410
- appendLogEntry,
411
- buildLogEntry,
412
- getLocalIP,
413
- getDeviceID,
414
- isLocalLoggingDisabled
415
- };
@@ -1,68 +0,0 @@
1
- /**
2
- * Feature Flags 管理器
3
- * 根据 ~/.rock/features.json 配置文件控制命令的可见性
4
- */
5
-
6
- const fs = require('fs-extra');
7
- const path = require('path');
8
- const os = require('os');
9
-
10
- const FEATURES_PATH = path.join(os.homedir(), '.rock', 'features.json');
11
-
12
- // 需要 feature flag 控制的命令
13
- const FEATURE_CONTROLLED_COMMANDS = ['test', 'ray', 'config', 'image', 'log'];
14
-
15
- /**
16
- * 读取 features.json 配置
17
- * @returns {Object} 配置对象,如果文件不存在或读取失败则返回空对象
18
- */
19
- function readFeatures() {
20
- try {
21
- if (fs.existsSync(FEATURES_PATH)) {
22
- return fs.readJsonSync(FEATURES_PATH);
23
- }
24
- } catch (e) {
25
- // 忽略读取错误
26
- }
27
- return {};
28
- }
29
-
30
- /**
31
- * 检查命令是否启用(在帮助中可见)
32
- * @param {string} command - 命令名称
33
- * @returns {boolean} 如果命令启用则返回 true
34
- */
35
- function isCommandEnabled(command) {
36
- if (!FEATURE_CONTROLLED_COMMANDS.includes(command)) {
37
- return true; // 不受控制的命令始终可见
38
- }
39
- const features = readFeatures();
40
- return features[command] === true;
41
- }
42
-
43
- /**
44
- * 保存 features.json 配置
45
- * @param {Object} features - 配置对象
46
- */
47
- function saveFeatures(features) {
48
- const dir = path.dirname(FEATURES_PATH);
49
- fs.ensureDirSync(dir);
50
- fs.writeJsonSync(FEATURES_PATH, features, { spaces: 2 });
51
- }
52
-
53
- /**
54
- * 检查 features.json 文件是否存在
55
- * @returns {boolean}
56
- */
57
- function featuresFileExists() {
58
- return fs.existsSync(FEATURES_PATH);
59
- }
60
-
61
- module.exports = {
62
- readFeatures,
63
- isCommandEnabled,
64
- saveFeatures,
65
- featuresFileExists,
66
- FEATURES_PATH,
67
- FEATURE_CONTROLLED_COMMANDS
68
- };