@lark-apaas/miaoda-cli 0.1.2-alpha.c7d0c89 → 0.1.2-alpha.dd636c5

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.
@@ -14,7 +14,11 @@ function registerDeployCommands(program) {
14
14
  // PRD:`miaoda deploy` 自身即为触发发布;get / history / error-log 是子命令
15
15
  const deployCmd = program
16
16
  .command("deploy")
17
- .description("触发发布;支持 --wait 阻塞到终态。子命令:get / history / error-log")
17
+ .description(`触发发布;支持 --wait 阻塞到终态。子命令:get / history / error-log。 特别注意:
18
+ 1.触发发布前,Agent 必须先前置检查工作区状态:因为 miaoda deploy 跑的是当前分支的远端 HEAD——本地未 commit / 未 push 的代码不会进发布产物。调用前先 \`git status\` + \`git rev-list --count @{u}..HEAD\` 确认;有未提交 / 未推送时:
19
+ - 是本次任务的产物 → 先 commit + push 再 deploy
20
+ - 不能确定意图 → 用 AskUser 跟用户确认(不要默默继续)
21
+ `)
18
22
  .addOption((0, shared_1.appIdOption)().hideHelp())
19
23
  .addOption((0, shared_1.branchOption)().hideHelp())
20
24
  .option("--wait", "阻塞直到流水线终态", false)
@@ -70,14 +74,9 @@ function registerDeployHistory(parent) {
70
74
  .command("history")
71
75
  .description("查询发布历史(按时间倒序,分页)")
72
76
  .addOption((0, shared_1.appIdOption)().hideHelp())
73
- .addOption(new commander_1.Option("--status <status>", "状态过滤(pipeline 节点状态)").choices([
74
- "todo",
75
- "running",
76
- "success",
77
- "failed",
78
- "canceled",
79
- "hold_on",
80
- ]))
77
+ .addOption(new commander_1.Option("--status <status>", "状态过滤(pipeline 节点状态,大小写不敏感)")
78
+ .choices(["todo", "running", "success", "failed", "canceled", "hold_on"])
79
+ .argParser((0, shared_1.caseInsensitiveChoice)(["todo", "running", "success", "failed", "canceled", "hold_on"])))
81
80
  .option("--since <time>", "起始时间")
82
81
  .option("--until <time>", "截止时间")
83
82
  .option("--limit <n>", "返回条数上限(1~100)", parseLimit, 50)
@@ -37,7 +37,9 @@ function registerLog(parent) {
37
37
  .command("log")
38
38
  .description("查询线上运行日志(按时间倒序,分页)")
39
39
  .addOption((0, shared_1.appIdOption)().hideHelp())
40
- .addOption(new commander_1.Option("--level <level>", "日志级别").choices(["DEBUG", "INFO", "WARN", "ERROR"]))
40
+ .addOption(new commander_1.Option("--level <level>", "日志级别(大小写不敏感)")
41
+ .choices(["DEBUG", "INFO", "WARN", "ERROR"])
42
+ .argParser((0, shared_1.caseInsensitiveChoice)(["DEBUG", "INFO", "WARN", "ERROR"])))
41
43
  .option("--since <time>", "开始时间")
42
44
  .option("--until <time>", "截止时间")
43
45
  .option("--trace-id <id>", "按 trace ID 过滤")
@@ -156,8 +158,9 @@ function registerMetric(parent) {
156
158
  .option("--series <name>", "过滤图表中的某条线:latency 取 p50/p99;requests 取 total/error;缺省返回所有相关线")
157
159
  .option("--since <time>", "开始时间")
158
160
  .option("--until <time>", "截止时间")
159
- .addOption(new commander_1.Option("--down-sample <duration>", "降采样粒度(1m=1分钟 / 1h=1小时 / 1d=1天)")
161
+ .addOption(new commander_1.Option("--down-sample <duration>", "降采样粒度(1m=1分钟 / 1h=1小时 / 1d=1天,大小写不敏感)")
160
162
  .choices(["1m", "1h", "1d"])
163
+ .argParser((0, shared_1.caseInsensitiveChoice)(["1m", "1h", "1d"]))
161
164
  .default("1h"))
162
165
  .addHelpText("after", `${COMMON_TIME_HELP}
163
166
  JSON 输出
@@ -6,6 +6,7 @@ exports.softRequiredOption = softRequiredOption;
6
6
  exports.resolveAppId = resolveAppId;
7
7
  exports.withHelp = withHelp;
8
8
  exports.failArgs = failArgs;
9
+ exports.caseInsensitiveChoice = caseInsensitiveChoice;
9
10
  exports.rejectCliOverride = rejectCliOverride;
10
11
  const commander_1 = require("commander");
11
12
  const error_1 = require("../../utils/error");
@@ -62,6 +63,29 @@ function withHelp(cmd, handler) {
62
63
  function failArgs(message) {
63
64
  throw new error_1.AppError("ARGS_INVALID", message);
64
65
  }
66
+ /**
67
+ * 大小写不敏感的 choice argParser:把用户输入按 lowerCase 匹配回 canonical 数组里
68
+ * 的同名值(保留 canonical 大小写),未命中抛 Commander 的 InvalidArgumentError
69
+ * (exit code 1,自带友好错误)。
70
+ *
71
+ * 必须先 .choices(canonical)、再 .argParser()——Commander 内部 .choices() 会
72
+ * 重置 parseArg,反过来调会让 argParser 失效。.choices() 仅留给 help 渲染
73
+ * "choices: A, B, C",实际白名单校验由 argParser 接管。
74
+ *
75
+ * new Option("--level <level>", "日志级别(不区分大小写)")
76
+ * .choices(["DEBUG", "INFO", "WARN", "ERROR"])
77
+ * .argParser(caseInsensitiveChoice(["DEBUG", "INFO", "WARN", "ERROR"]));
78
+ */
79
+ function caseInsensitiveChoice(canonical) {
80
+ const map = new Map(canonical.map((v) => [v.toLowerCase(), v]));
81
+ return (raw) => {
82
+ const hit = map.get(raw.toLowerCase());
83
+ if (hit === undefined) {
84
+ throw new commander_1.InvalidArgumentError(`Allowed choices are ${canonical.join(", ")} (case-insensitive).`);
85
+ }
86
+ return hit;
87
+ };
88
+ }
65
89
  /**
66
90
  * 拒绝 CLI 显式覆盖:用于 hideHelp() + .env() 的"沙箱注入参数"(如 --app-id / --branch)。
67
91
  *
@@ -10,6 +10,8 @@ exports.postInnerApi = postInnerApi;
10
10
  exports.getInnerApi = getInnerApi;
11
11
  const http_client_1 = require("@lark-apaas/http-client");
12
12
  const error_1 = require("./error");
13
+ const config_1 = require("./config");
14
+ const logger_1 = require("./logger");
13
15
  let adminClient;
14
16
  let runtimeClient;
15
17
  /**
@@ -70,11 +72,17 @@ function applyCanaryHeader(reqConfig) {
70
72
  }
71
73
  /** 走管理端 innerapi 的 POST + 信封解析 */
72
74
  async function postInnerApi(url, body, opts) {
73
- return handleInnerEnvelope(await getHttpClient().post(url, body), url, opts);
75
+ const startMs = logRequestStart("POST", url, body);
76
+ const response = await getHttpClient().post(url, body);
77
+ logResponseEnd("POST", url, response, startMs);
78
+ return handleInnerEnvelope(response, url, opts);
74
79
  }
75
80
  /** 走管理端 innerapi 的 GET + 信封解析 */
76
81
  async function getInnerApi(url, opts) {
77
- return handleInnerEnvelope(await getHttpClient().get(url), url, opts);
82
+ const startMs = logRequestStart("GET", url);
83
+ const response = await getHttpClient().get(url);
84
+ logResponseEnd("GET", url, response, startMs);
85
+ return handleInnerEnvelope(response, url, opts);
78
86
  }
79
87
  async function handleInnerEnvelope(response, url, opts) {
80
88
  if (!response.ok) {
@@ -85,6 +93,10 @@ async function handleInnerEnvelope(response, url, opts) {
85
93
  const env = result;
86
94
  if (env.status_code !== undefined && env.status_code !== "0") {
87
95
  const msg = env.message ?? env.error_msg ?? "unknown error";
96
+ // verbose: 把完整业务错误信封打到 stderr,便于追溯
97
+ if ((0, config_1.getConfig)().verbose) {
98
+ (0, logger_1.debug)(` envelope: ${truncateForLog(safeStringify(env), 1000)}`);
99
+ }
88
100
  const mapped = opts.mapErr?.(env.status_code, msg);
89
101
  if (mapped)
90
102
  throw mapped;
@@ -97,3 +109,45 @@ async function handleInnerEnvelope(response, url, opts) {
97
109
  }
98
110
  return result;
99
111
  }
112
+ // ── verbose 调试日志 ────────────
113
+ //
114
+ // 仅作用于 inner-api 链路(postInnerApi / getInnerApi),即 app / deploy /
115
+ // observability 三个域。--verbose 关闭时所有 helper 都直接退出,零开销。
116
+ function logRequestStart(method, url, body) {
117
+ const startMs = Date.now();
118
+ if (!(0, config_1.getConfig)().verbose)
119
+ return startMs;
120
+ (0, logger_1.debug)(`→ ${method} ${url}`);
121
+ if (body !== undefined) {
122
+ (0, logger_1.debug)(` body: ${truncateForLog(safeStringify(body), 1000)}`);
123
+ }
124
+ return startMs;
125
+ }
126
+ function logResponseEnd(method, url, response, startMs) {
127
+ if (!(0, config_1.getConfig)().verbose)
128
+ return;
129
+ const elapsedMs = Date.now() - startMs;
130
+ const logid = pickLogid(response.headers);
131
+ const tail = logid ? ` logid=${logid}` : "";
132
+ (0, logger_1.debug)(`← ${method} ${url} ${String(response.status)} ${String(elapsedMs)}ms${tail}`);
133
+ }
134
+ /** BAM gateway 在不同 PSM/版本上 logid 落在不同 header;按命中顺序取第一个非空。 */
135
+ function pickLogid(headers) {
136
+ return (headers.get("x-tt-logid") ??
137
+ headers.get("x-logid") ??
138
+ headers.get("logid") ??
139
+ headers.get("x-tt-trace-tag"));
140
+ }
141
+ function safeStringify(v) {
142
+ try {
143
+ return JSON.stringify(v);
144
+ }
145
+ catch {
146
+ return String(v);
147
+ }
148
+ }
149
+ function truncateForLog(s, n) {
150
+ if (s.length <= n)
151
+ return s;
152
+ return `${s.slice(0, n)}...(truncated, ${String(s.length)} chars)`;
153
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.2-alpha.c7d0c89",
3
+ "version": "0.1.2-alpha.dd636c5",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {