bfi-cli 0.1.74 → 0.1.76
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 +491 -4
- 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";
|
|
@@ -8239,7 +8240,7 @@ async function runYmc(args, globalOpts) {
|
|
|
8239
8240
|
const token = globalOpts.token;
|
|
8240
8241
|
const api = new YmcApi(token ?? null, void 0, dryRun);
|
|
8241
8242
|
if (!sub || sub === "--help" || sub === "-h") {
|
|
8242
|
-
printHelp();
|
|
8243
|
+
printHelp$2();
|
|
8243
8244
|
return;
|
|
8244
8245
|
}
|
|
8245
8246
|
if (sub === "help") {
|
|
@@ -8805,7 +8806,7 @@ async function runYmc(args, globalOpts) {
|
|
|
8805
8806
|
}
|
|
8806
8807
|
default:
|
|
8807
8808
|
console.error(`未知子命令:${sub}`);
|
|
8808
|
-
printHelp();
|
|
8809
|
+
printHelp$2();
|
|
8809
8810
|
process.exit(1);
|
|
8810
8811
|
}
|
|
8811
8812
|
}
|
|
@@ -8936,11 +8937,11 @@ function printCommandHelp(cmd) {
|
|
|
8936
8937
|
if (h) console.log(h);
|
|
8937
8938
|
else {
|
|
8938
8939
|
console.error(`未知子命令:${cmd}`);
|
|
8939
|
-
printHelp();
|
|
8940
|
+
printHelp$2();
|
|
8940
8941
|
process.exit(1);
|
|
8941
8942
|
}
|
|
8942
8943
|
}
|
|
8943
|
-
function printHelp() {
|
|
8944
|
+
function printHelp$2() {
|
|
8944
8945
|
console.log(`
|
|
8945
8946
|
bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
|
|
8946
8947
|
|
|
@@ -8984,6 +8985,486 @@ bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
|
|
|
8984
8985
|
详细帮助:bfi ymc help <命令>(或 bfi ymc <命令> -h)——含参数说明/默认值/示例。
|
|
8985
8986
|
`);
|
|
8986
8987
|
}
|
|
8988
|
+
const METRICS = [
|
|
8989
|
+
"entry",
|
|
8990
|
+
"fail",
|
|
8991
|
+
"costtime",
|
|
8992
|
+
"content"
|
|
8993
|
+
];
|
|
8994
|
+
const RESOURCES = [
|
|
8995
|
+
"type-doc",
|
|
8996
|
+
"type-info",
|
|
8997
|
+
"listener-doc"
|
|
8998
|
+
];
|
|
8999
|
+
function printHelp$1() {
|
|
9000
|
+
console.log(`bfi event — YonBIP 事件中心取数(诊断编排见 bfi-diagnose-event 技能)
|
|
9001
|
+
|
|
9002
|
+
用法:
|
|
9003
|
+
bfi event monitor <metric> [过滤参数]
|
|
9004
|
+
metric: entry|fail|costtime|content
|
|
9005
|
+
过滤: --log-date D --source-id S --event-type-code ET --node-code N --datacenter DC --size N
|
|
9006
|
+
bfi event master <resource> [action[:target]]
|
|
9007
|
+
resource: type-doc|type-info|listener-doc
|
|
9008
|
+
action[:target]:
|
|
9009
|
+
list | code:<编码> | id:<ID> | latest:<源ID,类型编码> | source-type:<源ID,类型编码>
|
|
9010
|
+
source-node:<源ID,节点编码> | event-type:<类型编码> | micro-service:<微服务编码>
|
|
9011
|
+
bfi event stat <dc> <date> [metric_type] [--page N] [--size N]
|
|
9012
|
+
date: yyyy-MM-dd 单日;metric_type: enter|fail|response_time_4s_|response_time_2s_4s|body_size_512k_1m|body_size_1m_
|
|
9013
|
+
bfi event record <dc> <date> --source-id S --event-type ET --node-code N --ds-code DS [--record-status fail] [--record-id ID] [--tenant-code T] [--size ≤20]
|
|
9014
|
+
bfi event content <dc> <date> --event-id E --ds-code DS --tenant-code T
|
|
9015
|
+
bfi event dclist <dc>
|
|
9016
|
+
全局: --json / --dry-run / --token <tok>
|
|
9017
|
+
|
|
9018
|
+
示例:
|
|
9019
|
+
bfi event master type-doc code:PC_PRODUCT_STOP
|
|
9020
|
+
bfi event dclist core1
|
|
9021
|
+
bfi event stat core1 2026-09-14 fail
|
|
9022
|
+
bfi event record core1 2026-09-14 --source-id MULILANG --event-type MULILANGCHANGE --node-code N --ds-code YonBip_core1_2_service`);
|
|
9023
|
+
}
|
|
9024
|
+
function missing(name) {
|
|
9025
|
+
console.error(`缺少参数 ${name}。运行 bfi event --help 查看用法。`);
|
|
9026
|
+
process.exit(1);
|
|
9027
|
+
}
|
|
9028
|
+
async function runEvent(args, globalOpts) {
|
|
9029
|
+
const sub = args[0];
|
|
9030
|
+
const rest = args.slice(1);
|
|
9031
|
+
if (!sub || sub === "--help" || sub === "-h" || flag(rest, "--help") || flag(rest, "-h")) {
|
|
9032
|
+
printHelp$1();
|
|
9033
|
+
return;
|
|
9034
|
+
}
|
|
9035
|
+
const jsonMode = flag(rest, "--json");
|
|
9036
|
+
const dryRun = flag(rest, "--dry-run");
|
|
9037
|
+
const api = new YmcApi(globalOpts.token ?? null, void 0, dryRun);
|
|
9038
|
+
if (!dryRun && !api.token) {
|
|
9039
|
+
console.error("未认证。先运行:bfi auth login --token <sk-token>(或传 --token)");
|
|
9040
|
+
process.exit(1);
|
|
9041
|
+
}
|
|
9042
|
+
const reqs = positionals(rest);
|
|
9043
|
+
const call = async (path, body) => emit(await api.call(path, body), jsonMode);
|
|
9044
|
+
const num = (name, dflt) => Number(opt(rest, name) ?? dflt);
|
|
9045
|
+
const str = (name) => opt(rest, name);
|
|
9046
|
+
if (sub === "monitor") {
|
|
9047
|
+
const metric = reqs[0];
|
|
9048
|
+
if (!metric || !METRICS.includes(metric)) {
|
|
9049
|
+
console.error("缺少/无效 metric(entry|fail|costtime|content)。例:bfi event monitor fail");
|
|
9050
|
+
process.exit(1);
|
|
9051
|
+
}
|
|
9052
|
+
const body = {
|
|
9053
|
+
metric,
|
|
9054
|
+
size: num("--size", 200)
|
|
9055
|
+
};
|
|
9056
|
+
for (const [cliName, key] of Object.entries({
|
|
9057
|
+
"--log-date": "log_date",
|
|
9058
|
+
"--source-id": "source_id",
|
|
9059
|
+
"--event-type-code": "event_type_code",
|
|
9060
|
+
"--node-code": "node_code",
|
|
9061
|
+
"--datacenter": "datacenter"
|
|
9062
|
+
})) {
|
|
9063
|
+
const v = str(cliName);
|
|
9064
|
+
if (v) body[key] = v;
|
|
9065
|
+
}
|
|
9066
|
+
await call("event/monitor", body);
|
|
9067
|
+
return;
|
|
9068
|
+
}
|
|
9069
|
+
if (sub === "master") {
|
|
9070
|
+
const resource = reqs[0];
|
|
9071
|
+
if (!resource || !RESOURCES.includes(resource)) {
|
|
9072
|
+
console.error("缺少/无效 resource(type-doc|type-info|listener-doc)。例:bfi event master type-doc code:PC_PRODUCT_STOP");
|
|
9073
|
+
process.exit(1);
|
|
9074
|
+
}
|
|
9075
|
+
let action = "list";
|
|
9076
|
+
let target;
|
|
9077
|
+
const second = reqs[1];
|
|
9078
|
+
if (second && second.includes(":")) {
|
|
9079
|
+
const i = second.indexOf(":");
|
|
9080
|
+
action = second.slice(0, i);
|
|
9081
|
+
target = second.slice(i + 1);
|
|
9082
|
+
} else if (second) {
|
|
9083
|
+
action = "code";
|
|
9084
|
+
target = second;
|
|
9085
|
+
}
|
|
9086
|
+
await call("event/master", {
|
|
9087
|
+
resource,
|
|
9088
|
+
action,
|
|
9089
|
+
target,
|
|
9090
|
+
query: {
|
|
9091
|
+
page: num("--page", 0),
|
|
9092
|
+
size: num("--size", 200)
|
|
9093
|
+
}
|
|
9094
|
+
});
|
|
9095
|
+
return;
|
|
9096
|
+
}
|
|
9097
|
+
if (sub === "stat") {
|
|
9098
|
+
const dc = reqs[0];
|
|
9099
|
+
const date = reqs[1];
|
|
9100
|
+
if (!dc || !date) {
|
|
9101
|
+
console.error("用法: bfi event stat <dc> <date> [metric_type]");
|
|
9102
|
+
process.exit(1);
|
|
9103
|
+
}
|
|
9104
|
+
await call("event/stat", {
|
|
9105
|
+
dc,
|
|
9106
|
+
date,
|
|
9107
|
+
metric_type: reqs[2] ?? "fail",
|
|
9108
|
+
page: num("--page", 0),
|
|
9109
|
+
size: num("--size", 1e3)
|
|
9110
|
+
});
|
|
9111
|
+
return;
|
|
9112
|
+
}
|
|
9113
|
+
if (sub === "record") {
|
|
9114
|
+
const dc = reqs[0];
|
|
9115
|
+
const date = reqs[1];
|
|
9116
|
+
if (!dc || !date) {
|
|
9117
|
+
console.error("用法: bfi event record <dc> <date> --source-id S --event-type ET --node-code N --ds-code DS");
|
|
9118
|
+
process.exit(1);
|
|
9119
|
+
}
|
|
9120
|
+
const sourceId = str("--source-id");
|
|
9121
|
+
const eventType = str("--event-type");
|
|
9122
|
+
const nodeCode = str("--node-code");
|
|
9123
|
+
const dsCode = str("--ds-code");
|
|
9124
|
+
if (!sourceId) missing("--source-id");
|
|
9125
|
+
if (!eventType) missing("--event-type");
|
|
9126
|
+
if (!nodeCode) missing("--node-code");
|
|
9127
|
+
if (!dsCode) missing("--ds-code");
|
|
9128
|
+
const body = {
|
|
9129
|
+
dc,
|
|
9130
|
+
date,
|
|
9131
|
+
source_id: sourceId,
|
|
9132
|
+
event_type: eventType,
|
|
9133
|
+
node_code: nodeCode,
|
|
9134
|
+
ds_code: dsCode,
|
|
9135
|
+
record_status: str("--record-status") ?? "fail",
|
|
9136
|
+
size: num("--size", 20)
|
|
9137
|
+
};
|
|
9138
|
+
const rid = str("--record-id");
|
|
9139
|
+
if (rid) body.record_id = rid;
|
|
9140
|
+
const tc = str("--tenant-code");
|
|
9141
|
+
if (tc) body.tenant_code = tc;
|
|
9142
|
+
await call("event/record", body);
|
|
9143
|
+
return;
|
|
9144
|
+
}
|
|
9145
|
+
if (sub === "content") {
|
|
9146
|
+
const dc = reqs[0];
|
|
9147
|
+
const date = reqs[1];
|
|
9148
|
+
const eventId = str("--event-id");
|
|
9149
|
+
const dsCode = str("--ds-code");
|
|
9150
|
+
const tenantCode = str("--tenant-code");
|
|
9151
|
+
if (!dc || !date || !eventId || !dsCode || !tenantCode) {
|
|
9152
|
+
console.error("用法: bfi event content <dc> <date> --event-id E --ds-code DS --tenant-code T");
|
|
9153
|
+
process.exit(1);
|
|
9154
|
+
}
|
|
9155
|
+
await call("event/content", {
|
|
9156
|
+
dc,
|
|
9157
|
+
date,
|
|
9158
|
+
event_id: eventId,
|
|
9159
|
+
ds_code: dsCode,
|
|
9160
|
+
tenant_code: tenantCode
|
|
9161
|
+
});
|
|
9162
|
+
return;
|
|
9163
|
+
}
|
|
9164
|
+
if (sub === "dclist") {
|
|
9165
|
+
const dc = reqs[0];
|
|
9166
|
+
if (!dc) {
|
|
9167
|
+
console.error("用法: bfi event dclist <dc>");
|
|
9168
|
+
process.exit(1);
|
|
9169
|
+
}
|
|
9170
|
+
await call("event/dclist", { dc });
|
|
9171
|
+
return;
|
|
9172
|
+
}
|
|
9173
|
+
console.error(`未知子命令: ${sub}。运行 bfi event --help`);
|
|
9174
|
+
process.exit(1);
|
|
9175
|
+
}
|
|
9176
|
+
function loadSourceMapConsumer() {
|
|
9177
|
+
return createRequire(import.meta.url)("source-map").SourceMapConsumer;
|
|
9178
|
+
}
|
|
9179
|
+
function parseErrorLog(log) {
|
|
9180
|
+
const out = {
|
|
9181
|
+
mdf: null,
|
|
9182
|
+
errorType: "",
|
|
9183
|
+
errorMessage: "",
|
|
9184
|
+
frames: []
|
|
9185
|
+
};
|
|
9186
|
+
const code = log.match(/\b(E\d{5})\b/);
|
|
9187
|
+
const kind = log.match(/\[([^\]]{2,20}错误)\]/);
|
|
9188
|
+
const diwork = log.match(/diworkCode:\s*([^\]]+)\]/);
|
|
9189
|
+
const method = log.match(/\[([a-zA-Z_$][\w$]*(?:\.[\w$$.]+)+)\]\s*:/);
|
|
9190
|
+
const event = log.match(/execute\[([^\]]+)\]/);
|
|
9191
|
+
const bill = log.match(/\[([^\]]+)\]\s*\[[^\]]{2,20}错误\]/);
|
|
9192
|
+
if (code && log.includes("【MDF】")) out.mdf = {
|
|
9193
|
+
code: code[1],
|
|
9194
|
+
billName: bill?.[1] ?? "",
|
|
9195
|
+
errorKind: kind?.[1] ?? "",
|
|
9196
|
+
diworkCode: diwork?.[1]?.trim() ?? "",
|
|
9197
|
+
method: method?.[1] ?? "",
|
|
9198
|
+
event: event?.[1] ?? ""
|
|
9199
|
+
};
|
|
9200
|
+
const errMatches = [...log.matchAll(/(TypeError|ReferenceError|SyntaxError|RangeError)\s*:\s*([^\n\r]+)/g)];
|
|
9201
|
+
if (errMatches.length > 0) {
|
|
9202
|
+
const last = errMatches[errMatches.length - 1];
|
|
9203
|
+
out.errorType = last[1];
|
|
9204
|
+
out.errorMessage = `${last[1]}: ${last[2].trim()}`;
|
|
9205
|
+
}
|
|
9206
|
+
for (const m of log.matchAll(/(https?:\/\/[^\s()"']+?\.min\.js):(\d+):(\d+)/g)) out.frames.push({
|
|
9207
|
+
index: out.frames.length,
|
|
9208
|
+
url: m[1],
|
|
9209
|
+
line: Number(m[2]),
|
|
9210
|
+
col: Number(m[3])
|
|
9211
|
+
});
|
|
9212
|
+
return out;
|
|
9213
|
+
}
|
|
9214
|
+
function pickFrame(frames) {
|
|
9215
|
+
const ext = frames.findIndex((f) => /\/extend\.\d+\.[a-f0-9]+/.test(f.url));
|
|
9216
|
+
return ext !== -1 ? ext : 0;
|
|
9217
|
+
}
|
|
9218
|
+
function resolveSourceMappingUrl(jsUrl, comment) {
|
|
9219
|
+
if (/^https?:\/\//.test(comment)) return comment;
|
|
9220
|
+
return `${jsUrl.replace(/\/[^/]*$/, "")}/${comment.replace(/^\.?\//, "")}`;
|
|
9221
|
+
}
|
|
9222
|
+
function webpackSourceInfo(source) {
|
|
9223
|
+
const m = source.match(/^webpack:\/\/([^/]+)\/(.+)$/);
|
|
9224
|
+
if (!m) return {
|
|
9225
|
+
repo: "",
|
|
9226
|
+
filePath: source.replace(/^webpack:\/\//, "")
|
|
9227
|
+
};
|
|
9228
|
+
return {
|
|
9229
|
+
repo: m[1],
|
|
9230
|
+
filePath: m[2].replace(/^\.\//, "")
|
|
9231
|
+
};
|
|
9232
|
+
}
|
|
9233
|
+
function extractContext(content, line, ctx) {
|
|
9234
|
+
const lines = content.split("\n");
|
|
9235
|
+
const start = Math.max(1, line - ctx);
|
|
9236
|
+
const end = Math.min(lines.length, line + ctx);
|
|
9237
|
+
const width = String(end).length;
|
|
9238
|
+
const out = [];
|
|
9239
|
+
for (let i = start; i <= end; i++) {
|
|
9240
|
+
const mark = i === line ? " --> " : " ";
|
|
9241
|
+
out.push(`${mark}${String(i).padStart(width)} | ${lines[i - 1]}`);
|
|
9242
|
+
}
|
|
9243
|
+
return out.join("\n");
|
|
9244
|
+
}
|
|
9245
|
+
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";
|
|
9246
|
+
function httpGet(url, redirects = 0) {
|
|
9247
|
+
return new Promise((resolve, reject) => {
|
|
9248
|
+
if (redirects > 5) {
|
|
9249
|
+
reject(/* @__PURE__ */ new Error(`重定向超过 5 次: ${url}`));
|
|
9250
|
+
return;
|
|
9251
|
+
}
|
|
9252
|
+
const req = (url.startsWith("https://") ? https : http).get(url, {
|
|
9253
|
+
headers: {
|
|
9254
|
+
"user-agent": UA,
|
|
9255
|
+
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
9256
|
+
cookie: "debug=true"
|
|
9257
|
+
},
|
|
9258
|
+
timeout: 6e4
|
|
9259
|
+
}, (res) => {
|
|
9260
|
+
const status = res.statusCode ?? 0;
|
|
9261
|
+
const loc = res.headers.location;
|
|
9262
|
+
if (status >= 300 && status < 400 && loc) {
|
|
9263
|
+
res.resume();
|
|
9264
|
+
httpGet(new URL(loc, url).toString(), redirects + 1).then(resolve, reject);
|
|
9265
|
+
return;
|
|
9266
|
+
}
|
|
9267
|
+
const chunks = [];
|
|
9268
|
+
res.on("data", (c) => chunks.push(c));
|
|
9269
|
+
res.on("end", () => resolve({
|
|
9270
|
+
status,
|
|
9271
|
+
body: Buffer.concat(chunks)
|
|
9272
|
+
}));
|
|
9273
|
+
});
|
|
9274
|
+
req.on("timeout", () => req.destroy(/* @__PURE__ */ new Error(`下载超时(60s): ${url}`)));
|
|
9275
|
+
req.on("error", reject);
|
|
9276
|
+
});
|
|
9277
|
+
}
|
|
9278
|
+
async function downloadTo(url, file, what) {
|
|
9279
|
+
const { status, body } = await httpGet(url);
|
|
9280
|
+
if (status !== 200 || body.length === 0) throw new Error(`${what}下载失败(HTTP ${status}): ${url}\n若 sourcemap 被剥离,降级:按 JS 文件名走 bfi-code-search 内容搜索定位`);
|
|
9281
|
+
fs.writeFileSync(file, body);
|
|
9282
|
+
return body.length;
|
|
9283
|
+
}
|
|
9284
|
+
function cacheDirFor(jsUrl, root) {
|
|
9285
|
+
const u = new URL(jsUrl);
|
|
9286
|
+
return path$1.join(root || path$1.join(os$1.homedir(), ".bfi", "fe-locate", "cache"), u.host, path$1.dirname(u.pathname).replace(/^\/|\/$/g, ""));
|
|
9287
|
+
}
|
|
9288
|
+
function readStdin() {
|
|
9289
|
+
return new Promise((resolve, reject) => {
|
|
9290
|
+
let data = "";
|
|
9291
|
+
process.stdin.setEncoding("utf-8");
|
|
9292
|
+
process.stdin.on("data", (c) => {
|
|
9293
|
+
data += c;
|
|
9294
|
+
});
|
|
9295
|
+
process.stdin.on("end", () => resolve(data));
|
|
9296
|
+
process.stdin.on("error", reject);
|
|
9297
|
+
});
|
|
9298
|
+
}
|
|
9299
|
+
function parseSourceMappingComment(jsContent) {
|
|
9300
|
+
const m = jsContent.slice(-3e3).match(/\/\/# sourceMappingURL=(\S+)/);
|
|
9301
|
+
if (!m) throw new Error("JS 文件末尾未找到 //# sourceMappingURL= 注释(构建未产出 sourcemap)");
|
|
9302
|
+
return m[1];
|
|
9303
|
+
}
|
|
9304
|
+
async function locateInMap(mapFile, line, col) {
|
|
9305
|
+
const consumer = await new (loadSourceMapConsumer())(JSON.parse(fs.readFileSync(mapFile, "utf-8")));
|
|
9306
|
+
try {
|
|
9307
|
+
let pos = consumer.originalPositionFor({
|
|
9308
|
+
line,
|
|
9309
|
+
column: Math.max(0, col - 1)
|
|
9310
|
+
});
|
|
9311
|
+
if (!pos?.source) pos = consumer.originalPositionFor({
|
|
9312
|
+
line,
|
|
9313
|
+
column: col
|
|
9314
|
+
});
|
|
9315
|
+
if (!pos?.source) throw new Error(`sourcemap 中无该位置映射(line=${line}, col=${col})——JS 与 map 版本可能不一致`);
|
|
9316
|
+
const content = pos.source ? consumer.sourceContentFor(pos.source, true) : null;
|
|
9317
|
+
return {
|
|
9318
|
+
source: pos.source,
|
|
9319
|
+
line: pos.line ?? 0,
|
|
9320
|
+
column: pos.column ?? 0,
|
|
9321
|
+
name: pos.name ?? "",
|
|
9322
|
+
content
|
|
9323
|
+
};
|
|
9324
|
+
} finally {
|
|
9325
|
+
consumer.destroy?.();
|
|
9326
|
+
}
|
|
9327
|
+
}
|
|
9328
|
+
function printHelp() {
|
|
9329
|
+
console.log(`bfi fe-locate — 前端错误定位(sourcemap 还原)
|
|
9330
|
+
|
|
9331
|
+
用法:
|
|
9332
|
+
bfi fe-locate "<错误日志>" # 单参数(控制台报错栈粘贴)
|
|
9333
|
+
cat error.log | bfi fe-locate --stdin # 长日志走 stdin(推荐,免转义)
|
|
9334
|
+
|
|
9335
|
+
选项:
|
|
9336
|
+
--json 结构化 JSON 输出(定位结果+全部帧+上下文)
|
|
9337
|
+
--stdin 从标准输入读取错误日志
|
|
9338
|
+
--context <N> 源码上下文行数(默认 5)
|
|
9339
|
+
--frame <N> 指定第 N 帧定位(1-based;默认智能选帧:extend 业务帧优先)
|
|
9340
|
+
--no-cache 强制重新下载 JS/sourcemap
|
|
9341
|
+
|
|
9342
|
+
示例:
|
|
9343
|
+
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)"`);
|
|
9344
|
+
}
|
|
9345
|
+
function printResult(r, ctxLines) {
|
|
9346
|
+
const err = r.error;
|
|
9347
|
+
const mdf = r.mdf;
|
|
9348
|
+
console.log("═══ bfi fe-locate — 前端错误定位 ═══\n");
|
|
9349
|
+
if (mdf) console.log(`【MDF 头】${mdf.code} | 单据:${mdf.billName} | 类型:${mdf.errorKind} | diworkCode:${mdf.diworkCode} | 方法:${mdf.method} | 事件:${mdf.event}`);
|
|
9350
|
+
console.log(`【错误】${err.type}: ${err.message}`);
|
|
9351
|
+
const frames = r.frames;
|
|
9352
|
+
const sel = r.selected;
|
|
9353
|
+
console.log(`【选帧】第 ${sel.index} 帧(${sel.reason}): ${path$1.basename(String(sel.url))}:${sel.line}:${sel.col}(共 ${frames.length} 帧)`);
|
|
9354
|
+
console.log(`【压缩 JS】${r.jsUrl}`);
|
|
9355
|
+
const resolved = r.resolved;
|
|
9356
|
+
if (resolved) {
|
|
9357
|
+
console.log("\n── sourcemap 还原 ──");
|
|
9358
|
+
console.log(`原始文件: ${resolved.source}`);
|
|
9359
|
+
const info = resolved.webpack;
|
|
9360
|
+
if (info.repo) console.log(`包内仓库: ${info.repo}(注:包内仓库名≠GitLab 仓库名,远程定位按文件名搜索)`);
|
|
9361
|
+
console.log(`文件路径: ${info.filePath}`);
|
|
9362
|
+
console.log(`位置: 行 ${resolved.line}, 列 ${resolved.column}`);
|
|
9363
|
+
if (resolved.name) console.log(`函数名: ${resolved.name}`);
|
|
9364
|
+
if (ctxLines) {
|
|
9365
|
+
console.log(`\n── 源码上下文(sourcemap sourcesContent,±${r.context} 行)──`);
|
|
9366
|
+
console.log(ctxLines);
|
|
9367
|
+
}
|
|
9368
|
+
console.log("\n── 下一步 ──");
|
|
9369
|
+
console.log(`· 远程仓库: SourceGraph 精确文件名搜索 "${path$1.basename(info.filePath)}"(bfi-code-search)`);
|
|
9370
|
+
console.log("· 归因分析: 对照 bfi-diagnose-engine references/fe-error-attribution.md");
|
|
9371
|
+
console.log("· 数据格式类根因(接口返回变化)→ deep 档联动 bfi ymc ingress/trace 取证");
|
|
9372
|
+
}
|
|
9373
|
+
console.log(`\n缓存: ${r.cacheDir}`);
|
|
9374
|
+
}
|
|
9375
|
+
async function runFeLocate(args, globalOpts) {
|
|
9376
|
+
if (!args.length || flag(args, "--help") || flag(args, "-h")) {
|
|
9377
|
+
printHelp();
|
|
9378
|
+
return;
|
|
9379
|
+
}
|
|
9380
|
+
const jsonMode = flag(args, "--json");
|
|
9381
|
+
const noCache = flag(args, "--no-cache");
|
|
9382
|
+
const ctxN = Number(opt(args, "--context") ?? 5) || 5;
|
|
9383
|
+
const frameN = opt(args, "--frame");
|
|
9384
|
+
let log = positionals(args)[0] ?? "";
|
|
9385
|
+
if (!log && flag(args, "--stdin")) log = await readStdin();
|
|
9386
|
+
if (!log && !process.stdin.isTTY) log = await readStdin();
|
|
9387
|
+
if (!log.trim()) {
|
|
9388
|
+
console.error("缺少错误日志参数。粘贴控制台报错栈,或 cat error.log | bfi fe-locate --stdin");
|
|
9389
|
+
process.exit(1);
|
|
9390
|
+
}
|
|
9391
|
+
const parsed = parseErrorLog(log);
|
|
9392
|
+
if (parsed.frames.length === 0) {
|
|
9393
|
+
console.error("未在日志中找到压缩 JS 帧(https://...xxx.min.js:行:列)。请确认粘贴的是浏览器控制台报错栈。");
|
|
9394
|
+
process.exit(1);
|
|
9395
|
+
}
|
|
9396
|
+
const selIdx = frameN ? Math.max(0, Number(frameN) - 1) : pickFrame(parsed.frames);
|
|
9397
|
+
const selReason = frameN ? "--frame 指定" : parsed.frames[selIdx] && /\/extend\.\d+\./.test(parsed.frames[selIdx].url) ? "extend 业务帧优先" : "默认第一帧";
|
|
9398
|
+
if (selIdx >= parsed.frames.length) {
|
|
9399
|
+
console.error(`--frame ${frameN} 超出范围(共 ${parsed.frames.length} 帧)`);
|
|
9400
|
+
process.exit(1);
|
|
9401
|
+
}
|
|
9402
|
+
const frame = parsed.frames[selIdx];
|
|
9403
|
+
const cDir = cacheDirFor(frame.url, globalOpts["fe-cache-dir"]);
|
|
9404
|
+
fs.mkdirSync(cDir, { recursive: true });
|
|
9405
|
+
const jsFile = path$1.join(cDir, "error.min.js");
|
|
9406
|
+
const mapFile = path$1.join(cDir, "error.min.js.map");
|
|
9407
|
+
const metaFile = path$1.join(cDir, "meta.json");
|
|
9408
|
+
let sourceMappingURL = "";
|
|
9409
|
+
if (!noCache && fs.existsSync(metaFile)) try {
|
|
9410
|
+
sourceMappingURL = JSON.parse(fs.readFileSync(metaFile, "utf-8")).sourceMappingUrl ?? "";
|
|
9411
|
+
} catch {}
|
|
9412
|
+
if (sourceMappingURL && fs.existsSync(mapFile)) console.error(`[缓存命中] ${cDir}`);
|
|
9413
|
+
else {
|
|
9414
|
+
const jsSize = await downloadTo(frame.url, jsFile, "压缩 JS");
|
|
9415
|
+
console.error(`[下载] 压缩 JS(${(jsSize / 1024).toFixed(0)} KB)`);
|
|
9416
|
+
const comment = parseSourceMappingComment(fs.readFileSync(jsFile, "utf-8"));
|
|
9417
|
+
sourceMappingURL = resolveSourceMappingUrl(frame.url, comment);
|
|
9418
|
+
console.error(`[解析] sourceMappingURL → ${sourceMappingURL}`);
|
|
9419
|
+
const mapSize = await downloadTo(sourceMappingURL, mapFile, "sourcemap");
|
|
9420
|
+
console.error(`[下载] sourcemap(${(mapSize / 1024).toFixed(0)} KB)`);
|
|
9421
|
+
fs.writeFileSync(metaFile, JSON.stringify({
|
|
9422
|
+
jsUrl: frame.url,
|
|
9423
|
+
sourceMappingUrl: sourceMappingURL,
|
|
9424
|
+
downloadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9425
|
+
}, null, 2));
|
|
9426
|
+
}
|
|
9427
|
+
const located = await locateInMap(mapFile, frame.line, frame.col);
|
|
9428
|
+
const webpack = webpackSourceInfo(located.source);
|
|
9429
|
+
const ctxLines = located.content ? extractContext(located.content, located.line, ctxN) : null;
|
|
9430
|
+
const result = {
|
|
9431
|
+
error: {
|
|
9432
|
+
type: parsed.errorType,
|
|
9433
|
+
message: parsed.errorMessage
|
|
9434
|
+
},
|
|
9435
|
+
mdf: parsed.mdf,
|
|
9436
|
+
frames: parsed.frames,
|
|
9437
|
+
selected: {
|
|
9438
|
+
...frame,
|
|
9439
|
+
index: selIdx + 1,
|
|
9440
|
+
reason: selReason
|
|
9441
|
+
},
|
|
9442
|
+
jsUrl: frame.url,
|
|
9443
|
+
sourceMappingURL,
|
|
9444
|
+
resolved: {
|
|
9445
|
+
source: located.source,
|
|
9446
|
+
webpack,
|
|
9447
|
+
line: located.line,
|
|
9448
|
+
column: located.column,
|
|
9449
|
+
name: located.name
|
|
9450
|
+
},
|
|
9451
|
+
context: ctxN,
|
|
9452
|
+
contextText: ctxLines,
|
|
9453
|
+
cacheDir: cDir,
|
|
9454
|
+
locatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9455
|
+
};
|
|
9456
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14);
|
|
9457
|
+
const runDir = path$1.join(cDir, stamp);
|
|
9458
|
+
fs.mkdirSync(runDir, { recursive: true });
|
|
9459
|
+
fs.writeFileSync(path$1.join(runDir, "error.log"), log);
|
|
9460
|
+
fs.writeFileSync(path$1.join(runDir, "result.json"), JSON.stringify(result, null, 2));
|
|
9461
|
+
if (jsonMode) {
|
|
9462
|
+
console.log(JSON.stringify(result, null, 2));
|
|
9463
|
+
return;
|
|
9464
|
+
}
|
|
9465
|
+
printResult(result, ctxLines);
|
|
9466
|
+
console.log(`\n结果已存: ${path$1.join(runDir, "result.json")}`);
|
|
9467
|
+
}
|
|
8987
9468
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8988
9469
|
function getVersion() {
|
|
8989
9470
|
try {
|
|
@@ -9374,6 +9855,12 @@ async function main() {
|
|
|
9374
9855
|
case "ymc":
|
|
9375
9856
|
await runYmc(restArgs, globalOpts);
|
|
9376
9857
|
break;
|
|
9858
|
+
case "fe-locate":
|
|
9859
|
+
await runFeLocate(restArgs, globalOpts);
|
|
9860
|
+
break;
|
|
9861
|
+
case "event":
|
|
9862
|
+
await runEvent(restArgs, globalOpts);
|
|
9863
|
+
break;
|
|
9377
9864
|
case "auth":
|
|
9378
9865
|
if (restArgs[0] === "logout") await runLogout();
|
|
9379
9866
|
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.76",
|
|
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
|
}
|