@p-moon/yue-cli 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +297 -12
  2. package/dist/bin/yue.js +1484 -62
  3. package/package.json +1 -1
package/dist/bin/yue.js CHANGED
@@ -145,6 +145,50 @@ async function withBrowser(fn, opts) {
145
145
  });
146
146
  }
147
147
  }
148
+ async function withBrowserAutoDisconnect(fn, opts) {
149
+ return withBrowser(async (page) => {
150
+ const ac = new AbortController();
151
+ const trigger = () => {
152
+ if (!ac.signal.aborted) {
153
+ ac.abort(new Error("Browser connection lost (page closed or extension disconnected)"));
154
+ }
155
+ };
156
+ const attachers = [];
157
+ try {
158
+ const handler = () => trigger();
159
+ if (typeof page?.on === "function") {
160
+ page.on("close", handler);
161
+ page.on("disconnected", handler);
162
+ page.on("crash", handler);
163
+ attachers.push(() => {
164
+ try {
165
+ page.off?.("close", handler);
166
+ } catch {
167
+ }
168
+ try {
169
+ page.off?.("disconnected", handler);
170
+ } catch {
171
+ }
172
+ try {
173
+ page.off?.("crash", handler);
174
+ } catch {
175
+ }
176
+ });
177
+ }
178
+ } catch {
179
+ }
180
+ try {
181
+ return await fn(page, trigger, ac.signal);
182
+ } finally {
183
+ while (attachers.length) {
184
+ try {
185
+ attachers.pop()();
186
+ } catch {
187
+ }
188
+ }
189
+ }
190
+ }, opts);
191
+ }
148
192
 
149
193
  // src/lib/mydb-api.ts
150
194
  var BASE_URL = "https://mydb.jdfmgt.com";
@@ -210,11 +254,11 @@ async function executeSql(page, sql, dbId, pageNum = 1, limit = 25) {
210
254
 
211
255
  // src/commands/datasources.ts
212
256
  function registerDatasourcesCommand(program) {
213
- program.command("datasources").description("List available data sources on mydb").option("--filter <keyword>", "Filter data sources by keyword").option("--output <format>", "Output format: table (default) or json", "table").action(async (opts) => {
257
+ program.command("datasources").description("\u5217\u51FA mydb \u53EF\u7528\u6570\u636E\u6E90").option("--filter <keyword>", "\u6309\u5173\u952E\u5B57\u8FC7\u6EE4\u6570\u636E\u6E90").option("--output <format>", "\u8F93\u51FA\u683C\u5F0F\uFF1Atable\uFF08\u9ED8\u8BA4\uFF09/ json", "table").action(async (opts) => {
214
258
  await withBrowser(async (page) => {
215
259
  const sources = await fetchDatasources(page, opts.filter);
216
260
  if (!sources || sources.length === 0) {
217
- console.log("No data sources found.");
261
+ console.log("\u672A\u627E\u5230\u6570\u636E\u6E90\u3002");
218
262
  return;
219
263
  }
220
264
  if ((opts.output ?? "table").toLowerCase() === "json") {
@@ -231,7 +275,7 @@ function registerDatasourcesCommand(program) {
231
275
  }
232
276
  console.log(table.toString());
233
277
  console.log(`
234
- ${sources.length} data source(s)`);
278
+ \u5171 ${sources.length} \u4E2A\u6570\u636E\u6E90`);
235
279
  });
236
280
  });
237
281
  }
@@ -239,7 +283,7 @@ ${sources.length} data source(s)`);
239
283
  // src/commands/query.ts
240
284
  import Table2 from "cli-table3";
241
285
  function registerQueryCommand(program) {
242
- program.command("query").description("Execute a SQL query on mydb").requiredOption("--sql <sql>", "SQL statement to execute").option("--dbId <id>", "Data source ID", parseInt).option("--dbName <name>", "Data source name (fuzzy match, alternative to --dbId)").option("--page <n>", "Page number (default: 1)", parseInt, 1).option("--limit <n>", "Rows per page (default: 25)", parseInt, 25).option("--output <format>", "Output format: table (default) or json", "table").action(async (opts) => {
286
+ program.command("query").description("\u5728 mydb \u4E0A\u6267\u884C SQL \u67E5\u8BE2").requiredOption("--sql <sql>", "\u8981\u6267\u884C\u7684 SQL \u8BED\u53E5\uFF08\u5FC5\u586B\uFF09").option("--dbId <id>", "\u6570\u636E\u6E90 ID", parseInt).option("--dbName <name>", "\u6570\u636E\u6E90\u540D\u79F0\uFF08\u6A21\u7CCA\u5339\u914D\uFF0C\u53EF\u66FF\u4EE3 --dbId\uFF09").option("--page <n>", "\u9875\u7801\uFF08\u9ED8\u8BA4\uFF1A1\uFF09", parseInt, 1).option("--limit <n>", "\u6BCF\u9875\u884C\u6570\uFF08\u9ED8\u8BA4\uFF1A25\uFF09", parseInt, 25).option("--output <format>", "\u8F93\u51FA\u683C\u5F0F\uFF1Atable\uFF08\u9ED8\u8BA4\uFF09/ json", "table").action(async (opts) => {
243
287
  const sql = opts.sql;
244
288
  let dbId = opts.dbId;
245
289
  const dbName = opts.dbName;
@@ -247,7 +291,7 @@ function registerQueryCommand(program) {
247
291
  const limitNum = opts.limit ?? 25;
248
292
  const format = (opts.output ?? "table").toLowerCase();
249
293
  if (!sql?.trim()) {
250
- console.error("Error: --sql is required");
294
+ console.error("\u9519\u8BEF\uFF1A\u5FC5\u987B\u63D0\u4F9B --sql");
251
295
  process.exit(1);
252
296
  }
253
297
  await withBrowser(async (page) => {
@@ -255,8 +299,8 @@ function registerQueryCommand(program) {
255
299
  const sources = await fetchDatasources(page);
256
300
  const match = resolveDbIdByName(sources, dbName);
257
301
  if (!match) {
258
- console.error(`Error: Data source "${dbName}" not found.`);
259
- console.error('Run "yue mydb datasources" to list available sources.');
302
+ console.error(`\u9519\u8BEF\uFF1A\u672A\u627E\u5230\u6570\u636E\u6E90 "${dbName}"\u3002`);
303
+ console.error('\u8BF7\u8FD0\u884C "yue mydb datasources" \u67E5\u770B\u53EF\u7528\u6570\u636E\u6E90\u3002');
260
304
  process.exit(1);
261
305
  }
262
306
  dbId = match.id;
@@ -264,16 +308,16 @@ function registerQueryCommand(program) {
264
308
  if (!dbId) {
265
309
  const sources = await fetchDatasources(page);
266
310
  if (sources.length === 0) {
267
- console.error("No data sources available. Make sure you are logged in.");
311
+ console.error("\u6CA1\u6709\u53EF\u7528\u7684\u6570\u636E\u6E90\uFF0C\u8BF7\u786E\u8BA4\u4F60\u5DF2\u5728\u6D4F\u89C8\u5668\u4E2D\u767B\u5F55 mydb\u3002");
268
312
  process.exit(1);
269
313
  }
270
314
  const listText = sources.map((s, i) => ` ${i + 1}. [${s.id}] ${s.name}`).join("\n");
271
315
  const answer = await page.evaluate((msg) => {
272
316
  return window.prompt(msg, "");
273
- }, `Select a data source (enter number, dbId, or name):
317
+ }, `\u8BF7\u9009\u62E9\u4E00\u4E2A\u6570\u636E\u6E90\uFF08\u8F93\u5165\u5E8F\u53F7 / dbId / \u540D\u79F0\uFF09\uFF1A
274
318
  ${listText}`);
275
319
  if (!answer) {
276
- console.error("No data source selected.");
320
+ console.error("\u672A\u9009\u62E9\u6570\u636E\u6E90\u3002");
277
321
  process.exit(1);
278
322
  }
279
323
  const trimmed = String(answer).trim();
@@ -285,7 +329,7 @@ ${listText}`);
285
329
  } else {
286
330
  const match = resolveDbIdByName(sources, trimmed);
287
331
  if (!match) {
288
- console.error(`No data source matching "${trimmed}".`);
332
+ console.error(`\u672A\u627E\u5230\u5339\u914D\u7684\u6570\u636E\u6E90 "${trimmed}"\u3002`);
289
333
  process.exit(1);
290
334
  }
291
335
  dbId = match.id;
@@ -323,7 +367,6 @@ ${result.tip}`);
323
367
  import Table3 from "cli-table3";
324
368
 
325
369
  // src/lib/digger-api.ts
326
- import * as crypto2 from "node:crypto";
327
370
  var BASE_URL2 = "https://digger.jd.com";
328
371
  function diggerXhr(page, method, url, body) {
329
372
  return page.evaluate((args) => {
@@ -366,52 +409,133 @@ async function fuzzySearchApps(page, appFuzzy) {
366
409
  online: r.online ?? true
367
410
  }));
368
411
  }
369
- async function searchLogs(page, opts) {
370
- const now = opts.endTime ?? Date.now();
371
- const oneHourAgo = opts.startTime ?? now - 60 * 60 * 1e3;
372
- const body = {
373
- apps: [opts.appName],
374
- startTime: oneHourAgo,
375
- endTime: now,
376
- keyword: opts.keyword,
377
- exclude: "",
378
- filePaths: [],
379
- logLevel: opts.logLevel ?? "ALL",
380
- limit: opts.limit ?? 50,
381
- logSize: "",
382
- station: "chinaStation",
383
- searchType: opts.searchType ?? "exact",
384
- productName: "app",
385
- direction: "backward",
386
- rentionPolicy: "LOKI",
387
- dirEnabled: false,
388
- showPlain: false,
389
- searchId: crypto2.randomUUID(),
390
- searchIndex: 0,
391
- searchEndTimeStr: null,
392
- coefficient: null
393
- };
394
- const url = `${BASE_URL2}/log/search/all`;
395
- const result = await diggerXhr(page, "POST", url, body);
412
+ async function listAppIpsV2(page, appName, sTime, eTime) {
413
+ const params = new URLSearchParams({ appName, ipOrPodName: "" });
414
+ if (sTime !== void 0) params.set("sTime", String(sTime));
415
+ if (eTime !== void 0) params.set("eTime", String(eTime));
416
+ const url = `${BASE_URL2}/app/ip?${params.toString()}`;
417
+ const result = await diggerXhr(page, "GET", url);
396
418
  if (!result.ok) {
397
- throw new Error(`Digger log search error: HTTP ${result.status} \u2014 ${JSON.stringify(result.body)}`);
419
+ throw new Error(`Digger app/ip error: HTTP ${result.status} \u2014 ${JSON.stringify(result.body)}`);
398
420
  }
399
- const json = result.body;
400
- const tData = json.tData ?? {};
401
- const logArray = Array.isArray(tData) ? tData : tData.data ?? json.data ?? [];
402
- const total = tData.total ?? json.total ?? logArray.length;
403
- const entries = logArray.map((item) => ({
404
- timestamp: item.timestamp ?? "",
405
- level: item.level ?? "INFO",
406
- message: item.m_s_g ?? item.message ?? item.msg ?? item.log ?? "",
407
- host: item.host ?? "",
408
- filePath: item.filePath ?? "",
409
- app: item.app ?? "",
410
- thread: item.t_h_r ?? "",
411
- className: item.c_l_s ?? "",
412
- traceId: item.traceId ?? item.pfinderTraceId ?? null
421
+ const list = Array.isArray(result.body) ? result.body : [];
422
+ return list.map((r) => ({
423
+ ip: r.ip ?? "",
424
+ type: r.type ?? "",
425
+ podName: r.podName ?? "",
426
+ show: r.show ?? "",
427
+ ipOrPodName: r.ipOrPodName ?? "",
428
+ groupName: r.groupName ?? ""
413
429
  }));
414
- return { entries, total };
430
+ }
431
+ async function listAppFiles(page, appName, containAll = false) {
432
+ const url = `${BASE_URL2}/app/files/new?appName=${encodeURIComponent(appName)}&containAll=${containAll}`;
433
+ const result = await diggerXhr(page, "GET", url);
434
+ if (!result.ok) {
435
+ throw new Error(`Digger app/files/new error: HTTP ${result.status} \u2014 ${JSON.stringify(result.body)}`);
436
+ }
437
+ return Array.isArray(result.body) ? result.body : [];
438
+ }
439
+ async function localLogTaskCommit(page, opts) {
440
+ const params = new URLSearchParams();
441
+ params.set("appName", opts.appName);
442
+ params.set("file", opts.file);
443
+ params.set("grep", opts.grep ?? "");
444
+ params.set("grep_v", opts.grepV ?? "");
445
+ params.set("maxMatchRow", String(opts.maxMatchRow ?? 50));
446
+ params.set("lineOffsetBefore", String(opts.lineOffsetBefore ?? 0));
447
+ params.set("lineOffsetAfter", String(opts.lineOffsetAfter ?? 0));
448
+ params.set("grepDirection", String(opts.grepDirection ?? true));
449
+ params.set("searchScope", opts.searchScope ?? "100M");
450
+ params.set("showPlain", String(opts.showPlain ?? false));
451
+ if (opts.selectIp) params.set("selectIp", opts.selectIp);
452
+ const url = `${BASE_URL2}/log/local/task/commit`;
453
+ const result = await page.evaluate((args) => {
454
+ return new Promise((resolve) => {
455
+ const xhr = new XMLHttpRequest();
456
+ xhr.open("POST", args.url, true);
457
+ xhr.withCredentials = true;
458
+ xhr.setRequestHeader("Accept", "application/json, text/plain, */*");
459
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
460
+ xhr.onload = () => {
461
+ try {
462
+ resolve({ ok: xhr.status >= 200 && xhr.status < 300, status: xhr.status, body: JSON.parse(xhr.responseText) });
463
+ } catch {
464
+ resolve({ ok: xhr.status >= 200 && xhr.status < 300, status: xhr.status, body: xhr.responseText });
465
+ }
466
+ };
467
+ xhr.onerror = () => resolve({ ok: false, status: 0, body: "Network error" });
468
+ xhr.send(args.body);
469
+ });
470
+ }, { url, body: params.toString() });
471
+ if (!result.ok) {
472
+ throw new Error(`Digger local task commit error: HTTP ${result.status} \u2014 ${JSON.stringify(result.body)}`);
473
+ }
474
+ return result.body;
475
+ }
476
+ async function localLogTaskResult(page, taskId) {
477
+ const url = `${BASE_URL2}/log/local/task/result?task_id=${taskId}`;
478
+ const result = await diggerXhr(page, "GET", url);
479
+ if (!result.ok) {
480
+ throw new Error(`Digger local task result error: HTTP ${result.status} \u2014 ${JSON.stringify(result.body)}`);
481
+ }
482
+ return result.body;
483
+ }
484
+ async function localLogTaskContent(page, taskId, ip, fileName, showPlain = false) {
485
+ const params = new URLSearchParams({
486
+ task_id: String(taskId),
487
+ ip,
488
+ fileName,
489
+ showPlain: String(showPlain)
490
+ });
491
+ const url = `${BASE_URL2}/log/local/task/content?${params.toString()}`;
492
+ const result = await diggerXhr(page, "GET", url);
493
+ if (!result.ok) {
494
+ throw new Error(`Digger local task content error: HTTP ${result.status} \u2014 ${JSON.stringify(result.body)}`);
495
+ }
496
+ return Array.isArray(result.body) ? result.body : [];
497
+ }
498
+ async function localLogSearch(page, opts, maxPollMs = 3e4) {
499
+ const task = await localLogTaskCommit(page, opts);
500
+ const start = Date.now();
501
+ let taskResult = {};
502
+ while (Date.now() - start < maxPollMs) {
503
+ taskResult = await localLogTaskResult(page, task.id);
504
+ const allDone = Object.values(taskResult).every(
505
+ (entries2) => entries2.every((e) => e.taskStatus === 0 || e.success)
506
+ );
507
+ if (allDone) break;
508
+ await new Promise((r) => setTimeout(r, 1e3));
509
+ }
510
+ let bestIp = "";
511
+ let bestCount = 0;
512
+ for (const [ip, entries2] of Object.entries(taskResult)) {
513
+ for (const e of entries2) {
514
+ if (e.success && e.logCount > bestCount) {
515
+ bestCount = e.logCount;
516
+ bestIp = ip;
517
+ }
518
+ }
519
+ }
520
+ let entries = [];
521
+ if (bestIp) {
522
+ entries = await localLogTaskContent(
523
+ page,
524
+ task.id,
525
+ bestIp,
526
+ opts.file,
527
+ opts.showPlain ?? false
528
+ );
529
+ }
530
+ return { entries, ip: bestIp, taskResult };
531
+ }
532
+ async function listAppIps(page, opts) {
533
+ const url = `${BASE_URL2}/overview/appIp/list?productName=${encodeURIComponent(opts.productName)}&instanceName=${encodeURIComponent(opts.instanceName)}&start=${encodeURIComponent(String(opts.start))}&end=${encodeURIComponent(String(opts.end))}`;
534
+ const result = await diggerXhr(page, "GET", url);
535
+ if (!result.ok) {
536
+ throw new Error(`Digger appIp list error: HTTP ${result.status} \u2014 ${JSON.stringify(result.body)}`);
537
+ }
538
+ return result.body;
415
539
  }
416
540
 
417
541
  // src/commands/digger.ts
@@ -447,18 +571,61 @@ function colorizeLevel(level) {
447
571
  }
448
572
  }
449
573
  function registerDiggerCommand(program) {
450
- const digger = program.command("digger").description("Digger (joywatch.jd.com) log operations");
451
- digger.command("history").description("Search logs in Digger").option("--appName <prefix>", "Application name prefix for fuzzy match").option("--keyWord <keyword>", "Keyword to search in logs").option("--limit <n>", "Max log entries to return (default: 50)", parseInt, 50).option("--logLevel <level>", "Log level filter (default: ALL)", "ALL").option("--searchType <type>", "Search type: exact (keyword match, default) or regular (regex)", "exact").option("--startTime <datetime>", 'Start time (e.g. "2026-07-01 09:00:00" or "2026-07-01T09:00")').option("--endTime <datetime>", 'End time (e.g. "2026-07-01 18:00:00" or "2026-07-01T18:00")').option("--output <format>", "Output format: plain (default) or json", "plain").action(async (opts) => {
574
+ const digger = program.command("digger").description("Digger\uFF08joywatch.jd.com\uFF09\u65E5\u5FD7\u76F8\u5173\u64CD\u4F5C");
575
+ digger.command("history").description("\u67E5\u8BE2 Digger \u5386\u53F2\u65E5\u5FD7\uFF08\u57FA\u4E8E Loki \u7D22\u5F15\uFF09").addHelpText("after", `
576
+ \u4F7F\u7528\u793A\u4F8B\uFF1A
577
+ # \u5217\u51FA\u5339\u914D\u7684\u5E94\u7528
578
+ yue digger history --appName baoxian
579
+
580
+ # \u5173\u952E\u5B57\u641C\u7D22\uFF08\u5B8C\u5168\u5339\u914D\uFF09
581
+ yue digger history --appName baoxian-mall-trade --keyWord getUserAuthInfo
582
+
583
+ # \u6B63\u5219\u5339\u914D
584
+ yue digger history --appName baoxian-mall-trade --keyWord "getUserAuthInfo.*req" --searchType regular
585
+
586
+ # \u6307\u5B9A\u65F6\u95F4\u8303\u56F4
587
+ yue digger history --appName baoxian-mall-trade --keyWord getUserAuthInfo \\
588
+ --startTime "2026-07-01 09:00:00" --endTime "2026-07-01 18:00:00"
589
+
590
+ # \u8FC7\u6EE4\u65E5\u5FD7\u7EA7\u522B
591
+ yue digger history --appName baoxian-mall-trade --keyWord getUserAuthInfo --logLevel ERROR
592
+
593
+ # \u6392\u9664\u5173\u952E\u5B57
594
+ yue digger history --appName baoxian-mall-trade --keyWord Exception --exclude "pfinder,sgm"
595
+
596
+ # \u9650\u5236\u8FD4\u56DE\u6761\u6570
597
+ yue digger history --appName baoxian-mall-trade --keyWord getUserAuthInfo --limit 20
598
+
599
+ # JSON \u8F93\u51FA
600
+ yue digger history --appName baoxian-mall-trade --keyWord getUserAuthInfo --output json
601
+ `).option("--appName <prefix>", "\u5E94\u7528\u540D\u79F0/\u524D\u7F00\uFF08\u6A21\u7CCA\u5339\u914D\uFF0C\u7528\u4E8E\u9009\u62E9\u5E94\u7528\uFF09").option("--keyWord <keyword>", "\u641C\u7D22\u5173\u952E\u5B57\uFF08\u6216\u6B63\u5219\u8868\u8FBE\u5F0F\uFF0C\u53D6\u51B3\u4E8E --searchType\uFF09").option("--limit <n>", "\u8FD4\u56DE\u6761\u6570\uFF08\u9ED8\u8BA4\uFF1A50\uFF09", parseInt, 50).option("--logLevel <level>", "\u65E5\u5FD7\u7EA7\u522B\uFF08\u9ED8\u8BA4\uFF1AALL\uFF09", "ALL").option("--searchType <type>", "\u641C\u7D22\u7C7B\u578B\uFF1Aexact\uFF08\u5173\u952E\u5B57\u5339\u914D\uFF0C\u9ED8\u8BA4\uFF09/ regular\uFF08\u6B63\u5219\u5339\u914D\uFF09", "exact").option("--exclude <text>", "\u6392\u9664\u5173\u952E\u5B57\uFF08exclude\uFF09").option("--filePaths <paths>", "\u65E5\u5FD7\u6587\u4EF6\u8DEF\u5F84\u8FC7\u6EE4\uFF0C\u9017\u53F7\u5206\u9694\uFF08filePaths\uFF09").option("--logSize <size>", "\u65E5\u5FD7\u5927\u5C0F\u8FC7\u6EE4\uFF08logSize\uFF0C\u900F\u4F20\uFF09").option("--station <name>", "\u7AD9\u70B9\uFF08station\uFF0C\u9ED8\u8BA4\uFF1AchinaStation\uFF09", "chinaStation").option("--productName <name>", "\u4EA7\u54C1\u540D\uFF08productName\uFF0C\u9ED8\u8BA4\uFF1Aapp\uFF09", "app").option("--direction <dir>", "\u65B9\u5411\uFF08direction\uFF09\uFF1Abackward\uFF08\u9ED8\u8BA4\uFF09/ forward", "backward").option("--rentionPolicy <policy>", "\u5B58\u50A8\u7B56\u7565\uFF08rentionPolicy\uFF0C\u9ED8\u8BA4\uFF1ALOKI\uFF09", "LOKI").option("--dirEnabled <bool>", "dirEnabled\uFF08\u9ED8\u8BA4\uFF1Afalse\uFF09", "false").option("--showPlain <bool>", "showPlain\uFF08\u9ED8\u8BA4\uFF1Afalse\uFF09", "false").option("--searchId <id>", "searchId\uFF08\u53EF\u9009\uFF1B\u9ED8\u8BA4\u81EA\u52A8\u751F\u6210 uuid\uFF09").option("--searchIndex <n>", "searchIndex\uFF08\u9ED8\u8BA4\uFF1A0\uFF09", parseInt, 0).option("--searchEndTimeStr <str>", "searchEndTimeStr\uFF08\u9ED8\u8BA4\uFF1Anull\uFF09").option("--coefficient <n>", "coefficient\uFF08\u9ED8\u8BA4\uFF1Anull\uFF09", parseFloat).option("--startTime <datetime>", '\u5F00\u59CB\u65F6\u95F4\uFF08\u4F8B\u5982\uFF1A"2026-07-01 09:00:00" \u6216 "2026-07-01T09:00"\uFF09').option("--endTime <datetime>", '\u7ED3\u675F\u65F6\u95F4\uFF08\u4F8B\u5982\uFF1A"2026-07-01 18:00:00" \u6216 "2026-07-01T18:00"\uFF09').option("--output <format>", "\u8F93\u51FA\u683C\u5F0F\uFF1Aplain\uFF08\u9ED8\u8BA4\uFF09/ json", "plain").action(async (opts) => {
452
602
  const appFuzzy = opts.appName;
453
603
  const keyword = opts.keyWord;
454
604
  const limit = opts.limit ?? 50;
455
605
  const logLevel = opts.logLevel ?? "ALL";
456
606
  const searchType = (opts.searchType ?? "exact").toLowerCase();
457
607
  const format = (opts.output ?? "plain").toLowerCase();
608
+ const exclude = opts.exclude ?? "";
609
+ const filePaths = typeof opts.filePaths === "string" && opts.filePaths.trim() ? String(opts.filePaths).split(",").map((s) => s.trim()).filter(Boolean) : void 0;
610
+ const logSize = opts.logSize ?? "";
611
+ const station = opts.station ?? "chinaStation";
612
+ const productName = opts.productName ?? "app";
613
+ const direction = opts.direction ?? "backward";
614
+ const rentionPolicy = opts.rentionPolicy ?? "LOKI";
615
+ const dirEnabled = String(opts.dirEnabled ?? "false").toLowerCase() === "true";
616
+ const showPlain = String(opts.showPlain ?? "false").toLowerCase() === "true";
617
+ const searchId = opts.searchId ?? void 0;
618
+ const searchIndex = opts.searchIndex ?? 0;
619
+ const searchEndTimeStr = opts.searchEndTimeStr === void 0 ? void 0 : opts.searchEndTimeStr;
620
+ const coefficient = opts.coefficient === void 0 || opts.coefficient === null || Number.isNaN(opts.coefficient) ? void 0 : opts.coefficient;
458
621
  if (searchType !== "exact" && searchType !== "regular") {
459
622
  console.error(`\u274C Invalid --searchType: "${opts.searchType}". Use "exact" or "regular".`);
460
623
  process.exit(1);
461
624
  }
625
+ if (direction !== "backward" && direction !== "forward") {
626
+ console.error(`\u274C Invalid --direction: "${opts.direction}". Use "backward" or "forward".`);
627
+ process.exit(1);
628
+ }
462
629
  const startTime = opts.startTime ? new Date(opts.startTime).getTime() : void 0;
463
630
  const endTime = opts.endTime ? new Date(opts.endTime).getTime() : void 0;
464
631
  if (opts.startTime && isNaN(startTime)) {
@@ -543,7 +710,7 @@ function registerDiggerCommand(program) {
543
710
  }
544
711
  const desc = selectedApp.appDesc ? ` (${selectedApp.appDesc})` : "";
545
712
  console.log(`\u2705 Using app: ${selectedApp.appName}${desc}`);
546
- const logAppName = selectedApp.appName.replace(/-/g, ".");
713
+ const logAppName = selectedApp.appName;
547
714
  const timeDesc = startTime || endTime ? ` from ${opts.startTime || "1h ago"} to ${opts.endTime || "now"}` : "";
548
715
  console.log(`\u{1F50E} Searching logs for "${keyword}" in ${logAppName}${timeDesc}...`);
549
716
  let result;
@@ -551,9 +718,22 @@ function registerDiggerCommand(program) {
551
718
  result = await searchLogs(page, {
552
719
  appName: logAppName,
553
720
  keyword,
554
- limit,
555
- logLevel,
556
721
  searchType,
722
+ exclude,
723
+ filePaths,
724
+ logLevel,
725
+ limit,
726
+ logSize,
727
+ station,
728
+ productName,
729
+ direction,
730
+ rentionPolicy,
731
+ dirEnabled,
732
+ showPlain,
733
+ searchId,
734
+ searchIndex,
735
+ searchEndTimeStr: searchEndTimeStr === void 0 ? null : searchEndTimeStr,
736
+ coefficient: coefficient === void 0 ? null : coefficient,
557
737
  startTime,
558
738
  endTime
559
739
  });
@@ -582,16 +762,1258 @@ function registerDiggerCommand(program) {
582
762
  \u{1F4C4} ${result.entries.length} log entry(s) shown (total: ${result.total ?? result.entries.length})`);
583
763
  }, { targetUrl: "https://joywatch.jd.com/digger/techApp/overview" });
584
764
  });
765
+ digger.command("realtime").description("\u62C9\u53D6 Digger \u5B9E\u65F6\u65E5\u5FD7\uFF08\u6D41\u5F0F\u8F93\u51FA\uFF09").addHelpText("after", `
766
+ \u4F7F\u7528\u793A\u4F8B\uFF1A
767
+ # \u6D41\u5F0F\u62C9\u53D6\u5B9E\u65F6\u65E5\u5FD7\uFF08Ctrl+C \u505C\u6B62\uFF09
768
+ yue digger realtime --appName baoxian-bridgehead \\
769
+ --ip 100.99.232.222 \\
770
+ --file /export/log/jdos_kj_baoxian-bridgehead/catalina.out
771
+
772
+ # \u6307\u5B9A\u6700\u5927\u884C\u6570
773
+ yue digger realtime --appName baoxian-bridgehead \\
774
+ --ip 100.99.232.222 \\
775
+ --file /export/log/jdos_kj_baoxian-bridgehead/catalina.out \\
776
+ --maxLines 200
777
+
778
+ # JSON \u8F93\u51FA\uFF08\u4E00\u6B21\u6027\u83B7\u53D6\uFF09
779
+ yue digger realtime --appName baoxian-bridgehead \\
780
+ --ip 100.99.232.222 \\
781
+ --file /export/log/jdos_kj_baoxian-bridgehead/catalina.out \\
782
+ --output json
783
+ `).option("--appName <name>", "\u5E94\u7528\u540D\u79F0\uFF08\u4F8B\u5982\uFF1Abaoxian-bridgehead\uFF09").option("--ip <ip>", "\u5B9E\u4F8B IP\uFF08\u4F8B\u5982\uFF1A100.99.232.222\uFF09").option("--file <path>", "\u65E5\u5FD7\u6587\u4EF6\u8DEF\u5F84\uFF08\u4F8B\u5982\uFF1A/export/log/.../catalina.out\uFF09").option("--fileSizeByte <n>", "\u6587\u4EF6\u5927\u5C0F\uFF08fileSizeByte\uFF0C\u900F\u4F20\uFF09", (v) => parseInt(v, 10)).option("--maxLines <n>", "\u6700\u5927\u884C\u6570\uFF08maxLines\uFF0C\u9ED8\u8BA4\uFF1A100\uFF09", (v) => parseInt(v, 10), 100).option("--showPlain <bool>", "\u662F\u5426 plain \u8F93\u51FA\uFF08showPlain\uFF0C\u9ED8\u8BA4\uFF1Afalse\uFF09", "false").option("--productName <name>", "\u4EA7\u54C1\u540D\uFF08productName\uFF0C\u9ED8\u8BA4\uFF1Aapp\uFF09", "app").option("--source <name>", "\u6765\u6E90\uFF08source\uFF0C\u9ED8\u8BA4\uFF1Ajdos_kj\uFF09", "jdos_kj").option("--resourceSection <name>", "\u8D44\u6E90\u533A\uFF08resourceSection\uFF0C\u9ED8\u8BA4\uFF1AJDT\uFF09", "JDT").option("--output <format>", "\u8F93\u51FA\u683C\u5F0F\uFF1Aplain\uFF08\u9ED8\u8BA4\uFF09/ json", "plain").action(async (opts) => {
784
+ const format = (opts.output ?? "plain").toLowerCase();
785
+ const appName = opts.appName;
786
+ const ip = opts.ip;
787
+ const file = opts.file;
788
+ if (!appName || !ip || !file) {
789
+ console.error("\u274C \u53C2\u6570\u4E0D\u8DB3\uFF1A\u5FC5\u987B\u63D0\u4F9B --appName\u3001--ip\u3001--file");
790
+ process.exit(1);
791
+ }
792
+ const fileSizeByte = opts.fileSizeByte === void 0 || Number.isNaN(opts.fileSizeByte) ? void 0 : Number(opts.fileSizeByte);
793
+ const maxLines = opts.maxLines === void 0 || Number.isNaN(opts.maxLines) ? 100 : Number(opts.maxLines);
794
+ const showPlain = String(opts.showPlain ?? "false").toLowerCase() === "true";
795
+ const productName = opts.productName ?? "app";
796
+ const source = opts.source ?? "jdos_kj";
797
+ const resourceSection = opts.resourceSection ?? "JDT";
798
+ await withBrowserAutoDisconnect(async (page, _onDisconnect, signal) => {
799
+ try {
800
+ await page.waitForNetworkIdle?.();
801
+ } catch {
802
+ }
803
+ if (format === "json") {
804
+ const r = await realtimeLogs(page, {
805
+ appName,
806
+ ip,
807
+ file,
808
+ fileSizeByte,
809
+ maxLines,
810
+ showPlain,
811
+ productName,
812
+ source,
813
+ resourceSection
814
+ });
815
+ console.log(JSON.stringify(r ?? null, null, 2));
816
+ return;
817
+ }
818
+ console.log(`\u{1F534} Realtime logs: ${appName} ${ip} ${file}`);
819
+ console.log(`${DIM}\u6309 Ctrl+C \u7ED3\u675F\uFF08\u6216\u5173\u95ED\u6D4F\u89C8\u5668 tab \u81EA\u52A8\u505C\u6B62\uFF09${RESET}`);
820
+ try {
821
+ await realtimeLogs(
822
+ page,
823
+ {
824
+ appName,
825
+ ip,
826
+ file,
827
+ fileSizeByte,
828
+ maxLines,
829
+ showPlain,
830
+ productName,
831
+ source,
832
+ resourceSection
833
+ },
834
+ signal,
835
+ (chunk) => {
836
+ if (chunk.startsWith("__meta__:")) {
837
+ try {
838
+ const meta = JSON.parse(chunk.slice("__meta__:".length));
839
+ console.log(`${GRAY}HTTP ${meta.status} ${meta.contentType}${RESET}`);
840
+ } catch {
841
+ console.log(`${GRAY}${chunk}${RESET}`);
842
+ }
843
+ return;
844
+ }
845
+ process.stdout.write(chunk);
846
+ if (!chunk.endsWith("\n")) process.stdout.write("\n");
847
+ }
848
+ );
849
+ } catch (err) {
850
+ if (signal.aborted) {
851
+ console.log(`
852
+ ${GRAY}\u23F9 \u6D4F\u89C8\u5668 tab \u5DF2\u5173\u95ED/\u8FDE\u63A5\u65AD\u5F00\uFF0C\u5DF2\u505C\u6B62\u62C9\u53D6\u3002${RESET}`);
853
+ return;
854
+ }
855
+ throw err;
856
+ }
857
+ }, { targetUrl: "https://joywatch.jd.com/digger/techApp/overview" });
858
+ });
859
+ digger.command("app-ips").description("\u67E5\u8BE2\u5E94\u7528\u5B9E\u4F8B IP \u5217\u8868\u53CA\u5206\u7EC4\u4FE1\u606F\uFF08\u65E7\u7248\uFF0C\u63A8\u8350\u4F7F\u7528 ips\uFF09").addHelpText("after", `
860
+ \u4F7F\u7528\u793A\u4F8B\uFF1A
861
+ # \u67E5\u8BE2\u5B9E\u4F8B IP\uFF08\u9ED8\u8BA4\u6700\u8FD11\u5C0F\u65F6\uFF09
862
+ yue digger app-ips --appName baoxian-bridgehead
863
+
864
+ # \u6307\u5B9A\u4EA7\u54C1\u540D
865
+ yue digger app-ips --appName baoxian-bridgehead --productName _jdosApp_
866
+
867
+ # \u6307\u5B9A\u65F6\u95F4\u8303\u56F4\uFF08\u6BEB\u79D2\u65F6\u95F4\u6233\uFF09
868
+ yue digger app-ips --appName baoxian-bridgehead --start 1782891564642 --end 1782895164642
869
+
870
+ # JSON \u8F93\u51FA
871
+ yue digger app-ips --appName baoxian-bridgehead --output json
872
+ `).option("--appName <name>", "\u5E94\u7528\u540D\u79F0\uFF08\u5BF9\u5E94 instanceName\uFF0C\u4F8B\u5982\uFF1Abaoxian-bridgehead\uFF09").option("--productName <name>", "\u4EA7\u54C1\u540D\uFF08\u9ED8\u8BA4\uFF1A_jdosApp_\uFF09", "_jdosApp_").option("--start <ms>", "\u5F00\u59CB\u65F6\u95F4\uFF08\u6BEB\u79D2\u65F6\u95F4\u6233\uFF1B\u9ED8\u8BA4\uFF1A\u5F53\u524D\u65F6\u95F4-1\u5C0F\u65F6\uFF09", (v) => parseInt(v, 10)).option("--end <ms>", "\u7ED3\u675F\u65F6\u95F4\uFF08\u6BEB\u79D2\u65F6\u95F4\u6233\uFF1B\u9ED8\u8BA4\uFF1A\u5F53\u524D\u65F6\u95F4\uFF09", (v) => parseInt(v, 10)).option("--output <fmt>", "\u8F93\u51FA\u683C\u5F0F\uFF1Atable\uFF08\u9ED8\u8BA4\uFF09/ json", "table").action(async (opts) => {
873
+ const appName = opts.appName;
874
+ const productName = opts.productName ?? "_jdosApp_";
875
+ const output = (opts.output ?? "table").toLowerCase();
876
+ if (!appName) {
877
+ console.error("\u274C \u53C2\u6570\u4E0D\u8DB3\uFF1A\u5FC5\u987B\u63D0\u4F9B --appName");
878
+ process.exit(1);
879
+ }
880
+ const now = Date.now();
881
+ const start = Number.isFinite(opts.start) ? Number(opts.start) : now - 60 * 60 * 1e3;
882
+ const end = Number.isFinite(opts.end) ? Number(opts.end) : now;
883
+ await withBrowser(async (page) => {
884
+ try {
885
+ await page.waitForNetworkIdle?.();
886
+ } catch {
887
+ }
888
+ let data;
889
+ try {
890
+ data = await listAppIps(page, {
891
+ productName,
892
+ instanceName: appName,
893
+ start,
894
+ end
895
+ });
896
+ } catch (err) {
897
+ console.error(`\u274C \u67E5\u8BE2\u5931\u8D25: ${err.message}`);
898
+ process.exit(1);
899
+ }
900
+ if (output === "json") {
901
+ console.log(JSON.stringify(data, null, 2));
902
+ return;
903
+ }
904
+ const list = Array.isArray(data) ? data : data?.data ?? data?.result ?? data?.list ?? [];
905
+ if (!Array.isArray(list) || list.length === 0) {
906
+ console.log("\u{1F4ED} \u672A\u67E5\u8BE2\u5230\u5B9E\u4F8B IP\u3002");
907
+ return;
908
+ }
909
+ const table = new Table3({
910
+ head: ["ip", "group", "instanceName", "productName"],
911
+ style: { head: ["cyan"], border: ["gray"] },
912
+ wordWrap: true,
913
+ colWidths: [18, 24, 28, 16]
914
+ });
915
+ for (const item of list) {
916
+ const ip = item.ip ?? item.host ?? item.instanceIp ?? item.instanceIP ?? item.ipAddr ?? item.ipAddress ?? item.privateIp ?? item.privateIP ?? item.address ?? item.addr ?? item.nodeIp ?? item.nodeIP ?? item.podIp ?? item.podIP ?? item.machineIp ?? item.machineIP ?? "-";
917
+ table.push([
918
+ ip,
919
+ item.group ?? item.groupName ?? item.section ?? item.resourceSection ?? "-",
920
+ item.instanceName ?? item.appName ?? appName,
921
+ item.productName ?? productName
922
+ ]);
923
+ }
924
+ console.log(table.toString());
925
+ console.log(`
926
+ \u{1F4CB} \u5171 ${list.length} \u6761\u8BB0\u5F55\uFF08${new Date(start).toLocaleString("zh-CN", { hour12: false })} ~ ${new Date(end).toLocaleString("zh-CN", { hour12: false })}\uFF09`);
927
+ }, { targetUrl: "https://joywatch.jd.com/digger/techApp/overview" });
928
+ });
929
+ digger.command("local").description("\u67E5\u8BE2 Digger \u672C\u5730\u65E5\u5FD7\uFF08grep \u672C\u5730\u6587\u4EF6\uFF09").addHelpText("after", `
930
+ \u4F7F\u7528\u793A\u4F8B\uFF1A
931
+ # \u57FA\u672C\u641C\u7D22\uFF08\u81EA\u52A8\u68C0\u6D4B\u4E3B\u65E5\u5FD7\u6587\u4EF6\uFF09
932
+ yue digger local --appName baoxian-bridgehead-zx --grep error
933
+
934
+ # \u6307\u5B9A\u65E5\u5FD7\u6587\u4EF6
935
+ yue digger local --appName baoxian-bridgehead-zx \\
936
+ --file /export/log/baoxian-bridgehead-zx/catalina.out \\
937
+ --grep error
938
+
939
+ # \u6392\u9664\u5173\u952E\u5B57\uFF08\u9017\u53F7\u5206\u9694\uFF09
940
+ yue digger local --appName baoxian-bridgehead-zx --grep Exception --grep-v "pfinder,sgm"
941
+
942
+ # \u589E\u5927\u5339\u914D\u884C\u6570 + JSON \u8F93\u51FA
943
+ yue digger local --appName baoxian-bridgehead-zx --grep error --maxMatchRow 100 --output json
944
+
945
+ # \u53EA\u67E5\u7279\u5B9A IP
946
+ yue digger local --appName baoxian-bridgehead-zx --grep error --ip 100.99.57.237
947
+ `).option("--appName <name>", "\u5E94\u7528\u540D\u79F0\uFF08\u4F8B\u5982\uFF1Abaoxian-bridgehead-zx\uFF09").option("--file <path>", "\u65E5\u5FD7\u6587\u4EF6\u8DEF\u5F84\uFF08\u4F8B\u5982\uFF1A/export/log/baoxian-bridgehead-zx/catalina.out\uFF09\uFF1B\u4E0D\u6307\u5B9A\u65F6\u81EA\u52A8\u9009\u62E9\u4E3B\u65E5\u5FD7\u6587\u4EF6").option("--grep <keyword>", "grep \u5173\u952E\u8BCD\uFF08\u5927\u4E8E3\u4E2A\u5B57\u7B26\uFF09").option("--grep-v <keywords>", "grep -v \u6392\u9664\u5173\u952E\u8BCD\uFF08\u9017\u53F7\u5206\u9694\uFF09").option("--maxMatchRow <n>", "\u6700\u5927\u5339\u914D\u884C\u6570\uFF08\u9ED8\u8BA4\uFF1A50\uFF09", (v) => parseInt(v, 10), 50).option("--lineOffsetBefore <n>", "\u5339\u914D\u884C\u524D\u504F\u79FB\u884C\u6570\uFF08\u9ED8\u8BA4\uFF1A0\uFF09", (v) => parseInt(v, 10), 0).option("--lineOffsetAfter <n>", "\u5339\u914D\u884C\u540E\u504F\u79FB\u884C\u6570\uFF08\u9ED8\u8BA4\uFF1A0\uFF09", (v) => parseInt(v, 10), 0).option("--searchScope <scope>", "\u641C\u7D22\u8303\u56F4\uFF08\u9ED8\u8BA4\uFF1A100M\uFF09", "100M").option("--showPlain <bool>", "\u662F\u5426 plain \u8F93\u51FA\uFF08\u9ED8\u8BA4\uFF1Afalse\uFF09", "false").option("--ip <ip>", "\u6307\u5B9A IP \u67E5\u8BE2\uFF08\u4E0D\u6307\u5B9A\u5219\u67E5\u8BE2\u6240\u6709\u4E3B\u673A\uFF09").option("--output <format>", "\u8F93\u51FA\u683C\u5F0F\uFF1Aplain\uFF08\u9ED8\u8BA4\uFF09/ json", "plain").action(async (opts) => {
948
+ const appName = opts.appName;
949
+ const grep = opts.grep;
950
+ const format = (opts.output ?? "plain").toLowerCase();
951
+ if (!appName) {
952
+ console.error("\u274C \u53C2\u6570\u4E0D\u8DB3\uFF1A\u5FC5\u987B\u63D0\u4F9B --appName");
953
+ process.exit(1);
954
+ }
955
+ if (!grep) {
956
+ console.error("\u274C \u53C2\u6570\u4E0D\u8DB3\uFF1A\u5FC5\u987B\u63D0\u4F9B --grep\uFF08grep \u5173\u952E\u8BCD\uFF09");
957
+ process.exit(1);
958
+ }
959
+ if (grep.length < 3) {
960
+ console.error("\u274C --grep \u5173\u952E\u8BCD\u957F\u5EA6\u5FC5\u987B\u5927\u4E8E3\u4E2A\u5B57\u7B26");
961
+ process.exit(1);
962
+ }
963
+ const file = opts.file;
964
+ const grepV = opts.grepV ?? "";
965
+ const maxMatchRow = opts.maxMatchRow ?? 50;
966
+ const lineOffsetBefore = opts.lineOffsetBefore ?? 0;
967
+ const lineOffsetAfter = opts.lineOffsetAfter ?? 0;
968
+ const searchScope = opts.searchScope ?? "100M";
969
+ const showPlain = String(opts.showPlain ?? "false").toLowerCase() === "true";
970
+ const selectIp = opts.ip ?? "";
971
+ await withBrowser(async (page) => {
972
+ try {
973
+ await page.waitForNetworkIdle?.();
974
+ } catch {
975
+ }
976
+ let logFile = file;
977
+ if (!logFile) {
978
+ console.log(`\u{1F4C2} \u672A\u6307\u5B9A\u65E5\u5FD7\u6587\u4EF6\uFF0C\u6B63\u5728\u83B7\u53D6\u6587\u4EF6\u5217\u8868...`);
979
+ let files;
980
+ try {
981
+ files = await listAppFiles(page, appName, false);
982
+ } catch (err) {
983
+ console.error(`\u274C \u83B7\u53D6\u6587\u4EF6\u5217\u8868\u5931\u8D25: ${err.message}`);
984
+ process.exit(1);
985
+ }
986
+ const mainFile = files.find((f) => f.endsWith("/catalina.out") || f.endsWith(".out"));
987
+ logFile = mainFile ?? files[0];
988
+ if (!logFile) {
989
+ console.error("\u274C \u672A\u627E\u5230\u53EF\u7528\u7684\u65E5\u5FD7\u6587\u4EF6\uFF0C\u8BF7\u4F7F\u7528 --file \u624B\u52A8\u6307\u5B9A\u3002");
990
+ process.exit(1);
991
+ }
992
+ console.log(`\u{1F4C2} \u81EA\u52A8\u9009\u62E9\u65E5\u5FD7\u6587\u4EF6: ${logFile}`);
993
+ }
994
+ console.log(`\u{1F50E} \u672C\u5730\u641C\u7D22: ${appName} ${logFile} grep="${grep}"`);
995
+ let result;
996
+ try {
997
+ result = await localLogSearch(page, {
998
+ appName,
999
+ file: logFile,
1000
+ grep,
1001
+ grepV,
1002
+ maxMatchRow,
1003
+ lineOffsetBefore,
1004
+ lineOffsetAfter,
1005
+ grepDirection: true,
1006
+ searchScope,
1007
+ showPlain,
1008
+ selectIp
1009
+ });
1010
+ } catch (err) {
1011
+ console.error(`\u274C \u672C\u5730\u65E5\u5FD7\u641C\u7D22\u5931\u8D25: ${err.message}`);
1012
+ process.exit(1);
1013
+ }
1014
+ const { entries, ip: bestIp, taskResult } = result;
1015
+ if (format !== "json") {
1016
+ const ipSummary = Object.entries(taskResult).map(([ip, entries2]) => {
1017
+ const e = Array.isArray(entries2) ? entries2[0] : entries2;
1018
+ const count = e?.logCount ?? 0;
1019
+ const ok = e?.success ? "\u2705" : "\u274C";
1020
+ return ` ${ok} ${ip}: ${count} \u6761`;
1021
+ }).join("\n");
1022
+ console.log(`
1023
+ \u{1F4CA} \u5404\u4E3B\u673A\u7ED3\u679C:
1024
+ ${ipSummary}`);
1025
+ if (bestIp) {
1026
+ console.log(`
1027
+ \u{1F4C4} \u663E\u793A ${bestIp} \u7684\u65E5\u5FD7\u5185\u5BB9:`);
1028
+ }
1029
+ }
1030
+ if (!entries || entries.length === 0) {
1031
+ console.log("\u{1F4ED} \u672A\u627E\u5230\u5339\u914D\u7684\u65E5\u5FD7\u5185\u5BB9\u3002");
1032
+ return;
1033
+ }
1034
+ if (format === "json") {
1035
+ console.log(JSON.stringify(entries, null, 2));
1036
+ return;
1037
+ }
1038
+ for (let i = 0; i < entries.length; i++) {
1039
+ const entry = entries[i];
1040
+ const content = entry.content ?? "";
1041
+ const levelMatch = content.match(/\b(ERROR|WARN|INFO|DEBUG|FATAL)\b/);
1042
+ const level = levelMatch ? colorizeLevel(levelMatch[1]) : "";
1043
+ console.log("");
1044
+ console.log(`${DIM}[${i + 1}]${RESET} ${level}`);
1045
+ console.log(content);
1046
+ }
1047
+ console.log(`
1048
+ \u{1F4C4} ${entries.length} \u6761\u5339\u914D\u7ED3\u679C`);
1049
+ }, { targetUrl: "https://joywatch.jd.com/digger/techApp/overview" });
1050
+ });
1051
+ digger.command("files").description("\u67E5\u8BE2\u5E94\u7528\u53EF\u7528\u7684\u65E5\u5FD7\u6587\u4EF6\u5217\u8868").addHelpText("after", `
1052
+ \u4F7F\u7528\u793A\u4F8B\uFF1A
1053
+ # \u5217\u51FA\u4E3B\u65E5\u5FD7\u6587\u4EF6
1054
+ yue digger files --appName baoxian-bridgehead-zx
1055
+
1056
+ # \u5305\u542B\u6240\u6709\u6587\u4EF6\uFF08\u542B\u5386\u53F2\u5F52\u6863\uFF09
1057
+ yue digger files --appName baoxian-bridgehead-zx --containAll
1058
+
1059
+ # JSON \u8F93\u51FA
1060
+ yue digger files --appName baoxian-bridgehead-zx --output json
1061
+ `).option("--appName <name>", "\u5E94\u7528\u540D\u79F0").option("--containAll", "\u662F\u5426\u5305\u542B\u6240\u6709\u6587\u4EF6\uFF08\u542B\u5386\u53F2\u5F52\u6863\uFF09\uFF0C\u9ED8\u8BA4\u53EA\u8FD4\u56DE\u4E3B\u65E5\u5FD7\u6587\u4EF6", false).option("--output <fmt>", "\u8F93\u51FA\u683C\u5F0F\uFF1Alist\uFF08\u9ED8\u8BA4\uFF09/ json", "list").action(async (opts) => {
1062
+ const appName = opts.appName;
1063
+ const containAll = !!opts.containAll;
1064
+ const output = (opts.output ?? "list").toLowerCase();
1065
+ if (!appName) {
1066
+ console.error("\u274C \u53C2\u6570\u4E0D\u8DB3\uFF1A\u5FC5\u987B\u63D0\u4F9B --appName");
1067
+ process.exit(1);
1068
+ }
1069
+ await withBrowser(async (page) => {
1070
+ try {
1071
+ await page.waitForNetworkIdle?.();
1072
+ } catch {
1073
+ }
1074
+ let files;
1075
+ try {
1076
+ files = await listAppFiles(page, appName, containAll);
1077
+ } catch (err) {
1078
+ console.error(`\u274C \u67E5\u8BE2\u5931\u8D25: ${err.message}`);
1079
+ process.exit(1);
1080
+ }
1081
+ if (output === "json") {
1082
+ console.log(JSON.stringify(files, null, 2));
1083
+ return;
1084
+ }
1085
+ if (!files || files.length === 0) {
1086
+ console.log("\u{1F4ED} \u672A\u627E\u5230\u53EF\u7528\u7684\u65E5\u5FD7\u6587\u4EF6\u3002");
1087
+ return;
1088
+ }
1089
+ const grouped = /* @__PURE__ */ new Map();
1090
+ for (const f of files) {
1091
+ const lastSlash = f.lastIndexOf("/");
1092
+ const dir = f.substring(0, lastSlash) || "/";
1093
+ const name = f.substring(lastSlash + 1);
1094
+ if (!grouped.has(dir)) grouped.set(dir, []);
1095
+ grouped.get(dir).push(name);
1096
+ }
1097
+ for (const [dir, names] of grouped) {
1098
+ console.log(`
1099
+ ${CYAN}${dir}/${RESET}`);
1100
+ for (const name of names) {
1101
+ console.log(` ${name}`);
1102
+ }
1103
+ }
1104
+ console.log(`
1105
+ \u{1F4CB} \u5171 ${files.length} \u4E2A\u6587\u4EF6`);
1106
+ }, { targetUrl: "https://joywatch.jd.com/digger/techApp/overview" });
1107
+ });
1108
+ digger.command("ips").description("\u67E5\u8BE2\u5E94\u7528\u5B9E\u4F8B IP \u5217\u8868\uFF08\u542B Pod \u540D\u3001\u5206\u7EC4\uFF09").addHelpText("after", `
1109
+ \u4F7F\u7528\u793A\u4F8B\uFF1A
1110
+ # \u67E5\u8BE2\u5B9E\u4F8B IP\uFF08\u542B Pod \u540D\u548C\u5206\u7EC4\uFF09
1111
+ yue digger ips --appName baoxian-bridgehead-zx
1112
+
1113
+ # JSON \u8F93\u51FA
1114
+ yue digger ips --appName baoxian-bridgehead-zx --output json
1115
+ `).option("--appName <name>", "\u5E94\u7528\u540D\u79F0").option("--output <fmt>", "\u8F93\u51FA\u683C\u5F0F\uFF1Atable\uFF08\u9ED8\u8BA4\uFF09/ json", "table").action(async (opts) => {
1116
+ const appName = opts.appName;
1117
+ const output = (opts.output ?? "table").toLowerCase();
1118
+ if (!appName) {
1119
+ console.error("\u274C \u53C2\u6570\u4E0D\u8DB3\uFF1A\u5FC5\u987B\u63D0\u4F9B --appName");
1120
+ process.exit(1);
1121
+ }
1122
+ await withBrowser(async (page) => {
1123
+ try {
1124
+ await page.waitForNetworkIdle?.();
1125
+ } catch {
1126
+ }
1127
+ let ips;
1128
+ try {
1129
+ ips = await listAppIpsV2(page, appName);
1130
+ } catch (err) {
1131
+ console.error(`\u274C \u67E5\u8BE2\u5931\u8D25: ${err.message}`);
1132
+ process.exit(1);
1133
+ }
1134
+ if (output === "json") {
1135
+ console.log(JSON.stringify(ips, null, 2));
1136
+ return;
1137
+ }
1138
+ if (!ips || ips.length === 0) {
1139
+ console.log("\u{1F4ED} \u672A\u67E5\u8BE2\u5230\u5B9E\u4F8B IP\u3002");
1140
+ return;
1141
+ }
1142
+ const table = new Table3({
1143
+ head: ["ip", "podName", "group", "type"],
1144
+ style: { head: ["cyan"], border: ["gray"] },
1145
+ wordWrap: true,
1146
+ colWidths: [18, 36, 16, 8]
1147
+ });
1148
+ for (const ip of ips) {
1149
+ table.push([ip.ip, ip.podName, ip.groupName, ip.type]);
1150
+ }
1151
+ console.log(table.toString());
1152
+ console.log(`
1153
+ \u{1F4CB} \u5171 ${ips.length} \u4E2A\u5B9E\u4F8B`);
1154
+ }, { targetUrl: "https://joywatch.jd.com/digger/techApp/overview" });
1155
+ });
1156
+ }
1157
+
1158
+ // src/commands/jmq.ts
1159
+ import Table4 from "cli-table3";
1160
+
1161
+ // src/lib/jmq-api.ts
1162
+ var SITE_BASE_URL = {
1163
+ cn: "https://taishan.jd.com",
1164
+ test: "http://test.taishan.jd.com",
1165
+ th: "http://th.taishan.jd.com",
1166
+ hl: "http://hl.taishan.jd.com",
1167
+ intl: "http://joybuy.taishan.jd.com",
1168
+ "intl-test": "http://test-intl.taishan.jd.com"
1169
+ };
1170
+ var ENV_TO_SITE = {
1171
+ prod: "cn",
1172
+ test: "test",
1173
+ th: "th",
1174
+ hl: "hl",
1175
+ intl: "intl",
1176
+ "intl-test": "intl-test"
1177
+ };
1178
+ var ENV_JMQ_HEADER = {
1179
+ prod: "prod",
1180
+ test: "prod",
1181
+ th: "prod",
1182
+ hl: "prod",
1183
+ intl: "prod",
1184
+ "intl-test": "prod"
1185
+ };
1186
+ var ENV_LABEL = {
1187
+ prod: "\u4E2D\u56FD\u7AD9-\u751F\u4EA7",
1188
+ test: "\u6D4B\u8BD5\u7AD9",
1189
+ th: "\u65B0\u52A0\u5761-\u751F\u4EA7",
1190
+ hl: "\u8377\u5170\u7AD9-\u751F\u4EA7",
1191
+ intl: "\u56FD\u9645\u7AD9-\u751F\u4EA7",
1192
+ "intl-test": "\u56FD\u9645\u6D4B\u8BD5\u7AD9"
1193
+ };
1194
+ function envJmqHeader(env) {
1195
+ return ENV_JMQ_HEADER[env];
1196
+ }
1197
+ function envLabel(env) {
1198
+ return ENV_LABEL[env];
1199
+ }
1200
+ function getEnvBaseUrl(env) {
1201
+ return SITE_BASE_URL[ENV_TO_SITE[env]];
1202
+ }
1203
+ function taishanXhr(page, method, url, body, env) {
1204
+ const jmqEnvHeader = envJmqHeader(env);
1205
+ return page.evaluate(
1206
+ (args) => {
1207
+ return new Promise((resolve) => {
1208
+ const xhr = new XMLHttpRequest();
1209
+ xhr.open(args.method, args.url, true);
1210
+ xhr.withCredentials = true;
1211
+ xhr.setRequestHeader("Accept", "application/json, text/plain, */*");
1212
+ xhr.setRequestHeader("Content-Type", "application/json");
1213
+ xhr.setRequestHeader("jmq_env", args.jmqEnv);
1214
+ xhr.setRequestHeader(
1215
+ "x-proxy-opts",
1216
+ '{"target":"http://origin.jmq.jd.com","pathRewrite":{"^/api/jmqApi":"/"}}'
1217
+ );
1218
+ xhr.onload = () => {
1219
+ try {
1220
+ resolve({
1221
+ ok: xhr.status >= 200 && xhr.status < 300,
1222
+ status: xhr.status,
1223
+ body: JSON.parse(xhr.responseText)
1224
+ });
1225
+ } catch {
1226
+ resolve({
1227
+ ok: xhr.status >= 200 && xhr.status < 300,
1228
+ status: xhr.status,
1229
+ body: xhr.responseText
1230
+ });
1231
+ }
1232
+ };
1233
+ xhr.onerror = () => resolve({ ok: false, status: 0, body: "Network error" });
1234
+ xhr.send(args.body || null);
1235
+ });
1236
+ },
1237
+ { method, url, body: body ? JSON.stringify(body) : void 0, jmqEnv: jmqEnvHeader }
1238
+ );
1239
+ }
1240
+ var MSG_TYPE_MAP = {
1241
+ 0: "\u666E\u901A\u6D88\u606F",
1242
+ 1: "\u987A\u5E8F\u6D88\u606F",
1243
+ 2: "\u5EF6\u65F6\u6D88\u606F",
1244
+ 3: "\u4E8B\u52A1\u6D88\u606F"
1245
+ };
1246
+ function msgTypeLabel(type) {
1247
+ return MSG_TYPE_MAP[type] ?? `\u7C7B\u578B${type}`;
1248
+ }
1249
+ async function searchGroups(page, keyword, env = "prod", pageNum, pageSize = 50) {
1250
+ const baseUrl = getEnvBaseUrl(env);
1251
+ const url = `${baseUrl}/api/jmqApi/v1/application/search`;
1252
+ const fetchAll = pageNum === void 0 || pageNum === null;
1253
+ const startPage = fetchAll ? 1 : pageNum;
1254
+ let allData = [];
1255
+ let pagination = { page: startPage, size: pageSize };
1256
+ let currentPage = startPage;
1257
+ let totalPages = 1;
1258
+ while (true) {
1259
+ const body = {
1260
+ pagination: { page: currentPage, size: pageSize },
1261
+ query: {
1262
+ keyword: keyword ?? "",
1263
+ fuzzy: 1,
1264
+ full: 0
1265
+ }
1266
+ };
1267
+ const result = await taishanXhr(page, "POST", url, body, env);
1268
+ if (!result.ok) {
1269
+ throw new Error(
1270
+ `JMQ group search error: HTTP ${result.status} \u2014 ${JSON.stringify(result.body)}`
1271
+ );
1272
+ }
1273
+ const json = result.body;
1274
+ const data = json.data ?? [];
1275
+ pagination = {
1276
+ page: json.pagination?.page ?? currentPage,
1277
+ size: json.pagination?.size ?? pageSize,
1278
+ totalRecord: json.pagination?.totalRecord ?? 0,
1279
+ pages: json.pagination?.pages
1280
+ };
1281
+ totalPages = pagination.pages ?? (Math.ceil((pagination.totalRecord ?? 0) / pageSize) || 1);
1282
+ allData = allData.concat(data);
1283
+ if (!fetchAll || currentPage >= totalPages) break;
1284
+ currentPage++;
1285
+ }
1286
+ const groups = allData.map((item) => ({
1287
+ id: item.id ?? 0,
1288
+ code: item.code ?? item.appId ?? "",
1289
+ name: item.name ?? item.description ?? "",
1290
+ env,
1291
+ department: item.department ?? item.dept ?? "",
1292
+ owner: typeof item.owner === "object" ? item.owner?.code ?? "" : item.owner ?? "",
1293
+ source: item.source != null ? String(item.source) : "",
1294
+ aliasCode: item.aliasCode ?? "",
1295
+ system: item.system ?? "",
1296
+ description: item.description ?? ""
1297
+ }));
1298
+ return { data: groups, pagination };
1299
+ }
1300
+ async function searchTopics(page, keyword, env = "prod", pageNum, pageSize = 50, personal = false) {
1301
+ const baseUrl = getEnvBaseUrl(env);
1302
+ const url = `${baseUrl}/api/jmqApi/v1/topicWiki/searchTopicWithWiki`;
1303
+ const fetchAll = pageNum === void 0 || pageNum === null;
1304
+ const startPage = fetchAll ? 1 : pageNum;
1305
+ let allData = [];
1306
+ let pagination = { page: startPage, size: pageSize };
1307
+ let currentPage = startPage;
1308
+ let totalPages = 1;
1309
+ while (true) {
1310
+ const body = {
1311
+ query: {
1312
+ type: "-1",
1313
+ keyword: keyword ?? "",
1314
+ fuzzy: 1,
1315
+ personal: personal ? 1 : 0
1316
+ },
1317
+ pagination: { page: currentPage, size: pageSize }
1318
+ };
1319
+ const result = await taishanXhr(page, "POST", url, body, env);
1320
+ if (!result.ok) {
1321
+ throw new Error(
1322
+ `JMQ topic search error: HTTP ${result.status} \u2014 ${JSON.stringify(result.body)}`
1323
+ );
1324
+ }
1325
+ const json = result.body;
1326
+ const data = json.data ?? [];
1327
+ pagination = {
1328
+ page: json.pagination?.page ?? currentPage,
1329
+ size: json.pagination?.size ?? pageSize,
1330
+ totalRecord: json.pagination?.totalRecord ?? 0,
1331
+ pages: json.pagination?.pages
1332
+ };
1333
+ totalPages = pagination.pages ?? (Math.ceil((pagination.totalRecord ?? 0) / pageSize) || 1);
1334
+ allData = allData.concat(data);
1335
+ if (!fetchAll || currentPage >= totalPages) break;
1336
+ currentPage++;
1337
+ }
1338
+ const topics = allData.map((item) => ({
1339
+ id: item.id ?? item.code ?? "",
1340
+ partitions: item.partitions ?? 0,
1341
+ type: item.type ?? 0,
1342
+ typeLabel: msgTypeLabel(item.type ?? 0),
1343
+ description: item.wiki?.description ?? item.description ?? "",
1344
+ clusters: (item.clusters ?? []).map((c) => c.code ?? c.name ?? "").filter(Boolean),
1345
+ archive: item.archive ?? false,
1346
+ retentionTime: "",
1347
+ preserveUnconsumed: false,
1348
+ createTime: item.wiki?.createTime ? new Date(item.wiki.createTime).toLocaleDateString("zh-CN") : ""
1349
+ }));
1350
+ return { data: topics, pagination };
1351
+ }
1352
+ async function getGroupDetail(page, groupCode, groupId, env = "prod") {
1353
+ const baseUrl = getEnvBaseUrl(env);
1354
+ const appRef = { id: String(groupId), code: groupCode };
1355
+ const consumerUrl = `${baseUrl}/api/jmqApi/v1/consumer/search-with-pagination`;
1356
+ let allConsumers = [];
1357
+ let consumerPage = 1;
1358
+ while (true) {
1359
+ const consumerBody = {
1360
+ pagination: { page: consumerPage, size: 50 },
1361
+ query: { keyword: "", app: appRef }
1362
+ };
1363
+ const consumerResult = await taishanXhr(page, "POST", consumerUrl, consumerBody, env);
1364
+ if (!consumerResult.ok) {
1365
+ throw new Error(`JMQ consumer search error: HTTP ${consumerResult.status}`);
1366
+ }
1367
+ const cJson = consumerResult.body;
1368
+ const cData = cJson.data ?? [];
1369
+ allConsumers = allConsumers.concat(cData);
1370
+ const cPages = cJson.pagination?.pages ?? 1;
1371
+ if (consumerPage >= cPages) break;
1372
+ consumerPage++;
1373
+ }
1374
+ const producerUrl = `${baseUrl}/api/jmqApi/v1/producer/search-with-pagination`;
1375
+ let allProducers = [];
1376
+ let producerPage = 1;
1377
+ while (true) {
1378
+ const producerBody = {
1379
+ pagination: { page: producerPage, size: 50 },
1380
+ query: { keyword: "", app: appRef }
1381
+ };
1382
+ const producerResult = await taishanXhr(page, "POST", producerUrl, producerBody, env);
1383
+ if (!producerResult.ok) {
1384
+ throw new Error(`JMQ producer search error: HTTP ${producerResult.status}`);
1385
+ }
1386
+ const pJson = producerResult.body;
1387
+ const pData = pJson.data ?? [];
1388
+ allProducers = allProducers.concat(pData);
1389
+ const pPages = pJson.pagination?.pages ?? 1;
1390
+ if (producerPage >= pPages) break;
1391
+ producerPage++;
1392
+ }
1393
+ const monitorUrl = `${baseUrl}/api/jmqApi/v1/monitor/find`;
1394
+ const consumerMonitors = /* @__PURE__ */ new Map();
1395
+ for (const consumer of allConsumers) {
1396
+ const topicCode = consumer.topic?.code ?? "";
1397
+ if (!topicCode) continue;
1398
+ const monitorBody = {
1399
+ topic: { code: topicCode },
1400
+ namespace: { id: "", code: "" },
1401
+ app: { id: groupId, code: groupCode },
1402
+ subscribeGroup: consumer.subscribeGroup ?? "",
1403
+ type: 2
1404
+ // consumer
1405
+ };
1406
+ try {
1407
+ const mResult = await taishanXhr(page, "POST", monitorUrl, monitorBody, env);
1408
+ if (mResult.ok && mResult.body?.data) {
1409
+ consumerMonitors.set(topicCode, mResult.body.data);
1410
+ }
1411
+ } catch {
1412
+ }
1413
+ }
1414
+ const producerMonitors = /* @__PURE__ */ new Map();
1415
+ for (const producer of allProducers) {
1416
+ const topicCode = producer.topic?.code ?? "";
1417
+ if (!topicCode) continue;
1418
+ const monitorBody = {
1419
+ topic: { code: topicCode },
1420
+ namespace: { id: "", code: "" },
1421
+ app: { id: groupId, code: groupCode },
1422
+ subscribeGroup: producer.subscribeGroup ?? "",
1423
+ type: 1
1424
+ // producer
1425
+ };
1426
+ try {
1427
+ const mResult = await taishanXhr(page, "POST", monitorUrl, monitorBody, env);
1428
+ if (mResult.ok && mResult.body?.data) {
1429
+ producerMonitors.set(topicCode, mResult.body.data);
1430
+ }
1431
+ } catch {
1432
+ }
1433
+ }
1434
+ const subscriptions = allConsumers.map((c) => {
1435
+ const topicCode = c.topic?.code ?? "";
1436
+ const m = consumerMonitors.get(topicCode);
1437
+ return {
1438
+ topic: topicCode,
1439
+ envName: c.envName ?? "",
1440
+ connections: m?.connections ?? 0,
1441
+ pendingCount: m?.pending?.count ?? 0,
1442
+ dequeueCount: m?.deQuence?.count ?? 0,
1443
+ retryCount: m?.retry?.count ?? 0,
1444
+ retryCurrent: m?.retry?.current ?? 0,
1445
+ concurrent: c.config?.concurrent ?? 1,
1446
+ retry: c.config?.retry ?? false,
1447
+ paused: c.config?.paused ?? false
1448
+ };
1449
+ });
1450
+ const productions = allProducers.map((p) => {
1451
+ const topicCode = p.topic?.code ?? "";
1452
+ const m = producerMonitors.get(topicCode);
1453
+ return {
1454
+ topic: topicCode,
1455
+ envName: p.envName ?? "",
1456
+ connections: m?.connections ?? 0,
1457
+ enqueueCount: m?.enQuence?.count ?? m?.deQuence?.count ?? 0
1458
+ };
1459
+ });
1460
+ return {
1461
+ group: groupCode,
1462
+ groupId,
1463
+ subscriptions,
1464
+ productions
1465
+ };
1466
+ }
1467
+ async function getTopicDetail(page, topicCode, env = "prod") {
1468
+ const baseUrl = getEnvBaseUrl(env);
1469
+ const topicRef = { id: topicCode, code: topicCode, namespace: { code: "" } };
1470
+ const consumerUrl = `${baseUrl}/api/jmqApi/v1/consumer/search-with-pagination`;
1471
+ let allConsumers = [];
1472
+ let consumerPage = 1;
1473
+ while (true) {
1474
+ const consumerBody = {
1475
+ pagination: { page: consumerPage, size: 50 },
1476
+ query: { keyword: "", topic: topicRef }
1477
+ };
1478
+ const consumerResult = await taishanXhr(page, "POST", consumerUrl, consumerBody, env);
1479
+ if (!consumerResult.ok) {
1480
+ throw new Error(`JMQ consumer search error: HTTP ${consumerResult.status}`);
1481
+ }
1482
+ const cJson = consumerResult.body;
1483
+ const cData = cJson.data ?? [];
1484
+ allConsumers = allConsumers.concat(cData);
1485
+ const cPages = cJson.pagination?.pages ?? 1;
1486
+ if (consumerPage >= cPages) break;
1487
+ consumerPage++;
1488
+ }
1489
+ const producerUrl = `${baseUrl}/api/jmqApi/v1/producer/search-with-pagination`;
1490
+ let allProducers = [];
1491
+ let producerPage = 1;
1492
+ while (true) {
1493
+ const producerBody = {
1494
+ pagination: { page: producerPage, size: 50 },
1495
+ query: { keyword: "", topic: topicRef }
1496
+ };
1497
+ const producerResult = await taishanXhr(page, "POST", producerUrl, producerBody, env);
1498
+ if (!producerResult.ok) {
1499
+ throw new Error(`JMQ producer search error: HTTP ${producerResult.status}`);
1500
+ }
1501
+ const pJson = producerResult.body;
1502
+ const pData = pJson.data ?? [];
1503
+ allProducers = allProducers.concat(pData);
1504
+ const pPages = pJson.pagination?.pages ?? 1;
1505
+ if (producerPage >= pPages) break;
1506
+ producerPage++;
1507
+ }
1508
+ const monitorUrl = `${baseUrl}/api/jmqApi/v1/monitor/find`;
1509
+ const consumerMonitors = /* @__PURE__ */ new Map();
1510
+ for (const consumer of allConsumers) {
1511
+ const groupCode = consumer.app?.code ?? "";
1512
+ const groupId = consumer.app?.id ?? 0;
1513
+ if (!groupCode) continue;
1514
+ const monitorBody = {
1515
+ topic: { code: topicCode },
1516
+ namespace: { id: "", code: "" },
1517
+ app: { id: groupId, code: groupCode },
1518
+ subscribeGroup: consumer.subscribeGroup ?? "",
1519
+ type: 2
1520
+ // consumer
1521
+ };
1522
+ try {
1523
+ const mResult = await taishanXhr(page, "POST", monitorUrl, monitorBody, env);
1524
+ if (mResult.ok && mResult.body?.data) {
1525
+ consumerMonitors.set(groupCode, mResult.body.data);
1526
+ }
1527
+ } catch {
1528
+ }
1529
+ }
1530
+ const producerMonitors = /* @__PURE__ */ new Map();
1531
+ for (const producer of allProducers) {
1532
+ const groupCode = producer.app?.code ?? "";
1533
+ const groupId = producer.app?.id ?? 0;
1534
+ if (!groupCode) continue;
1535
+ const monitorBody = {
1536
+ topic: { code: topicCode },
1537
+ namespace: { id: "", code: "" },
1538
+ app: { id: groupId, code: groupCode },
1539
+ subscribeGroup: producer.subscribeGroup ?? "",
1540
+ type: 1
1541
+ // producer
1542
+ };
1543
+ try {
1544
+ const mResult = await taishanXhr(page, "POST", monitorUrl, monitorBody, env);
1545
+ if (mResult.ok && mResult.body?.data) {
1546
+ producerMonitors.set(groupCode, mResult.body.data);
1547
+ }
1548
+ } catch {
1549
+ }
1550
+ }
1551
+ const consumers = allConsumers.map((c) => {
1552
+ const groupCode = c.app?.code ?? "";
1553
+ const m = consumerMonitors.get(groupCode);
1554
+ return {
1555
+ group: groupCode,
1556
+ groupId: c.app?.id ?? 0,
1557
+ envName: c.envName ?? "",
1558
+ connections: m?.connections ?? 0,
1559
+ pendingCount: m?.pending?.count ?? 0,
1560
+ dequeueCount: m?.deQuence?.count ?? 0,
1561
+ retryCount: m?.retry?.count ?? 0,
1562
+ timerRetryCount: m?.timerRetry?.count ?? 0,
1563
+ deadMessages: m?.deadMessages ?? 0,
1564
+ concurrent: c.config?.concurrent ?? 1,
1565
+ retry: c.config?.retry ?? false,
1566
+ paused: c.config?.paused ?? false
1567
+ };
1568
+ });
1569
+ const producers = allProducers.map((p) => {
1570
+ const groupCode = p.app?.code ?? "";
1571
+ const m = producerMonitors.get(groupCode);
1572
+ return {
1573
+ group: groupCode,
1574
+ groupId: p.app?.id ?? 0,
1575
+ envName: p.envName ?? "",
1576
+ connections: m?.connections ?? 0,
1577
+ enqueueCount: m?.enQuence?.count ?? m?.deQuence?.count ?? 0
1578
+ };
1579
+ });
1580
+ return {
1581
+ topic: topicCode,
1582
+ consumers,
1583
+ producers
1584
+ };
1585
+ }
1586
+
1587
+ // src/commands/jmq.ts
1588
+ var CYAN2 = "\x1B[36m";
1589
+ var GREEN2 = "\x1B[32m";
1590
+ var YELLOW2 = "\x1B[33m";
1591
+ var RED2 = "\x1B[31m";
1592
+ var DIM2 = "\x1B[2m";
1593
+ var RESET2 = "\x1B[0m";
1594
+ function jmqTargetUrl(env) {
1595
+ return `${getEnvBaseUrl(env)}/jmq/application?JMQ_ENV=prod`;
1596
+ }
1597
+ function parseEnv(val) {
1598
+ const lower = val.toLowerCase();
1599
+ const envMap = {
1600
+ prod: "prod",
1601
+ \u751F\u4EA7: "prod",
1602
+ \u4E2D\u56FD\u7AD9: "prod",
1603
+ test: "test",
1604
+ \u6D4B\u8BD5\u7AD9: "test",
1605
+ th: "th",
1606
+ \u65B0\u52A0\u5761: "th",
1607
+ hl: "hl",
1608
+ \u8377\u5170\u7AD9: "hl",
1609
+ intl: "intl",
1610
+ \u56FD\u9645\u7AD9: "intl",
1611
+ "intl-test": "intl-test",
1612
+ \u56FD\u9645\u6D4B\u8BD5\u7AD9: "intl-test"
1613
+ };
1614
+ if (envMap[lower]) return envMap[lower];
1615
+ console.error(`\u274C Invalid --env: "${val}". Use "prod", "test", "th", "hl", "intl", or "intl-test".`);
1616
+ process.exit(1);
1617
+ }
1618
+ function renderGroupsTable(groups, env) {
1619
+ const table = new Table4({
1620
+ head: ["Group", "\u540D\u79F0", "\u73AF\u5883", "\u8D1F\u8D23\u4EBA", "\u6765\u6E90"],
1621
+ style: { head: ["cyan"], border: ["gray"] },
1622
+ wordWrap: true,
1623
+ colWidths: [30, 30, 8, 16, 40]
1624
+ });
1625
+ for (const g of groups) {
1626
+ const source = g.system && g.aliasCode ? `${g.system}/${g.aliasCode}` : g.source || "-";
1627
+ table.push([g.code, g.name || "-", envLabel(env), g.owner || "-", source]);
1628
+ }
1629
+ console.log("");
1630
+ console.log(table.toString());
1631
+ }
1632
+ function renderTopicsTable(topics) {
1633
+ const table = new Table4({
1634
+ head: ["Topic", "\u96C6\u7FA4", "\u5206\u533A\u6570", "\u6D88\u606F\u7C7B\u578B", "\u63CF\u8FF0"],
1635
+ style: { head: ["cyan"], border: ["gray"] },
1636
+ wordWrap: true,
1637
+ colWidths: [38, 28, 8, 10, 36]
1638
+ });
1639
+ for (const t of topics) {
1640
+ const cluster = t.clusters.length > 0 ? t.clusters.join(", ") : "-";
1641
+ table.push([t.id, cluster, String(t.partitions), t.typeLabel, t.description || "-"]);
1642
+ }
1643
+ console.log("");
1644
+ console.log(table.toString());
1645
+ }
1646
+ function formatNumber(n) {
1647
+ if (n === 0) return "0";
1648
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
1649
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
1650
+ return String(n);
1651
+ }
1652
+ function colorizePending(count) {
1653
+ if (count > 1e3) return `${RED2}${formatNumber(count)}${RESET2}`;
1654
+ if (count > 0) return `${YELLOW2}${formatNumber(count)}${RESET2}`;
1655
+ return `${GREEN2}0${RESET2}`;
1656
+ }
1657
+ function colorizeRetry(count) {
1658
+ if (count > 0) return `${RED2}${formatNumber(count)}${RESET2}`;
1659
+ return `${GREEN2}0${RESET2}`;
1660
+ }
1661
+ function renderGroupDetail(detail) {
1662
+ if (detail.subscriptions.length > 0) {
1663
+ const table = new Table4({
1664
+ head: ["Topic", "\u8FDE\u63A5\u6570", "\u79EF\u538B\u6570", "\u51FA\u961F\u6570", "\u91CD\u8BD5\u6570", "\u8BA2\u9605\u7EBF\u7A0B", "\u6D88\u8D39\u72B6\u6001"],
1665
+ style: { head: ["cyan"], border: ["gray"] },
1666
+ wordWrap: true,
1667
+ colWidths: [32, 8, 10, 12, 8, 8, 10]
1668
+ });
1669
+ for (const s of detail.subscriptions) {
1670
+ const status = s.paused ? `${YELLOW2}\u5DF2\u6682\u505C${RESET2}` : `${GREEN2}\u6B63\u5E38\u6D88\u8D39${RESET2}`;
1671
+ table.push([
1672
+ s.topic,
1673
+ String(s.connections),
1674
+ colorizePending(s.pendingCount),
1675
+ formatNumber(s.dequeueCount),
1676
+ colorizeRetry(s.retryCount),
1677
+ String(s.concurrent),
1678
+ status
1679
+ ]);
1680
+ }
1681
+ console.log(`
1682
+ ${CYAN2}\u{1F4CB} \u6D88\u8D39\u8005\u8BA2\u9605${RESET2}`);
1683
+ console.log(table.toString());
1684
+ } else {
1685
+ console.log(`
1686
+ ${DIM2}\u{1F4CB} \u6D88\u8D39\u8005\u8BA2\u9605\uFF1A\u6682\u65E0${RESET2}`);
1687
+ }
1688
+ if (detail.productions.length > 0) {
1689
+ const table = new Table4({
1690
+ head: ["Topic", "\u8FDE\u63A5\u6570", "\u5165\u961F\u6570"],
1691
+ style: { head: ["cyan"], border: ["gray"] },
1692
+ wordWrap: true,
1693
+ colWidths: [32, 8, 14]
1694
+ });
1695
+ for (const p of detail.productions) {
1696
+ table.push([p.topic, String(p.connections), formatNumber(p.enqueueCount)]);
1697
+ }
1698
+ console.log(`
1699
+ ${CYAN2}\u{1F4E4} \u751F\u4EA7\u8005\u8BA2\u9605${RESET2}`);
1700
+ console.log(table.toString());
1701
+ } else {
1702
+ console.log(`
1703
+ ${DIM2}\u{1F4E4} \u751F\u4EA7\u8005\u8BA2\u9605\uFF1A\u6682\u65E0${RESET2}`);
1704
+ }
1705
+ }
1706
+ function renderTopicDetail(detail) {
1707
+ if (detail.consumers.length > 0) {
1708
+ const table = new Table4({
1709
+ head: ["Group", "\u8FDE\u63A5\u6570", "\u79EF\u538B\u6570", "\u51FA\u961F\u6570", "\u91CD\u8BD5\u6570", "\u9AD8\u6027\u80FD\u91CD\u8BD5", "\u6B7B\u4FE1\u6D88\u606F", "\u6D88\u8D39\u72B6\u6001"],
1710
+ style: { head: ["cyan"], border: ["gray"] },
1711
+ wordWrap: true,
1712
+ colWidths: [28, 8, 10, 12, 8, 10, 10, 10]
1713
+ });
1714
+ for (const c of detail.consumers) {
1715
+ const status = c.paused ? `${YELLOW2}\u5DF2\u6682\u505C${RESET2}` : `${GREEN2}\u6B63\u5E38\u6D88\u8D39${RESET2}`;
1716
+ table.push([
1717
+ c.group,
1718
+ String(c.connections),
1719
+ colorizePending(c.pendingCount),
1720
+ formatNumber(c.dequeueCount),
1721
+ colorizeRetry(c.retryCount),
1722
+ colorizeRetry(c.timerRetryCount),
1723
+ String(c.deadMessages),
1724
+ status
1725
+ ]);
1726
+ }
1727
+ console.log(`
1728
+ ${CYAN2}\u{1F4CB} \u6D88\u8D39\u7EC4${RESET2}`);
1729
+ console.log(table.toString());
1730
+ } else {
1731
+ console.log(`
1732
+ ${DIM2}\u{1F4CB} \u6D88\u8D39\u7EC4\uFF1A\u6682\u65E0${RESET2}`);
1733
+ }
1734
+ if (detail.producers.length > 0) {
1735
+ const table = new Table4({
1736
+ head: ["Group", "\u8FDE\u63A5\u6570", "\u5165\u961F\u6570"],
1737
+ style: { head: ["cyan"], border: ["gray"] },
1738
+ wordWrap: true,
1739
+ colWidths: [28, 8, 14]
1740
+ });
1741
+ for (const p of detail.producers) {
1742
+ table.push([p.group, String(p.connections), formatNumber(p.enqueueCount)]);
1743
+ }
1744
+ console.log(`
1745
+ ${CYAN2}\u{1F4E4} \u751F\u4EA7\u7EC4${RESET2}`);
1746
+ console.log(table.toString());
1747
+ } else {
1748
+ console.log(`
1749
+ ${DIM2}\u{1F4E4} \u751F\u4EA7\u7EC4\uFF1A\u6682\u65E0${RESET2}`);
1750
+ }
1751
+ }
1752
+ function registerJmqCommand(program) {
1753
+ const jmq = program.command("jmq").description("Taishan JMQ\uFF08taishan.jd.com\uFF09\u6D88\u606F\u961F\u5217\u7BA1\u7406");
1754
+ jmq.command("groups").description("\u641C\u7D22 JMQ Group\uFF08\u5373\u539F\u6765\u7684\u5E94\u7528\uFF09\uFF0C\u9ED8\u8BA4\u8FD4\u56DE\u5168\u90E8\u7ED3\u679C").addHelpText("after", `
1755
+ \u73AF\u5883\u8BF4\u660E\uFF08--env\uFF09\uFF1A
1756
+ prod \u4E2D\u56FD\u7AD9\u751F\u4EA7\uFF08taishan.jd.com\uFF09
1757
+ test \u6D4B\u8BD5\u7AD9\uFF08test.taishan.jd.com\uFF09
1758
+ th \u65B0\u52A0\u5761\u751F\u4EA7\uFF08th.taishan.jd.com\uFF09
1759
+ hl \u8377\u5170\u7AD9\u751F\u4EA7\uFF08hl.taishan.jd.com\uFF09
1760
+ intl \u56FD\u9645\u7AD9\u751F\u4EA7\uFF08joybuy.taishan.jd.com\uFF09
1761
+ intl-test \u56FD\u9645\u6D4B\u8BD5\u7AD9\uFF08test-intl.taishan.jd.com\uFF09
1762
+
1763
+ \u4F7F\u7528\u793A\u4F8B\uFF1A
1764
+ # \u67E5\u8BE2\u6240\u6709 Group\uFF08\u4E2D\u56FD\u7AD9\u751F\u4EA7\uFF09
1765
+ yue jmq groups
1766
+
1767
+ # \u6309 Group \u540D\u79F0\u6A21\u7CCA\u641C\u7D22
1768
+ yue jmq groups --name baoxian
1769
+
1770
+ # \u6D4B\u8BD5\u7AD9
1771
+ yue jmq groups --name baoxian --env test
1772
+
1773
+ # \u8377\u5170\u7AD9
1774
+ yue jmq groups --name baoxian --env hl
1775
+
1776
+ # \u53EA\u770B\u67D0\u4E00\u9875
1777
+ yue jmq groups --name baoxian --page 2
1778
+
1779
+ # JSON \u8F93\u51FA
1780
+ yue jmq groups --name baoxian --output json
1781
+ `).option("--name <keyword>", "Group \u540D\u79F0\u5173\u952E\u5B57\uFF08\u6A21\u7CCA\u641C\u7D22\uFF09\uFF0C\u4E0D\u4F20\u5219\u67E5\u8BE2\u6240\u6709").option("--env <env>", "\u73AF\u5883\uFF1Aprod\uFF08\u9ED8\u8BA4\uFF09/ test / th / hl / intl / intl-test", "prod").option("--page <n>", "\u9875\u7801\uFF08\u4E0D\u6307\u5B9A\u5219\u8FD4\u56DE\u5168\u90E8\uFF09", parseInt).option("--output <format>", "\u8F93\u51FA\u683C\u5F0F\uFF1Atable\uFF08\u9ED8\u8BA4\uFF09/ json", "table").action(async (opts) => {
1782
+ const env = parseEnv(opts.env ?? "prod");
1783
+ const pageNum = opts.page;
1784
+ const keyword = opts.name ?? "";
1785
+ const output = (opts.output ?? "table").toLowerCase();
1786
+ await withBrowser(async (pageCtx) => {
1787
+ try {
1788
+ await pageCtx.waitForNetworkIdle?.();
1789
+ } catch {
1790
+ }
1791
+ const searchDesc = keyword ? `"${keyword}"` : "\u5168\u90E8";
1792
+ console.log(`\u{1F50D} \u641C\u7D22 JMQ Group: ${searchDesc} (${envLabel(env)})...`);
1793
+ let result;
1794
+ try {
1795
+ result = await searchGroups(pageCtx, keyword, env, pageNum, 50);
1796
+ } catch (err) {
1797
+ console.error(`\u274C \u641C\u7D22\u5931\u8D25: ${err.message}`);
1798
+ console.error(` \u8BF7\u786E\u4FDD\u6D4F\u89C8\u5668\u5DF2\u767B\u5F55 ${getEnvBaseUrl(env)}`);
1799
+ process.exit(1);
1800
+ }
1801
+ if (output === "json") {
1802
+ console.log(JSON.stringify(result, null, 2));
1803
+ return;
1804
+ }
1805
+ if (!result.data || result.data.length === 0) {
1806
+ console.log("\u{1F4ED} \u672A\u627E\u5230\u5339\u914D\u7684 Group\u3002");
1807
+ return;
1808
+ }
1809
+ renderGroupsTable(result.data, env);
1810
+ const total = result.pagination.totalRecord ?? result.data.length;
1811
+ console.log(`
1812
+ \u{1F4CB} \u5171 ${result.data.length} \u6761\u7ED3\u679C\uFF08\u603B\u8BA1 ${total} \u6761\uFF09`);
1813
+ }, { targetUrl: jmqTargetUrl(env) });
1814
+ });
1815
+ jmq.command("topics").description("\u641C\u7D22 JMQ Topic\uFF0C\u9ED8\u8BA4\u8FD4\u56DE\u5168\u90E8\u7ED3\u679C").addHelpText("after", `
1816
+ \u73AF\u5883\u8BF4\u660E\uFF08--env\uFF09\uFF1A
1817
+ prod \u4E2D\u56FD\u7AD9\u751F\u4EA7\uFF08taishan.jd.com\uFF09
1818
+ test \u6D4B\u8BD5\u7AD9\uFF08test.taishan.jd.com\uFF09
1819
+ th \u65B0\u52A0\u5761\u751F\u4EA7\uFF08th.taishan.jd.com\uFF09
1820
+ hl \u8377\u5170\u7AD9\u751F\u4EA7\uFF08hl.taishan.jd.com\uFF09
1821
+ intl \u56FD\u9645\u7AD9\u751F\u4EA7\uFF08joybuy.taishan.jd.com\uFF09
1822
+ intl-test \u56FD\u9645\u6D4B\u8BD5\u7AD9\uFF08test-intl.taishan.jd.com\uFF09
1823
+
1824
+ \u4F7F\u7528\u793A\u4F8B\uFF1A
1825
+ # \u67E5\u8BE2\u6240\u6709 Topic\uFF08\u4E2D\u56FD\u7AD9\u751F\u4EA7\uFF09
1826
+ yue jmq topics
1827
+
1828
+ # \u6309 Topic \u540D\u79F0\u6A21\u7CCA\u641C\u7D22
1829
+ yue jmq topics --name baoxian
1830
+
1831
+ # \u53EA\u770B\u6211\u7684\u4E3B\u9898
1832
+ yue jmq topics --name baoxian --personal
1833
+
1834
+ # \u6D4B\u8BD5\u7AD9
1835
+ yue jmq topics --name baoxian --env test
1836
+
1837
+ # \u8377\u5170\u7AD9
1838
+ yue jmq topics --name baoxian --env hl
1839
+
1840
+ # \u53EA\u770B\u67D0\u4E00\u9875
1841
+ yue jmq topics --name baoxian --page 2
1842
+
1843
+ # JSON \u8F93\u51FA
1844
+ yue jmq topics --name baoxian --output json
1845
+ `).option("--name <keyword>", "Topic \u540D\u79F0\u5173\u952E\u5B57\uFF08\u6A21\u7CCA\u641C\u7D22\uFF09\uFF0C\u4E0D\u4F20\u5219\u67E5\u8BE2\u6240\u6709").option("--env <env>", "\u73AF\u5883\uFF1Aprod\uFF08\u9ED8\u8BA4\uFF09/ test / th / hl / intl / intl-test", "prod").option("--personal", "\u53EA\u770B\u6211\u7684\u4E3B\u9898", false).option("--page <n>", "\u9875\u7801\uFF08\u4E0D\u6307\u5B9A\u5219\u8FD4\u56DE\u5168\u90E8\uFF09", parseInt).option("--output <format>", "\u8F93\u51FA\u683C\u5F0F\uFF1Atable\uFF08\u9ED8\u8BA4\uFF09/ json", "table").action(async (opts) => {
1846
+ const env = parseEnv(opts.env ?? "prod");
1847
+ const pageNum = opts.page;
1848
+ const keyword = opts.name ?? "";
1849
+ const personal = !!opts.personal;
1850
+ const output = (opts.output ?? "table").toLowerCase();
1851
+ await withBrowser(async (pageCtx) => {
1852
+ try {
1853
+ await pageCtx.waitForNetworkIdle?.();
1854
+ } catch {
1855
+ }
1856
+ const searchDesc = keyword ? `"${keyword}"` : "\u5168\u90E8";
1857
+ const personalTag = personal ? " (\u6211\u7684\u4E3B\u9898)" : "";
1858
+ console.log(`\u{1F50D} \u641C\u7D22 JMQ Topic: ${searchDesc} (${envLabel(env)})${personalTag}...`);
1859
+ let result;
1860
+ try {
1861
+ result = await searchTopics(pageCtx, keyword, env, pageNum, 50, personal);
1862
+ } catch (err) {
1863
+ console.error(`\u274C \u641C\u7D22\u5931\u8D25: ${err.message}`);
1864
+ console.error(` \u8BF7\u786E\u4FDD\u6D4F\u89C8\u5668\u5DF2\u767B\u5F55 ${getEnvBaseUrl(env)}`);
1865
+ process.exit(1);
1866
+ }
1867
+ if (output === "json") {
1868
+ console.log(JSON.stringify(result, null, 2));
1869
+ return;
1870
+ }
1871
+ if (!result.data || result.data.length === 0) {
1872
+ console.log("\u{1F4ED} \u672A\u627E\u5230\u5339\u914D\u7684 Topic\u3002");
1873
+ return;
1874
+ }
1875
+ renderTopicsTable(result.data);
1876
+ const total = result.pagination.totalRecord ?? result.data.length;
1877
+ console.log(`
1878
+ \u{1F4CB} \u5171 ${result.data.length} \u6761\u7ED3\u679C\uFF08\u603B\u8BA1 ${total} \u6761\uFF09`);
1879
+ }, { targetUrl: jmqTargetUrl(env) });
1880
+ });
1881
+ jmq.command("group").description("\u67E5\u770B Group \u8BE6\u60C5\uFF1A\u6D88\u8D39\u91CD\u8BD5\u3001\u79EF\u538B\u6570\u3001\u8BA2\u9605/\u751F\u4EA7\u7EBF\u7A0B\u6570\u7B49\u8FD0\u884C\u65F6\u6570\u636E").addHelpText("after", `
1882
+ \u73AF\u5883\u8BF4\u660E\uFF08--env\uFF09\uFF1A
1883
+ prod \u4E2D\u56FD\u7AD9\u751F\u4EA7\uFF08taishan.jd.com\uFF09
1884
+ test \u6D4B\u8BD5\u7AD9\uFF08test.taishan.jd.com\uFF09
1885
+ th \u65B0\u52A0\u5761\u751F\u4EA7\uFF08th.taishan.jd.com\uFF09
1886
+ hl \u8377\u5170\u7AD9\u751F\u4EA7\uFF08hl.taishan.jd.com\uFF09
1887
+ intl \u56FD\u9645\u7AD9\u751F\u4EA7\uFF08joybuy.taishan.jd.com\uFF09
1888
+ intl-test \u56FD\u9645\u6D4B\u8BD5\u7AD9\uFF08test-intl.taishan.jd.com\uFF09
1889
+
1890
+ \u4F7F\u7528\u793A\u4F8B\uFF1A
1891
+ # \u67E5\u770B Group \u8BE6\u60C5\uFF08\u4E2D\u56FD\u7AD9\u751F\u4EA7\uFF09
1892
+ yue jmq group baoxianMessageMall
1893
+
1894
+ # \u6D4B\u8BD5\u7AD9
1895
+ yue jmq group baoxianMessageMall --env test
1896
+
1897
+ # \u8377\u5170\u7AD9
1898
+ yue jmq group baoxianMessageMall --env hl
1899
+
1900
+ # JSON \u8F93\u51FA
1901
+ yue jmq group baoxianMessageMall --output json
1902
+ `).argument("<groupCode>", "Group \u540D\u79F0").option("--env <env>", "\u73AF\u5883\uFF1Aprod\uFF08\u9ED8\u8BA4\uFF09/ test / th / hl / intl / intl-test", "prod").option("--output <format>", "\u8F93\u51FA\u683C\u5F0F\uFF1Atable\uFF08\u9ED8\u8BA4\uFF09/ json", "table").action(async (groupCode, opts) => {
1903
+ const env = parseEnv(opts.env ?? "prod");
1904
+ const output = (opts.output ?? "table").toLowerCase();
1905
+ await withBrowser(async (pageCtx) => {
1906
+ try {
1907
+ await pageCtx.waitForNetworkIdle?.();
1908
+ } catch {
1909
+ }
1910
+ console.log(`\u{1F50D} \u641C\u7D22 Group: "${groupCode}" (${envLabel(env)})...`);
1911
+ let searchResult;
1912
+ try {
1913
+ searchResult = await searchGroups(pageCtx, groupCode, env, void 0, 50);
1914
+ } catch (err) {
1915
+ console.error(`\u274C \u641C\u7D22\u5931\u8D25: ${err.message}`);
1916
+ console.error(` \u8BF7\u786E\u4FDD\u6D4F\u89C8\u5668\u5DF2\u767B\u5F55 ${getEnvBaseUrl(env)}`);
1917
+ process.exit(1);
1918
+ }
1919
+ let selectedGroup;
1920
+ selectedGroup = searchResult.data.find((g) => g.code === groupCode);
1921
+ if (!selectedGroup) {
1922
+ selectedGroup = searchResult.data.find((g) => g.code.toLowerCase() === groupCode.toLowerCase());
1923
+ }
1924
+ if (!selectedGroup && searchResult.data.length > 0) {
1925
+ selectedGroup = searchResult.data[0];
1926
+ }
1927
+ if (!selectedGroup) {
1928
+ console.error(`\u274C \u672A\u627E\u5230 Group: "${groupCode}"`);
1929
+ process.exit(1);
1930
+ }
1931
+ if (selectedGroup.code !== groupCode) {
1932
+ console.log(`${YELLOW2}\u26A0 \u672A\u7CBE\u786E\u5339\u914D\uFF0C\u4F7F\u7528: ${selectedGroup.code}${RESET2}`);
1933
+ }
1934
+ console.log(`\u{1F4CA} \u83B7\u53D6 ${selectedGroup.code} \u7684\u8FD0\u884C\u65F6\u6570\u636E...`);
1935
+ let detail;
1936
+ try {
1937
+ detail = await getGroupDetail(pageCtx, selectedGroup.code, selectedGroup.id, env);
1938
+ } catch (err) {
1939
+ console.error(`\u274C \u83B7\u53D6\u8BE6\u60C5\u5931\u8D25: ${err.message}`);
1940
+ process.exit(1);
1941
+ }
1942
+ if (output === "json") {
1943
+ console.log(JSON.stringify(detail, null, 2));
1944
+ return;
1945
+ }
1946
+ console.log(`
1947
+ ${CYAN2}\u2501\u2501\u2501 ${detail.group} \u2501\u2501\u2501${RESET2}`);
1948
+ renderGroupDetail(detail);
1949
+ const totalSubs = detail.subscriptions.length;
1950
+ const totalProds = detail.productions.length;
1951
+ console.log(`
1952
+ \u{1F4CB} \u6D88\u8D39 ${totalSubs} \u4E2A Topic\uFF0C\u751F\u4EA7 ${totalProds} \u4E2A Topic`);
1953
+ }, { targetUrl: jmqTargetUrl(env) });
1954
+ });
1955
+ jmq.command("topic").description("\u67E5\u770B Topic \u8BE6\u60C5\uFF1A\u6D88\u8D39\u7EC4\u3001\u751F\u4EA7\u7EC4\u3001\u79EF\u538B\u3001\u91CD\u8BD5\u7B49\u8FD0\u884C\u65F6\u6570\u636E").addHelpText("after", `
1956
+ \u73AF\u5883\u8BF4\u660E\uFF08--env\uFF09\uFF1A
1957
+ prod \u4E2D\u56FD\u7AD9\u751F\u4EA7\uFF08taishan.jd.com\uFF09
1958
+ test \u6D4B\u8BD5\u7AD9\uFF08test.taishan.jd.com\uFF09
1959
+ th \u65B0\u52A0\u5761\u751F\u4EA7\uFF08th.taishan.jd.com\uFF09
1960
+ hl \u8377\u5170\u7AD9\u751F\u4EA7\uFF08hl.taishan.jd.com\uFF09
1961
+ intl \u56FD\u9645\u7AD9\u751F\u4EA7\uFF08joybuy.taishan.jd.com\uFF09
1962
+ intl-test \u56FD\u9645\u6D4B\u8BD5\u7AD9\uFF08test-intl.taishan.jd.com\uFF09
1963
+
1964
+ \u4F7F\u7528\u793A\u4F8B\uFF1A
1965
+ # \u67E5\u770B Topic \u8BE6\u60C5\uFF08\u4E2D\u56FD\u7AD9\u751F\u4EA7\uFF09
1966
+ yue jmq topic baoxianMessageMall_Order_Submit
1967
+
1968
+ # \u6D4B\u8BD5\u7AD9
1969
+ yue jmq topic baoxianMessageMall_Order_Submit --env test
1970
+
1971
+ # \u8377\u5170\u7AD9
1972
+ yue jmq topic baoxianMessageMall_Order_Submit --env hl
1973
+
1974
+ # JSON \u8F93\u51FA
1975
+ yue jmq topic baoxianMessageMall_Order_Submit --output json
1976
+ `).argument("<topicCode>", "Topic \u540D\u79F0").option("--env <env>", "\u73AF\u5883\uFF1Aprod\uFF08\u9ED8\u8BA4\uFF09/ test / th / hl / intl / intl-test", "prod").option("--output <format>", "\u8F93\u51FA\u683C\u5F0F\uFF1Atable\uFF08\u9ED8\u8BA4\uFF09/ json", "table").action(async (topicCode, opts) => {
1977
+ const env = parseEnv(opts.env ?? "prod");
1978
+ const output = (opts.output ?? "table").toLowerCase();
1979
+ await withBrowser(async (pageCtx) => {
1980
+ try {
1981
+ await pageCtx.waitForNetworkIdle?.();
1982
+ } catch {
1983
+ }
1984
+ console.log(`\u{1F4CA} \u83B7\u53D6 Topic "${topicCode}" \u7684\u8BE6\u60C5 (${envLabel(env)})...`);
1985
+ let detail;
1986
+ try {
1987
+ detail = await getTopicDetail(pageCtx, topicCode, env);
1988
+ } catch (err) {
1989
+ console.error(`\u274C \u83B7\u53D6\u8BE6\u60C5\u5931\u8D25: ${err.message}`);
1990
+ console.error(` \u8BF7\u786E\u4FDD\u6D4F\u89C8\u5668\u5DF2\u767B\u5F55 ${getEnvBaseUrl(env)}`);
1991
+ process.exit(1);
1992
+ }
1993
+ if (output === "json") {
1994
+ console.log(JSON.stringify(detail, null, 2));
1995
+ return;
1996
+ }
1997
+ console.log(`
1998
+ ${CYAN2}\u2501\u2501\u2501 ${detail.topic} \u2501\u2501\u2501${RESET2}`);
1999
+ renderTopicDetail(detail);
2000
+ const totalConsumers = detail.consumers.length;
2001
+ const totalProducers = detail.producers.length;
2002
+ console.log(`
2003
+ \u{1F4CB} ${totalConsumers} \u4E2A\u6D88\u8D39\u7EC4\uFF0C${totalProducers} \u4E2A\u751F\u4EA7\u7EC4`);
2004
+ }, { targetUrl: jmqTargetUrl(env) });
2005
+ });
585
2006
  }
586
2007
 
587
2008
  // src/cli.ts
588
2009
  function run() {
589
2010
  const program = new Command();
590
- program.name("yue").description("CLI tool for mydb & Digger \u2014 query databases, search logs").version("0.1.4");
591
- const mydb = program.command("mydb").description("mydb.jdfmgt.com operations");
2011
+ program.name("yue").description("yue-cli\uFF1Amydb \u67E5\u8BE2\u4E0E Digger \u65E5\u5FD7\u68C0\u7D22").version("0.1.5");
2012
+ const mydb = program.command("mydb").description("mydb\uFF08mydb.jdfmgt.com\uFF09\u76F8\u5173\u64CD\u4F5C");
592
2013
  registerDatasourcesCommand(mydb);
593
2014
  registerQueryCommand(mydb);
594
2015
  registerDiggerCommand(program);
2016
+ registerJmqCommand(program);
595
2017
  program.parse();
596
2018
  }
597
2019