bfi-cli 0.1.73 → 0.1.75
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/dist/cli.mjs +371 -10
- package/package.json +4 -3
package/dist/cli.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { require_picocolors } from "./_chunks/libs/picocolors.mjs";
|
|
|
6
6
|
import { addSkillToLocalLock, addSkillToLock, computeSkillFolderHash, discoverSkills, dismissPrompt, filterSkills, getAllLockedSkills, getGitHubToken, getLastSelectedAgents, getSkillDisplayName, getSkillFromLock, isPromptDismissed, parseFrontmatter, parseSkillMd, readLocalLock, readSkillLock, removeSkillFromLock, sanitizeMetadata, saveSelectedAgents, stripTerminalEscapes } from "./_chunks/skills.mjs";
|
|
7
7
|
import { esm_default } from "./_chunks/libs/simple-git.mjs";
|
|
8
8
|
import { EVE_SUBAGENTS_DIR, agents, detectInstalledAgents, getEveSubagents, getNonUniversalAgents, getUniversalAgents, getVisibleUniversalAgents, isUniversalAgent } from "./_chunks/agents.mjs";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
9
10
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
|
|
10
11
|
import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
|
|
11
12
|
import { fileURLToPath } from "url";
|
|
@@ -8058,6 +8059,7 @@ var YmcApi = class {
|
|
|
8058
8059
|
if (Object.keys(body).length > 0) console.log(JSON.stringify(body, null, 2));
|
|
8059
8060
|
process.exit(0);
|
|
8060
8061
|
}
|
|
8062
|
+
if (!this.token) throw new Error("未配置 API token:请 `bfi auth login --token <token>` 或 export BFI_TOKEN");
|
|
8061
8063
|
const url = `${this.baseUrl}/${path.replace(/^\//, "")}`;
|
|
8062
8064
|
const payload = JSON.stringify(body);
|
|
8063
8065
|
const mod = url.startsWith("https://") ? https : http;
|
|
@@ -8159,27 +8161,58 @@ function emit(result, jsonMode, summarize) {
|
|
|
8159
8161
|
function raw0(text) {
|
|
8160
8162
|
return text || "{}";
|
|
8161
8163
|
}
|
|
8162
|
-
const ENV_DOC = "环境: test/daily/pre/core1
|
|
8164
|
+
const ENV_DOC = "环境: test/daily/pre/core1/core2/core3/core4/commldev";
|
|
8165
|
+
const VALID_ENVS = [
|
|
8166
|
+
"test",
|
|
8167
|
+
"daily",
|
|
8168
|
+
"pre",
|
|
8169
|
+
"core1",
|
|
8170
|
+
"core2",
|
|
8171
|
+
"core3",
|
|
8172
|
+
"core4",
|
|
8173
|
+
"commldev"
|
|
8174
|
+
];
|
|
8175
|
+
const TIME_RE = /^\d+(m|h|d|w)$/;
|
|
8176
|
+
function envError(env) {
|
|
8177
|
+
if (!env || !env.trim()) return `缺少环境参数:-e <${ENV_DOC}>`;
|
|
8178
|
+
if (!VALID_ENVS.includes(env.toLowerCase())) return `环境参数无效:收到 '${env}'\n合法值:${VALID_ENVS.join(" | ")}(若为新环境请升级 bfi CLI)\n例:bfi ymc slowsql -t 1d --app yonbip-scm-scmmp -e core3`;
|
|
8179
|
+
return null;
|
|
8180
|
+
}
|
|
8181
|
+
function timeError(t) {
|
|
8182
|
+
if (!t || !t.trim()) return `缺少时间参数:-t <简写>(如 1h/3d/10m/4w,表示过去 N 单位到现在)\n精确窗口:--start-time "yyyy-MM-dd HH:mm:ss" --end-time 同格式(北京时间)`;
|
|
8183
|
+
if (!TIME_RE.test(t)) return `时间简写无效:收到 '${t}'\n合法格式:<N><单位>,单位 m(分)/h(时)/d(天)/w(周),如 10m/2h/3d/4w\n精确窗口:--start-time "yyyy-MM-dd HH:mm:ss" --end-time 同格式(北京时间)`;
|
|
8184
|
+
return null;
|
|
8185
|
+
}
|
|
8163
8186
|
function requireEnv(args) {
|
|
8164
8187
|
const env = opt(args, "-e", "--env", "--env-name");
|
|
8165
|
-
|
|
8166
|
-
|
|
8188
|
+
const err = envError(env);
|
|
8189
|
+
if (err) {
|
|
8190
|
+
console.error(err);
|
|
8167
8191
|
process.exit(1);
|
|
8168
8192
|
}
|
|
8169
8193
|
return { env_name: env };
|
|
8170
8194
|
}
|
|
8171
8195
|
function timeOf(args, required = true) {
|
|
8172
8196
|
const t = opt(args, "-t", "--time");
|
|
8173
|
-
|
|
8174
|
-
|
|
8197
|
+
const err = timeError(t);
|
|
8198
|
+
if (err && required) {
|
|
8199
|
+
console.error(err);
|
|
8175
8200
|
process.exit(1);
|
|
8176
8201
|
}
|
|
8202
|
+
if (t && !err) validateTime(t);
|
|
8177
8203
|
return t ? { time: t } : {};
|
|
8178
8204
|
}
|
|
8179
8205
|
function timeOpts(args) {
|
|
8180
8206
|
const out = {};
|
|
8181
8207
|
const t = opt(args, "-t", "--time");
|
|
8182
|
-
if (t)
|
|
8208
|
+
if (t) {
|
|
8209
|
+
const err = timeError(t);
|
|
8210
|
+
if (err) {
|
|
8211
|
+
console.error(err);
|
|
8212
|
+
process.exit(1);
|
|
8213
|
+
}
|
|
8214
|
+
out.time = t;
|
|
8215
|
+
}
|
|
8183
8216
|
const st = opt(args, "--start-time");
|
|
8184
8217
|
if (st) out.start_time = st;
|
|
8185
8218
|
const et = opt(args, "--end-time");
|
|
@@ -8207,7 +8240,7 @@ async function runYmc(args, globalOpts) {
|
|
|
8207
8240
|
const token = globalOpts.token;
|
|
8208
8241
|
const api = new YmcApi(token ?? null, void 0, dryRun);
|
|
8209
8242
|
if (!sub || sub === "--help" || sub === "-h") {
|
|
8210
|
-
printHelp();
|
|
8243
|
+
printHelp$1();
|
|
8211
8244
|
return;
|
|
8212
8245
|
}
|
|
8213
8246
|
if (sub === "help") {
|
|
@@ -8725,6 +8758,27 @@ async function runYmc(args, globalOpts) {
|
|
|
8725
8758
|
}
|
|
8726
8759
|
break;
|
|
8727
8760
|
}
|
|
8761
|
+
case "tool-download": {
|
|
8762
|
+
const uploadFileName = positionals(rest)[0];
|
|
8763
|
+
if (!uploadFileName) {
|
|
8764
|
+
console.error("用法:bfi ymc tool-download <uploadFileName> -e <env> [-o 输出文件]");
|
|
8765
|
+
console.error("例:bfi ymc tool-download monitor/17ff7937-xxx.html -e pre -o ~/flame.html");
|
|
8766
|
+
console.error("uploadFileName 来源:bfi ymc call tool/list(uploadPath 字段)或 bfi ymc dump-list(uploadPath)");
|
|
8767
|
+
process.exit(1);
|
|
8768
|
+
}
|
|
8769
|
+
const out = opt(rest, "-o", "--out") || uploadFileName.split("/").pop() || "download.bin";
|
|
8770
|
+
try {
|
|
8771
|
+
const n = await api.download("ymc/tool/download", {
|
|
8772
|
+
...requireEnv(rest),
|
|
8773
|
+
uploadFileName
|
|
8774
|
+
}, out);
|
|
8775
|
+
console.log(`✓ 已下载 ${n} 字节 → ${out}`);
|
|
8776
|
+
} catch (e) {
|
|
8777
|
+
console.error(`✗ ${e.message}`);
|
|
8778
|
+
process.exit(1);
|
|
8779
|
+
}
|
|
8780
|
+
break;
|
|
8781
|
+
}
|
|
8728
8782
|
case "call": {
|
|
8729
8783
|
const route = positionals(rest)[0];
|
|
8730
8784
|
const dataStr = opt(rest, "--data") || "{}";
|
|
@@ -8752,7 +8806,7 @@ async function runYmc(args, globalOpts) {
|
|
|
8752
8806
|
}
|
|
8753
8807
|
default:
|
|
8754
8808
|
console.error(`未知子命令:${sub}`);
|
|
8755
|
-
printHelp();
|
|
8809
|
+
printHelp$1();
|
|
8756
8810
|
process.exit(1);
|
|
8757
8811
|
}
|
|
8758
8812
|
}
|
|
@@ -8866,6 +8920,10 @@ const HELP = {
|
|
|
8866
8920
|
"bpr-download": `bfi ymc bpr-download <pk> -e <env> [-o <输出文件>]
|
|
8867
8921
|
BPR 报告 HTML 下载(文件流写盘)。pk 来源:bfi ymc call bpr/list --data '{"page":1,"pageSize":10}' -e <env>。
|
|
8868
8922
|
输出默认 bpr_<pk前16>.html。`,
|
|
8923
|
+
"tool-download": `bfi ymc tool-download <uploadFileName> -e <env> [-o <输出文件>]
|
|
8924
|
+
诊断文件内容下载(CPU 火焰图/堆 dump/BPR HTML 等文件流写盘)。
|
|
8925
|
+
uploadFileName 来源:bfi ymc call tool/list(uploadPath)或 bfi ymc dump-list(uploadPath)。
|
|
8926
|
+
输出默认取文件名部分(monitor/xx.html → xx.html)。`,
|
|
8869
8927
|
call: `bfi ymc call <route> -e <env> [--data '<json>'] [-t <time> | --start-time <s> --end-time <e>] [--timeout <秒>] [--json]
|
|
8870
8928
|
通用透传:覆盖全部注册表端点(warningCall/warningSql/exceptionHistory/bpr/list/alert/detail/tool/list/app/history/environment/uba/query ...)。
|
|
8871
8929
|
完整清单与参数声明:curl http://172.20.35.178:8901/openapi.json 或 /docs(Swagger UI)。
|
|
@@ -8879,14 +8937,16 @@ function printCommandHelp(cmd) {
|
|
|
8879
8937
|
if (h) console.log(h);
|
|
8880
8938
|
else {
|
|
8881
8939
|
console.error(`未知子命令:${cmd}`);
|
|
8882
|
-
printHelp();
|
|
8940
|
+
printHelp$1();
|
|
8883
8941
|
process.exit(1);
|
|
8884
8942
|
}
|
|
8885
8943
|
}
|
|
8886
|
-
function printHelp() {
|
|
8944
|
+
function printHelp$1() {
|
|
8887
8945
|
console.log(`
|
|
8888
8946
|
bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
|
|
8889
8947
|
|
|
8948
|
+
详细帮助/示例:bfi ymc help <命令>(首次使用必看——含参数默认值与易错点)
|
|
8949
|
+
|
|
8890
8950
|
命令:
|
|
8891
8951
|
status 登录会话状态(dev/prod)
|
|
8892
8952
|
trace <trace_id> -e <env> 三源合一(SLS+链路+ingress)
|
|
@@ -8902,6 +8962,12 @@ bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
|
|
|
8902
8962
|
url-stats </uri> -t <time> -e <env> [--app <service_name>]
|
|
8903
8963
|
ingress --trace-id <id> | --uri </path> [-t <time>] -e <env> [--exact] [--app <service_name>]
|
|
8904
8964
|
dump-list [-t <time>] -e <env> [--app] [--type profiler]
|
|
8965
|
+
tool-download <uploadPath> -e <env> [-o <文件>] 诊断文件内容下载
|
|
8966
|
+
log-url <...> SLS 直链(日志中心页面直达)
|
|
8967
|
+
events / sample-list 集群事件 / 采样数据列表
|
|
8968
|
+
callstat / sqlstat 慢调用/慢SQL 统计版聚合
|
|
8969
|
+
rca <...> 官方智能诊断 RCA(长耗时)
|
|
8970
|
+
bpr-download <pk> BPR 报告下载
|
|
8905
8971
|
cases <search|show|upload|index> 案例库(KEDB)
|
|
8906
8972
|
wiki search "<kw>" [--source] [--titles] 知识库统一检索(wiki/jira/文库)
|
|
8907
8973
|
wiki show <id> [--source] 知识条目全文
|
|
@@ -8919,6 +8985,298 @@ bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
|
|
|
8919
8985
|
详细帮助:bfi ymc help <命令>(或 bfi ymc <命令> -h)——含参数说明/默认值/示例。
|
|
8920
8986
|
`);
|
|
8921
8987
|
}
|
|
8988
|
+
function loadSourceMapConsumer() {
|
|
8989
|
+
return createRequire(import.meta.url)("source-map").SourceMapConsumer;
|
|
8990
|
+
}
|
|
8991
|
+
function parseErrorLog(log) {
|
|
8992
|
+
const out = {
|
|
8993
|
+
mdf: null,
|
|
8994
|
+
errorType: "",
|
|
8995
|
+
errorMessage: "",
|
|
8996
|
+
frames: []
|
|
8997
|
+
};
|
|
8998
|
+
const code = log.match(/\b(E\d{5})\b/);
|
|
8999
|
+
const kind = log.match(/\[([^\]]{2,20}错误)\]/);
|
|
9000
|
+
const diwork = log.match(/diworkCode:\s*([^\]]+)\]/);
|
|
9001
|
+
const method = log.match(/\[([a-zA-Z_$][\w$]*(?:\.[\w$$.]+)+)\]\s*:/);
|
|
9002
|
+
const event = log.match(/execute\[([^\]]+)\]/);
|
|
9003
|
+
const bill = log.match(/\[([^\]]+)\]\s*\[[^\]]{2,20}错误\]/);
|
|
9004
|
+
if (code && log.includes("【MDF】")) out.mdf = {
|
|
9005
|
+
code: code[1],
|
|
9006
|
+
billName: bill?.[1] ?? "",
|
|
9007
|
+
errorKind: kind?.[1] ?? "",
|
|
9008
|
+
diworkCode: diwork?.[1]?.trim() ?? "",
|
|
9009
|
+
method: method?.[1] ?? "",
|
|
9010
|
+
event: event?.[1] ?? ""
|
|
9011
|
+
};
|
|
9012
|
+
const errMatches = [...log.matchAll(/(TypeError|ReferenceError|SyntaxError|RangeError)\s*:\s*([^\n\r]+)/g)];
|
|
9013
|
+
if (errMatches.length > 0) {
|
|
9014
|
+
const last = errMatches[errMatches.length - 1];
|
|
9015
|
+
out.errorType = last[1];
|
|
9016
|
+
out.errorMessage = `${last[1]}: ${last[2].trim()}`;
|
|
9017
|
+
}
|
|
9018
|
+
for (const m of log.matchAll(/(https?:\/\/[^\s()"']+?\.min\.js):(\d+):(\d+)/g)) out.frames.push({
|
|
9019
|
+
index: out.frames.length,
|
|
9020
|
+
url: m[1],
|
|
9021
|
+
line: Number(m[2]),
|
|
9022
|
+
col: Number(m[3])
|
|
9023
|
+
});
|
|
9024
|
+
return out;
|
|
9025
|
+
}
|
|
9026
|
+
function pickFrame(frames) {
|
|
9027
|
+
const ext = frames.findIndex((f) => /\/extend\.\d+\.[a-f0-9]+/.test(f.url));
|
|
9028
|
+
return ext !== -1 ? ext : 0;
|
|
9029
|
+
}
|
|
9030
|
+
function resolveSourceMappingUrl(jsUrl, comment) {
|
|
9031
|
+
if (/^https?:\/\//.test(comment)) return comment;
|
|
9032
|
+
return `${jsUrl.replace(/\/[^/]*$/, "")}/${comment.replace(/^\.?\//, "")}`;
|
|
9033
|
+
}
|
|
9034
|
+
function webpackSourceInfo(source) {
|
|
9035
|
+
const m = source.match(/^webpack:\/\/([^/]+)\/(.+)$/);
|
|
9036
|
+
if (!m) return {
|
|
9037
|
+
repo: "",
|
|
9038
|
+
filePath: source.replace(/^webpack:\/\//, "")
|
|
9039
|
+
};
|
|
9040
|
+
return {
|
|
9041
|
+
repo: m[1],
|
|
9042
|
+
filePath: m[2].replace(/^\.\//, "")
|
|
9043
|
+
};
|
|
9044
|
+
}
|
|
9045
|
+
function extractContext(content, line, ctx) {
|
|
9046
|
+
const lines = content.split("\n");
|
|
9047
|
+
const start = Math.max(1, line - ctx);
|
|
9048
|
+
const end = Math.min(lines.length, line + ctx);
|
|
9049
|
+
const width = String(end).length;
|
|
9050
|
+
const out = [];
|
|
9051
|
+
for (let i = start; i <= end; i++) {
|
|
9052
|
+
const mark = i === line ? " --> " : " ";
|
|
9053
|
+
out.push(`${mark}${String(i).padStart(width)} | ${lines[i - 1]}`);
|
|
9054
|
+
}
|
|
9055
|
+
return out.join("\n");
|
|
9056
|
+
}
|
|
9057
|
+
const UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36";
|
|
9058
|
+
function httpGet(url, redirects = 0) {
|
|
9059
|
+
return new Promise((resolve, reject) => {
|
|
9060
|
+
if (redirects > 5) {
|
|
9061
|
+
reject(/* @__PURE__ */ new Error(`重定向超过 5 次: ${url}`));
|
|
9062
|
+
return;
|
|
9063
|
+
}
|
|
9064
|
+
const req = (url.startsWith("https://") ? https : http).get(url, {
|
|
9065
|
+
headers: {
|
|
9066
|
+
"user-agent": UA,
|
|
9067
|
+
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
9068
|
+
cookie: "debug=true"
|
|
9069
|
+
},
|
|
9070
|
+
timeout: 6e4
|
|
9071
|
+
}, (res) => {
|
|
9072
|
+
const status = res.statusCode ?? 0;
|
|
9073
|
+
const loc = res.headers.location;
|
|
9074
|
+
if (status >= 300 && status < 400 && loc) {
|
|
9075
|
+
res.resume();
|
|
9076
|
+
httpGet(new URL(loc, url).toString(), redirects + 1).then(resolve, reject);
|
|
9077
|
+
return;
|
|
9078
|
+
}
|
|
9079
|
+
const chunks = [];
|
|
9080
|
+
res.on("data", (c) => chunks.push(c));
|
|
9081
|
+
res.on("end", () => resolve({
|
|
9082
|
+
status,
|
|
9083
|
+
body: Buffer.concat(chunks)
|
|
9084
|
+
}));
|
|
9085
|
+
});
|
|
9086
|
+
req.on("timeout", () => req.destroy(/* @__PURE__ */ new Error(`下载超时(60s): ${url}`)));
|
|
9087
|
+
req.on("error", reject);
|
|
9088
|
+
});
|
|
9089
|
+
}
|
|
9090
|
+
async function downloadTo(url, file, what) {
|
|
9091
|
+
const { status, body } = await httpGet(url);
|
|
9092
|
+
if (status !== 200 || body.length === 0) throw new Error(`${what}下载失败(HTTP ${status}): ${url}\n若 sourcemap 被剥离,降级:按 JS 文件名走 bfi-code-search 内容搜索定位`);
|
|
9093
|
+
fs.writeFileSync(file, body);
|
|
9094
|
+
return body.length;
|
|
9095
|
+
}
|
|
9096
|
+
function cacheDirFor(jsUrl, root) {
|
|
9097
|
+
const u = new URL(jsUrl);
|
|
9098
|
+
return path$1.join(root || path$1.join(os$1.homedir(), ".bfi", "fe-locate", "cache"), u.host, path$1.dirname(u.pathname).replace(/^\/|\/$/g, ""));
|
|
9099
|
+
}
|
|
9100
|
+
function readStdin() {
|
|
9101
|
+
return new Promise((resolve, reject) => {
|
|
9102
|
+
let data = "";
|
|
9103
|
+
process.stdin.setEncoding("utf-8");
|
|
9104
|
+
process.stdin.on("data", (c) => {
|
|
9105
|
+
data += c;
|
|
9106
|
+
});
|
|
9107
|
+
process.stdin.on("end", () => resolve(data));
|
|
9108
|
+
process.stdin.on("error", reject);
|
|
9109
|
+
});
|
|
9110
|
+
}
|
|
9111
|
+
function parseSourceMappingComment(jsContent) {
|
|
9112
|
+
const m = jsContent.slice(-3e3).match(/\/\/# sourceMappingURL=(\S+)/);
|
|
9113
|
+
if (!m) throw new Error("JS 文件末尾未找到 //# sourceMappingURL= 注释(构建未产出 sourcemap)");
|
|
9114
|
+
return m[1];
|
|
9115
|
+
}
|
|
9116
|
+
async function locateInMap(mapFile, line, col) {
|
|
9117
|
+
const consumer = await new (loadSourceMapConsumer())(JSON.parse(fs.readFileSync(mapFile, "utf-8")));
|
|
9118
|
+
try {
|
|
9119
|
+
let pos = consumer.originalPositionFor({
|
|
9120
|
+
line,
|
|
9121
|
+
column: Math.max(0, col - 1)
|
|
9122
|
+
});
|
|
9123
|
+
if (!pos?.source) pos = consumer.originalPositionFor({
|
|
9124
|
+
line,
|
|
9125
|
+
column: col
|
|
9126
|
+
});
|
|
9127
|
+
if (!pos?.source) throw new Error(`sourcemap 中无该位置映射(line=${line}, col=${col})——JS 与 map 版本可能不一致`);
|
|
9128
|
+
const content = pos.source ? consumer.sourceContentFor(pos.source, true) : null;
|
|
9129
|
+
return {
|
|
9130
|
+
source: pos.source,
|
|
9131
|
+
line: pos.line ?? 0,
|
|
9132
|
+
column: pos.column ?? 0,
|
|
9133
|
+
name: pos.name ?? "",
|
|
9134
|
+
content
|
|
9135
|
+
};
|
|
9136
|
+
} finally {
|
|
9137
|
+
consumer.destroy?.();
|
|
9138
|
+
}
|
|
9139
|
+
}
|
|
9140
|
+
function printHelp() {
|
|
9141
|
+
console.log(`bfi fe-locate — 前端错误定位(sourcemap 还原)
|
|
9142
|
+
|
|
9143
|
+
用法:
|
|
9144
|
+
bfi fe-locate "<错误日志>" # 单参数(控制台报错栈粘贴)
|
|
9145
|
+
cat error.log | bfi fe-locate --stdin # 长日志走 stdin(推荐,免转义)
|
|
9146
|
+
|
|
9147
|
+
选项:
|
|
9148
|
+
--json 结构化 JSON 输出(定位结果+全部帧+上下文)
|
|
9149
|
+
--stdin 从标准输入读取错误日志
|
|
9150
|
+
--context <N> 源码上下文行数(默认 5)
|
|
9151
|
+
--frame <N> 指定第 N 帧定位(1-based;默认智能选帧:extend 业务帧优先)
|
|
9152
|
+
--no-cache 强制重新下载 JS/sourcemap
|
|
9153
|
+
|
|
9154
|
+
示例:
|
|
9155
|
+
bfi fe-locate "TypeError: e.get(...).setValue is not a function at y (https://static-dc-core1.yonyoucloud.com/static/mdf/udinghuo/latest/javascripts/extend.23.xxx.min.js:1:100358)"`);
|
|
9156
|
+
}
|
|
9157
|
+
function printResult(r, ctxLines) {
|
|
9158
|
+
const err = r.error;
|
|
9159
|
+
const mdf = r.mdf;
|
|
9160
|
+
console.log("═══ bfi fe-locate — 前端错误定位 ═══\n");
|
|
9161
|
+
if (mdf) console.log(`【MDF 头】${mdf.code} | 单据:${mdf.billName} | 类型:${mdf.errorKind} | diworkCode:${mdf.diworkCode} | 方法:${mdf.method} | 事件:${mdf.event}`);
|
|
9162
|
+
console.log(`【错误】${err.type}: ${err.message}`);
|
|
9163
|
+
const frames = r.frames;
|
|
9164
|
+
const sel = r.selected;
|
|
9165
|
+
console.log(`【选帧】第 ${sel.index} 帧(${sel.reason}): ${path$1.basename(String(sel.url))}:${sel.line}:${sel.col}(共 ${frames.length} 帧)`);
|
|
9166
|
+
console.log(`【压缩 JS】${r.jsUrl}`);
|
|
9167
|
+
const resolved = r.resolved;
|
|
9168
|
+
if (resolved) {
|
|
9169
|
+
console.log("\n── sourcemap 还原 ──");
|
|
9170
|
+
console.log(`原始文件: ${resolved.source}`);
|
|
9171
|
+
const info = resolved.webpack;
|
|
9172
|
+
if (info.repo) console.log(`包内仓库: ${info.repo}(注:包内仓库名≠GitLab 仓库名,远程定位按文件名搜索)`);
|
|
9173
|
+
console.log(`文件路径: ${info.filePath}`);
|
|
9174
|
+
console.log(`位置: 行 ${resolved.line}, 列 ${resolved.column}`);
|
|
9175
|
+
if (resolved.name) console.log(`函数名: ${resolved.name}`);
|
|
9176
|
+
if (ctxLines) {
|
|
9177
|
+
console.log(`\n── 源码上下文(sourcemap sourcesContent,±${r.context} 行)──`);
|
|
9178
|
+
console.log(ctxLines);
|
|
9179
|
+
}
|
|
9180
|
+
console.log("\n── 下一步 ──");
|
|
9181
|
+
console.log(`· 远程仓库: SourceGraph 精确文件名搜索 "${path$1.basename(info.filePath)}"(bfi-code-search)`);
|
|
9182
|
+
console.log("· 归因分析: 对照 bfi-diagnose-engine references/fe-error-attribution.md");
|
|
9183
|
+
console.log("· 数据格式类根因(接口返回变化)→ deep 档联动 bfi ymc ingress/trace 取证");
|
|
9184
|
+
}
|
|
9185
|
+
console.log(`\n缓存: ${r.cacheDir}`);
|
|
9186
|
+
}
|
|
9187
|
+
async function runFeLocate(args, globalOpts) {
|
|
9188
|
+
if (!args.length || flag(args, "--help") || flag(args, "-h")) {
|
|
9189
|
+
printHelp();
|
|
9190
|
+
return;
|
|
9191
|
+
}
|
|
9192
|
+
const jsonMode = flag(args, "--json");
|
|
9193
|
+
const noCache = flag(args, "--no-cache");
|
|
9194
|
+
const ctxN = Number(opt(args, "--context") ?? 5) || 5;
|
|
9195
|
+
const frameN = opt(args, "--frame");
|
|
9196
|
+
let log = positionals(args)[0] ?? "";
|
|
9197
|
+
if (!log && flag(args, "--stdin")) log = await readStdin();
|
|
9198
|
+
if (!log && !process.stdin.isTTY) log = await readStdin();
|
|
9199
|
+
if (!log.trim()) {
|
|
9200
|
+
console.error("缺少错误日志参数。粘贴控制台报错栈,或 cat error.log | bfi fe-locate --stdin");
|
|
9201
|
+
process.exit(1);
|
|
9202
|
+
}
|
|
9203
|
+
const parsed = parseErrorLog(log);
|
|
9204
|
+
if (parsed.frames.length === 0) {
|
|
9205
|
+
console.error("未在日志中找到压缩 JS 帧(https://...xxx.min.js:行:列)。请确认粘贴的是浏览器控制台报错栈。");
|
|
9206
|
+
process.exit(1);
|
|
9207
|
+
}
|
|
9208
|
+
const selIdx = frameN ? Math.max(0, Number(frameN) - 1) : pickFrame(parsed.frames);
|
|
9209
|
+
const selReason = frameN ? "--frame 指定" : parsed.frames[selIdx] && /\/extend\.\d+\./.test(parsed.frames[selIdx].url) ? "extend 业务帧优先" : "默认第一帧";
|
|
9210
|
+
if (selIdx >= parsed.frames.length) {
|
|
9211
|
+
console.error(`--frame ${frameN} 超出范围(共 ${parsed.frames.length} 帧)`);
|
|
9212
|
+
process.exit(1);
|
|
9213
|
+
}
|
|
9214
|
+
const frame = parsed.frames[selIdx];
|
|
9215
|
+
const cDir = cacheDirFor(frame.url, globalOpts["fe-cache-dir"]);
|
|
9216
|
+
fs.mkdirSync(cDir, { recursive: true });
|
|
9217
|
+
const jsFile = path$1.join(cDir, "error.min.js");
|
|
9218
|
+
const mapFile = path$1.join(cDir, "error.min.js.map");
|
|
9219
|
+
const metaFile = path$1.join(cDir, "meta.json");
|
|
9220
|
+
let sourceMappingURL = "";
|
|
9221
|
+
if (!noCache && fs.existsSync(metaFile)) try {
|
|
9222
|
+
sourceMappingURL = JSON.parse(fs.readFileSync(metaFile, "utf-8")).sourceMappingUrl ?? "";
|
|
9223
|
+
} catch {}
|
|
9224
|
+
if (sourceMappingURL && fs.existsSync(mapFile)) console.error(`[缓存命中] ${cDir}`);
|
|
9225
|
+
else {
|
|
9226
|
+
const jsSize = await downloadTo(frame.url, jsFile, "压缩 JS");
|
|
9227
|
+
console.error(`[下载] 压缩 JS(${(jsSize / 1024).toFixed(0)} KB)`);
|
|
9228
|
+
const comment = parseSourceMappingComment(fs.readFileSync(jsFile, "utf-8"));
|
|
9229
|
+
sourceMappingURL = resolveSourceMappingUrl(frame.url, comment);
|
|
9230
|
+
console.error(`[解析] sourceMappingURL → ${sourceMappingURL}`);
|
|
9231
|
+
const mapSize = await downloadTo(sourceMappingURL, mapFile, "sourcemap");
|
|
9232
|
+
console.error(`[下载] sourcemap(${(mapSize / 1024).toFixed(0)} KB)`);
|
|
9233
|
+
fs.writeFileSync(metaFile, JSON.stringify({
|
|
9234
|
+
jsUrl: frame.url,
|
|
9235
|
+
sourceMappingUrl: sourceMappingURL,
|
|
9236
|
+
downloadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9237
|
+
}, null, 2));
|
|
9238
|
+
}
|
|
9239
|
+
const located = await locateInMap(mapFile, frame.line, frame.col);
|
|
9240
|
+
const webpack = webpackSourceInfo(located.source);
|
|
9241
|
+
const ctxLines = located.content ? extractContext(located.content, located.line, ctxN) : null;
|
|
9242
|
+
const result = {
|
|
9243
|
+
error: {
|
|
9244
|
+
type: parsed.errorType,
|
|
9245
|
+
message: parsed.errorMessage
|
|
9246
|
+
},
|
|
9247
|
+
mdf: parsed.mdf,
|
|
9248
|
+
frames: parsed.frames,
|
|
9249
|
+
selected: {
|
|
9250
|
+
...frame,
|
|
9251
|
+
index: selIdx + 1,
|
|
9252
|
+
reason: selReason
|
|
9253
|
+
},
|
|
9254
|
+
jsUrl: frame.url,
|
|
9255
|
+
sourceMappingURL,
|
|
9256
|
+
resolved: {
|
|
9257
|
+
source: located.source,
|
|
9258
|
+
webpack,
|
|
9259
|
+
line: located.line,
|
|
9260
|
+
column: located.column,
|
|
9261
|
+
name: located.name
|
|
9262
|
+
},
|
|
9263
|
+
context: ctxN,
|
|
9264
|
+
contextText: ctxLines,
|
|
9265
|
+
cacheDir: cDir,
|
|
9266
|
+
locatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9267
|
+
};
|
|
9268
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14);
|
|
9269
|
+
const runDir = path$1.join(cDir, stamp);
|
|
9270
|
+
fs.mkdirSync(runDir, { recursive: true });
|
|
9271
|
+
fs.writeFileSync(path$1.join(runDir, "error.log"), log);
|
|
9272
|
+
fs.writeFileSync(path$1.join(runDir, "result.json"), JSON.stringify(result, null, 2));
|
|
9273
|
+
if (jsonMode) {
|
|
9274
|
+
console.log(JSON.stringify(result, null, 2));
|
|
9275
|
+
return;
|
|
9276
|
+
}
|
|
9277
|
+
printResult(result, ctxLines);
|
|
9278
|
+
console.log(`\n结果已存: ${path$1.join(runDir, "result.json")}`);
|
|
9279
|
+
}
|
|
8922
9280
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8923
9281
|
function getVersion() {
|
|
8924
9282
|
try {
|
|
@@ -9309,6 +9667,9 @@ async function main() {
|
|
|
9309
9667
|
case "ymc":
|
|
9310
9668
|
await runYmc(restArgs, globalOpts);
|
|
9311
9669
|
break;
|
|
9670
|
+
case "fe-locate":
|
|
9671
|
+
await runFeLocate(restArgs, globalOpts);
|
|
9672
|
+
break;
|
|
9312
9673
|
case "auth":
|
|
9313
9674
|
if (restArgs[0] === "logout") await runLogout();
|
|
9314
9675
|
else if (restArgs[0] === "whoami") await runWhoami(globalOpts);
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bfi-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.75",
|
|
4
4
|
"description": "用友大业财BFI统一命令行工具,支持技能,技能包,元数据,UI元数据等",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"bfi": "
|
|
8
|
-
"bfi-db-mcp": "
|
|
7
|
+
"bfi": "bin/bfi.js",
|
|
8
|
+
"bfi-db-mcp": "bin/bfi-db-mcp.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
51
51
|
"@vercel/detect-agent": "^1.2.3",
|
|
52
52
|
"mysql2": "^3.14.0",
|
|
53
|
+
"source-map": "^0.7.4",
|
|
53
54
|
"yaml": "^2.8.3",
|
|
54
55
|
"zod": "^3.25.0"
|
|
55
56
|
}
|