@lark-apaas/miaoda-cli 0.1.0-alpha.36c401b → 0.1.0-alpha.7235c35

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.
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.traceRequest = traceRequest;
4
+ exports.traceResponse = traceResponse;
5
+ exports.traceError = traceError;
6
+ const logger_1 = require("../../utils/logger");
7
+ /**
8
+ * 从 HttpClient 实例抠出 baseURL,用于把相对 path 拼成完整 URL 输出。
9
+ * defaultConfig.baseURL 在 http-client 0.1.4 d.ts 里没声明但运行时可访问;
10
+ * 找不到时返回空串(trace 仍能输出 path,只是不完整)。
11
+ */
12
+ function clientBaseURL(client) {
13
+ return client?.defaultConfig?.baseURL ?? "";
14
+ }
15
+ /** 请求前打 method + 完整 URL(baseURL + path)。 */
16
+ function traceRequest(method, url, client) {
17
+ (0, logger_1.debug)(`http → ${method} ${clientBaseURL(client)}${url}`);
18
+ }
19
+ /** 响应后打 status + x-tt-logid。 */
20
+ function traceResponse(method, url, status, headers, client) {
21
+ const logId = headers?.get("x-tt-logid") ?? headers?.get("X-Tt-Logid") ?? "(none)";
22
+ (0, logger_1.debug)(`http ← ${method} ${clientBaseURL(client)}${url} status=${String(status)} x-tt-logid=${logId}`);
23
+ }
24
+ /**
25
+ * 网络层错误(fetch failed / DNS / TLS / 连接拒绝)兜底:response 没回来,
26
+ * 没有 logid,但要把错误原因记到 trace,避免用户只看到 "Error: fetch failed"。
27
+ */
28
+ function traceError(method, url, err, client) {
29
+ const msg = err instanceof Error ? err.message : String(err);
30
+ (0, logger_1.debug)(`http ✗ ${method} ${clientBaseURL(client)}${url} (no response, network-level error: ${msg})`);
31
+ }
@@ -5,6 +5,8 @@ exports.getSchema = getSchema;
5
5
  exports.importData = importData;
6
6
  exports.exportData = exportData;
7
7
  const http_1 = require("../../utils/http");
8
+ // TODO(REMOVE-BEFORE-RELEASE): debug-only HTTP trace(详见 _debug_trace.ts)
9
+ const _debug_trace_1 = require("./_debug_trace");
8
10
  const error_1 = require("../../utils/error");
9
11
  const client_1 = require("./client");
10
12
  // CLI 不再为 dbBranch 设默认值:
@@ -24,7 +26,16 @@ async function execSql(opts) {
24
26
  const url = (0, client_1.buildInnerUrl)(opts.appId, "/db/sql", {
25
27
  dbBranch: opts.dbBranch,
26
28
  });
27
- const response = await client.post(url, { sql: opts.sql });
29
+ let response;
30
+ try {
31
+ (0, _debug_trace_1.traceRequest)("POST", url, client);
32
+ response = await client.post(url, { sql: opts.sql });
33
+ (0, _debug_trace_1.traceResponse)("POST", url, response.status, response.headers, client);
34
+ }
35
+ catch (err) {
36
+ (0, _debug_trace_1.traceError)("POST", url, err, client);
37
+ throw err;
38
+ }
28
39
  if (!response.ok) {
29
40
  // 4xx / 5xx:尝试解析 body 取 status_code 映射业务错误
30
41
  let body = null;
@@ -57,7 +68,16 @@ async function getSchema(opts) {
57
68
  includeStats: opts.includeStats ? "true" : undefined,
58
69
  dbBranch: opts.dbBranch,
59
70
  });
60
- const response = await client.get(url);
71
+ let response;
72
+ try {
73
+ (0, _debug_trace_1.traceRequest)("GET", url, client);
74
+ response = await client.get(url);
75
+ (0, _debug_trace_1.traceResponse)("GET", url, response.status, response.headers, client);
76
+ }
77
+ catch (err) {
78
+ (0, _debug_trace_1.traceError)("GET", url, err, client);
79
+ throw err;
80
+ }
61
81
  if (!response.ok) {
62
82
  let body = null;
63
83
  try {
@@ -89,12 +109,21 @@ async function importData(opts) {
89
109
  });
90
110
  const contentType = opts.format === "csv" ? "text/csv" : "application/json";
91
111
  const ab = opts.body.buffer.slice(opts.body.byteOffset, opts.body.byteOffset + opts.body.byteLength);
92
- const response = await client.request({
93
- method: "POST",
94
- url,
95
- headers: { "Content-Type": contentType },
96
- body: ab,
97
- });
112
+ let response;
113
+ try {
114
+ (0, _debug_trace_1.traceRequest)("POST", url, client);
115
+ response = await client.request({
116
+ method: "POST",
117
+ url,
118
+ headers: { "Content-Type": contentType },
119
+ body: ab,
120
+ });
121
+ (0, _debug_trace_1.traceResponse)("POST", url, response.status, response.headers, client);
122
+ }
123
+ catch (err) {
124
+ (0, _debug_trace_1.traceError)("POST", url, err, client);
125
+ throw err;
126
+ }
98
127
  if (!response.ok) {
99
128
  let body = null;
100
129
  try {
@@ -131,7 +160,16 @@ async function exportData(opts) {
131
160
  dbBranch: opts.dbBranch,
132
161
  });
133
162
  const reqBody = { limit: opts.limit ?? 5000 };
134
- const response = await client.post(url, reqBody);
163
+ let response;
164
+ try {
165
+ (0, _debug_trace_1.traceRequest)("POST", url, client);
166
+ response = await client.post(url, reqBody);
167
+ (0, _debug_trace_1.traceResponse)("POST", url, response.status, response.headers, client);
168
+ }
169
+ catch (err) {
170
+ (0, _debug_trace_1.traceError)("POST", url, err, client);
171
+ throw err;
172
+ }
135
173
  if (!response.ok) {
136
174
  // 错误路径:body 是 JSON envelope
137
175
  let body = null;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.traceRequest = traceRequest;
4
+ exports.traceResponse = traceResponse;
5
+ exports.traceError = traceError;
6
+ const logger_1 = require("../../utils/logger");
7
+ function clientBaseURL(client) {
8
+ return client?.defaultConfig?.baseURL ?? "";
9
+ }
10
+ /** 请求前打 method + 完整 URL(baseURL + path)。 */
11
+ function traceRequest(method, url, client) {
12
+ (0, logger_1.debug)(`http → ${method} ${clientBaseURL(client)}${url}`);
13
+ }
14
+ /** 响应后打 status + x-tt-logid。 */
15
+ function traceResponse(method, url, status, headers, client) {
16
+ const logId = headers?.get("x-tt-logid") ?? headers?.get("X-Tt-Logid") ?? "(none)";
17
+ (0, logger_1.debug)(`http ← ${method} ${clientBaseURL(client)}${url} status=${String(status)} x-tt-logid=${logId}`);
18
+ }
19
+ /**
20
+ * 网络层错误(fetch failed / DNS / TLS / 连接拒绝)兜底:response 没回来,
21
+ * 没有 logid,但要把错误原因记到 trace,避免用户只看到 "Error: fetch failed"。
22
+ */
23
+ function traceError(method, url, err, client) {
24
+ const msg = err instanceof Error ? err.message : String(err);
25
+ (0, logger_1.debug)(`http ✗ ${method} ${clientBaseURL(client)}${url} (no response, network-level error: ${msg})`);
26
+ }
@@ -8,6 +8,8 @@ exports.doPost = doPost;
8
8
  exports.doRequest = doRequest;
9
9
  const http_1 = require("../../utils/http");
10
10
  const error_1 = require("../../utils/error");
11
+ // TODO(REMOVE-BEFORE-RELEASE): debug-only HTTP trace(详见 _debug_trace.ts)
12
+ const _debug_trace_1 = require("./_debug_trace");
11
13
  const http_client_1 = require("@lark-apaas/http-client");
12
14
  /** 进程内 bucket 缓存:{appId: bucketId}。不跨进程。 */
13
15
  const bucketCache = new Map();
@@ -131,10 +133,18 @@ async function mapHttpError(err, opts) {
131
133
  */
132
134
  async function doGet(url, opts = {}, client = (0, http_1.getHttpClient)()) {
133
135
  try {
136
+ (0, _debug_trace_1.traceRequest)("GET", url, client);
134
137
  const response = await client.get(url);
138
+ (0, _debug_trace_1.traceResponse)("GET", url, response.status, response.headers, client);
135
139
  return (await response.json());
136
140
  }
137
141
  catch (err) {
142
+ if (err instanceof http_client_1.HttpError && err.response) {
143
+ (0, _debug_trace_1.traceResponse)("GET", url, err.response.status, err.response.headers, client);
144
+ }
145
+ else {
146
+ (0, _debug_trace_1.traceError)("GET", url, err, client);
147
+ }
138
148
  await mapHttpError(err, opts);
139
149
  throw err; // 不可达,mapHttpError 必定 throw
140
150
  }
@@ -142,10 +152,18 @@ async function doGet(url, opts = {}, client = (0, http_1.getHttpClient)()) {
142
152
  /** 包一层 client.post + HttpError 统一映射。默认 admin innerapi,可显式切 runtime。 */
143
153
  async function doPost(url, body, opts = {}, client = (0, http_1.getHttpClient)()) {
144
154
  try {
155
+ (0, _debug_trace_1.traceRequest)("POST", url, client);
145
156
  const response = await client.post(url, body);
157
+ (0, _debug_trace_1.traceResponse)("POST", url, response.status, response.headers, client);
146
158
  return (await response.json());
147
159
  }
148
160
  catch (err) {
161
+ if (err instanceof http_client_1.HttpError && err.response) {
162
+ (0, _debug_trace_1.traceResponse)("POST", url, err.response.status, err.response.headers, client);
163
+ }
164
+ else {
165
+ (0, _debug_trace_1.traceError)("POST", url, err, client);
166
+ }
149
167
  await mapHttpError(err, opts);
150
168
  throw err;
151
169
  }
@@ -153,10 +171,18 @@ async function doPost(url, body, opts = {}, client = (0, http_1.getHttpClient)()
153
171
  /** 包一层 client.request + HttpError 统一映射(DELETE 带 body 的场景)。 */
154
172
  async function doRequest(cfg, opts = {}, client = (0, http_1.getHttpClient)()) {
155
173
  try {
174
+ (0, _debug_trace_1.traceRequest)(cfg.method, cfg.url, client);
156
175
  const response = await client.request(cfg);
176
+ (0, _debug_trace_1.traceResponse)(cfg.method, cfg.url, response.status, response.headers, client);
157
177
  return (await response.json());
158
178
  }
159
179
  catch (err) {
180
+ if (err instanceof http_client_1.HttpError && err.response) {
181
+ (0, _debug_trace_1.traceResponse)(cfg.method, cfg.url, err.response.status, err.response.headers, client);
182
+ }
183
+ else {
184
+ (0, _debug_trace_1.traceError)(cfg.method, cfg.url, err, client);
185
+ }
160
186
  await mapHttpError(err, opts);
161
187
  throw err;
162
188
  }
@@ -7,7 +7,9 @@ exports.withHelp = withHelp;
7
7
  exports.failArgs = failArgs;
8
8
  const commander_1 = require("commander");
9
9
  const error_1 = require("../../utils/error");
10
- /** --app-id option,需要应用上下文的命令自行 .addOption(appIdOption()) */
10
+ /** --app-id option,需要应用上下文的命令自行 .addOption(appIdOption())
11
+ * Commander 的 .env() 只接受单个变量名,第二个兜底 `app_id` 在 resolveAppId 里手动检查。
12
+ */
11
13
  function appIdOption() {
12
14
  return new commander_1.Option("--app-id <id>", "指定目标应用").env("MIAODA_APP_ID");
13
15
  }
@@ -17,9 +19,12 @@ function appIdOption() {
17
19
  function softRequiredOption(name, desc) {
18
20
  return new commander_1.Option(name, desc);
19
21
  }
20
- /** 解析 appId,CLI flag > env > 抛错 */
22
+ /**
23
+ * 解析 appId,优先级:CLI flag > MIAODA_APP_ID > app_id > 抛错。
24
+ * app_id 是部分外部沙箱环境注入应用 ID 的别名,作为兜底兼容(小写下划线形态)。
25
+ */
21
26
  function resolveAppId(opts) {
22
- const id = opts.appId ?? process.env.MIAODA_APP_ID;
27
+ const id = opts.appId ?? process.env.MIAODA_APP_ID ?? process.env.app_id;
23
28
  if (!id) {
24
29
  throw new error_1.AppError("APP_ID_MISSING", "未指定应用", {
25
30
  next_actions: [
@@ -114,7 +114,7 @@ function renderSingle(raw) {
114
114
  return;
115
115
  }
116
116
  const cols = collectColumns(parsed.rows);
117
- const rows = parsed.rows.map((r) => cols.map((c) => formatCell(r[c])));
117
+ const rows = parsed.rows.map((r) => cols.map((c) => formatCell(r[c], tty)));
118
118
  (0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(cols, rows) : (0, render_1.renderTsv)(cols, rows));
119
119
  return;
120
120
  }
@@ -155,9 +155,19 @@ function collectColumns(rows) {
155
155
  return [];
156
156
  return Object.keys(rows[0]);
157
157
  }
158
- function formatCell(v) {
159
- if (v === null || v === undefined)
160
- return "";
158
+ /**
159
+ * SQL 单元格 字符串:
160
+ * - null / undefined → "NULL"(TTY 下用 dim+gray ANSI 弱化,对齐 mysql/DuckDB;
161
+ * non-TTY 用裸字面量,避免被 cat / 管道吞掉样式;JSON 模式不会走到这里,
162
+ * 走 toJson 直接用 parsed.rows,JSON.stringify 输出 `null` 字面)
163
+ * - string → 原样
164
+ * - number / boolean → toString
165
+ * - object / array → JSON.stringify
166
+ */
167
+ function formatCell(v, tty) {
168
+ if (v === null || v === undefined) {
169
+ return tty ? "\u001b[2;90mNULL\u001b[0m" : "NULL";
170
+ }
161
171
  if (typeof v === "string")
162
172
  return v;
163
173
  if (typeof v === "number" || typeof v === "boolean")
@@ -14,6 +14,7 @@
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.formatSize = formatSize;
16
16
  exports.formatTime = formatTime;
17
+ exports.visibleWidth = visibleWidth;
17
18
  exports.renderAlignedTable = renderAlignedTable;
18
19
  exports.renderTsv = renderTsv;
19
20
  exports.renderKeyValue = renderKeyValue;
@@ -64,21 +65,35 @@ function formatTime(iso, isTty) {
64
65
  const da = String(date.getDate()).padStart(2, "0");
65
66
  return `${y}-${mo}-${da}`;
66
67
  }
67
- /** 渲染 TTY 对齐表格(多行,列宽按最长内容)。 */
68
+ /**
69
+ * SGR (Select Graphic Rendition) ANSI 转义匹配:`ESC[<digits;...>m`。
70
+ * 用于在列宽计算时剥离不显示的控制字符——否则带 ANSI 颜色的 cell 会按 .length
71
+ * 算进列宽(比如 dim "NULL" 实际占 4 字符但 .length=11),导致表格对齐错位。
72
+ */
73
+ const ANSI_SGR_RE = /\[[0-9;]*m/g;
74
+ /** cell 可见字符宽度:剥离 ANSI 转义后再算长度。 */
75
+ function visibleWidth(s) {
76
+ return s.replace(ANSI_SGR_RE, "").length;
77
+ }
78
+ function padVisibleEnd(s, targetWidth) {
79
+ const w = visibleWidth(s);
80
+ return w >= targetWidth ? s : s + " ".repeat(targetWidth - w);
81
+ }
82
+ /** 渲染 TTY 对齐表格(多行,列宽按最长内容;ANSI 转义不计入列宽)。 */
68
83
  function renderAlignedTable(headers, rows) {
69
84
  const colWidths = headers.map((h, i) => {
70
- let w = h.length;
85
+ let w = visibleWidth(h);
71
86
  for (const row of rows) {
72
- const cell = row[i] || "";
73
- if (cell.length > w)
74
- w = cell.length;
87
+ const cw = visibleWidth(row[i] || "");
88
+ if (cw > w)
89
+ w = cw;
75
90
  }
76
91
  return w;
77
92
  });
78
93
  const lines = [];
79
- lines.push(headers.map((h, i) => h.padEnd(colWidths[i])).join(" ").trimEnd());
94
+ lines.push(headers.map((h, i) => padVisibleEnd(h, colWidths[i])).join(" ").trimEnd());
80
95
  for (const row of rows) {
81
- lines.push(row.map((cell, i) => (cell || "").padEnd(colWidths[i])).join(" ").trimEnd());
96
+ lines.push(row.map((cell, i) => padVisibleEnd(cell || "", colWidths[i])).join(" ").trimEnd());
82
97
  }
83
98
  return lines.join("\n");
84
99
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.0-alpha.36c401b",
3
+ "version": "0.1.0-alpha.7235c35",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {