bfi-cli 0.1.74 → 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.
Files changed (2) hide show
  1. package/dist/cli.mjs +300 -4
  2. 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$1();
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$1();
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$1();
8940
8941
  process.exit(1);
8941
8942
  }
8942
8943
  }
8943
- function printHelp() {
8944
+ function printHelp$1() {
8944
8945
  console.log(`
8945
8946
  bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
8946
8947
 
@@ -8984,6 +8985,298 @@ bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
8984
8985
  详细帮助:bfi ymc help <命令>(或 bfi ymc <命令> -h)——含参数说明/默认值/示例。
8985
8986
  `);
8986
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
+ }
8987
9280
  const __dirname = dirname(fileURLToPath(import.meta.url));
8988
9281
  function getVersion() {
8989
9282
  try {
@@ -9374,6 +9667,9 @@ async function main() {
9374
9667
  case "ymc":
9375
9668
  await runYmc(restArgs, globalOpts);
9376
9669
  break;
9670
+ case "fe-locate":
9671
+ await runFeLocate(restArgs, globalOpts);
9672
+ break;
9377
9673
  case "auth":
9378
9674
  if (restArgs[0] === "logout") await runLogout();
9379
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.74",
3
+ "version": "0.1.75",
4
4
  "description": "用友大业财BFI统一命令行工具,支持技能,技能包,元数据,UI元数据等",
5
5
  "type": "module",
6
6
  "bin": {
7
- "bfi": "./bin/bfi.js",
8
- "bfi-db-mcp": "./bin/bfi-db-mcp.js"
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
  }