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.
- package/index.js +15 -5
- package/package.json +2 -2
- package/commands/log/core/constants.js +0 -237
- package/commands/log/core/display.js +0 -370
- package/commands/log/core/search.js +0 -330
- package/commands/log/core/tail.js +0 -216
- package/commands/log/core/utils.js +0 -424
- package/commands/log.js +0 -298
- package/commands/sandbox/core/log-bridge.js +0 -119
- package/commands/sandbox/core/replay/analyzer.js +0 -311
- package/commands/sandbox/core/replay/batch-orchestrator.js +0 -536
- package/commands/sandbox/core/replay/batch-task.js +0 -369
- package/commands/sandbox/core/replay/concurrent-display.js +0 -70
- package/commands/sandbox/core/replay/concurrent-orchestrator.js +0 -170
- package/commands/sandbox/core/replay/data-source.js +0 -86
- package/commands/sandbox/core/replay/display.js +0 -231
- package/commands/sandbox/core/replay/executor.js +0 -634
- package/commands/sandbox/core/replay/history-fetcher.js +0 -124
- package/commands/sandbox/core/replay/index.js +0 -338
- package/commands/sandbox/core/replay/loghouse-data-source.js +0 -177
- package/commands/sandbox/core/replay/pid-mapping.js +0 -26
- package/commands/sandbox/core/replay/request.js +0 -109
- package/commands/sandbox/core/replay/worker.js +0 -166
- package/commands/sandbox/core/session.js +0 -346
- package/commands/sandbox/log-bridge.js +0 -2
- package/commands/sandbox/ray.js +0 -2
- package/commands/sandbox/replay/analyzer.js +0 -311
- package/commands/sandbox/replay/batch-orchestrator.js +0 -536
- package/commands/sandbox/replay/batch-task.js +0 -369
- package/commands/sandbox/replay/concurrent-display.js +0 -70
- package/commands/sandbox/replay/concurrent-orchestrator.js +0 -170
- package/commands/sandbox/replay/display.js +0 -231
- package/commands/sandbox/replay/executor.js +0 -634
- package/commands/sandbox/replay/history-fetcher.js +0 -118
- package/commands/sandbox/replay/index.js +0 -338
- package/commands/sandbox/replay/pid-mapping.js +0 -26
- package/commands/sandbox/replay/request.js +0 -109
- package/commands/sandbox/replay/worker.js +0 -166
- package/commands/sandbox/replay.js +0 -2
- package/commands/sandbox/session.js +0 -2
- package/commands/sandbox-original.js +0 -1393
- package/commands/sandbox.js +0 -499
- package/help/help.json +0 -1071
- package/help/middleware.js +0 -71
- package/help/renderer.js +0 -800
- package/lib/plugin-context.js +0 -40
- package/sdks/sandbox/core/client.js +0 -845
- package/sdks/sandbox/core/config.js +0 -70
- package/sdks/sandbox/core/types.js +0 -74
- package/sdks/sandbox/httpLogger.js +0 -251
- package/sdks/sandbox/index.js +0 -9
- package/utils/asciiArt.js +0 -138
- package/utils/bun-compat.js +0 -59
- package/utils/ciPipelines.js +0 -138
- package/utils/cli.js +0 -17
- package/utils/command-router.js +0 -79
- package/utils/configManager.js +0 -503
- package/utils/dependency-resolver.js +0 -135
- package/utils/eagleeye_traceid.js +0 -151
- package/utils/envDetector.js +0 -78
- package/utils/execution_logger.js +0 -415
- package/utils/featureManager.js +0 -68
- package/utils/firstTimeTip.js +0 -44
- package/utils/hook-manager.js +0 -125
- package/utils/http-logger.js +0 -264
- package/utils/i18n.js +0 -139
- package/utils/image-progress.js +0 -159
- package/utils/logger.js +0 -154
- package/utils/plugin-loader.js +0 -124
- package/utils/plugin-manager.js +0 -348
- package/utils/ray_cli_wrapper.js +0 -746
- package/utils/sandbox-client.js +0 -419
- package/utils/terminal.js +0 -32
- package/utils/tips.js +0 -106
|
@@ -1,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
|
-
};
|
package/utils/envDetector.js
DELETED
|
@@ -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
|
-
};
|
package/utils/featureManager.js
DELETED
|
@@ -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
|
-
};
|