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

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,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.traceRequest = traceRequest;
4
+ exports.traceResponse = traceResponse;
5
+ exports.traceError = traceError;
6
+ const config_1 = require("../../utils/config");
7
+ const logger_1 = require("../../utils/logger");
8
+ /**
9
+ * 从 HttpClient 实例抠出 baseURL,用于把相对 path 拼成完整 URL 输出。
10
+ * defaultConfig.baseURL 在 http-client 0.1.4 d.ts 里没声明但运行时可访问;
11
+ * 找不到时返回空串(trace 仍能输出 path,只是不完整)。
12
+ */
13
+ function clientBaseURL(client) {
14
+ return client?.defaultConfig?.baseURL ?? "";
15
+ }
16
+ /**
17
+ * 给 client 注册一个最后的 request interceptor:等 platform plugin / canary
18
+ * 等其它拦截器把 JWT / AK/SK / X-Miaoda-Client-Token / x-tt-env 注入 cfg.headers
19
+ * 之后,用注入完毕的 cfg 拼一行可直接复制运行的 curl 命令打到 stderr。
20
+ *
21
+ * 拦截器只装一次(用 client 实例的 Symbol 标记),verbose 关闭时拦截器内
22
+ * 直接 return(零开销)。
23
+ */
24
+ function installCurlTrace(client) {
25
+ const c = client;
26
+ if (c.__miaoda_curl_trace_installed__)
27
+ return;
28
+ // 单测里的 mock client 没有 interceptors,直接 noop 避免炸测试。
29
+ const interceptors = client.interceptors;
30
+ if (!interceptors)
31
+ return;
32
+ c.__miaoda_curl_trace_installed__ = true;
33
+ interceptors.request.use((cfg) => {
34
+ if (!(0, config_1.getConfig)().verbose)
35
+ return cfg;
36
+ const method = (cfg.method ?? "GET").toUpperCase();
37
+ const url = `${cfg.baseURL ?? ""}${cfg.url ?? ""}`;
38
+ const parts = [`curl -i -X ${method} '${url}'`];
39
+ const headers = cfg.headers instanceof Headers
40
+ ? cfg.headers
41
+ : new Headers(cfg.headers ?? {});
42
+ headers.forEach((v, k) => parts.push(` -H '${k}: ${v}'`));
43
+ if (cfg.body !== undefined && cfg.body !== null) {
44
+ let body;
45
+ if (typeof cfg.body === "string")
46
+ body = cfg.body;
47
+ else if (cfg.body instanceof ArrayBuffer || cfg.body instanceof Uint8Array)
48
+ body = "<binary>";
49
+ else
50
+ body = JSON.stringify(cfg.body);
51
+ const escaped = body.replace(/'/g, "'\\''");
52
+ parts.push(` -d '${escaped}'`);
53
+ }
54
+ (0, logger_1.debug)(`curl:\n${parts.join(" \\\n")}`);
55
+ return cfg;
56
+ });
57
+ }
58
+ /** 请求前打 method + 完整 URL;同时懒装 curl-trace 拦截器(idempotent)。 */
59
+ function traceRequest(method, url, client) {
60
+ if (client)
61
+ installCurlTrace(client);
62
+ (0, logger_1.debug)(`http → ${method} ${clientBaseURL(client)}${url}`);
63
+ }
64
+ /** 响应后打 status + x-tt-logid。 */
65
+ function traceResponse(method, url, status, headers, client) {
66
+ const logId = headers?.get("x-tt-logid") ?? headers?.get("X-Tt-Logid") ?? "(none)";
67
+ (0, logger_1.debug)(`http ← ${method} ${clientBaseURL(client)}${url} status=${String(status)} x-tt-logid=${logId}`);
68
+ }
69
+ /**
70
+ * 网络层错误(fetch failed / DNS / TLS / 连接拒绝)兜底:response 没回来,
71
+ * 没有 logid,但要把错误原因记到 trace,避免用户只看到 "Error: fetch failed"。
72
+ */
73
+ function traceError(method, url, err, client) {
74
+ const msg = err instanceof Error ? err.message : String(err);
75
+ (0, logger_1.debug)(`http ✗ ${method} ${clientBaseURL(client)}${url} (no response, network-level error: ${msg})`);
76
+ }
@@ -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,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.traceRequest = traceRequest;
4
+ exports.traceResponse = traceResponse;
5
+ exports.traceError = traceError;
6
+ const config_1 = require("../../utils/config");
7
+ const logger_1 = require("../../utils/logger");
8
+ function clientBaseURL(client) {
9
+ return client?.defaultConfig?.baseURL ?? "";
10
+ }
11
+ function installCurlTrace(client) {
12
+ const c = client;
13
+ if (c.__miaoda_curl_trace_installed__)
14
+ return;
15
+ // 单测里的 mock client 没有 interceptors,直接 noop 避免炸测试。
16
+ const interceptors = client.interceptors;
17
+ if (!interceptors)
18
+ return;
19
+ c.__miaoda_curl_trace_installed__ = true;
20
+ interceptors.request.use((cfg) => {
21
+ if (!(0, config_1.getConfig)().verbose)
22
+ return cfg;
23
+ const method = (cfg.method ?? "GET").toUpperCase();
24
+ const url = `${cfg.baseURL ?? ""}${cfg.url ?? ""}`;
25
+ const parts = [`curl -i -X ${method} '${url}'`];
26
+ const headers = cfg.headers instanceof Headers
27
+ ? cfg.headers
28
+ : new Headers(cfg.headers ?? {});
29
+ headers.forEach((v, k) => parts.push(` -H '${k}: ${v}'`));
30
+ if (cfg.body !== undefined && cfg.body !== null) {
31
+ let body;
32
+ if (typeof cfg.body === "string")
33
+ body = cfg.body;
34
+ else if (cfg.body instanceof ArrayBuffer || cfg.body instanceof Uint8Array)
35
+ body = "<binary>";
36
+ else
37
+ body = JSON.stringify(cfg.body);
38
+ const escaped = body.replace(/'/g, "'\\''");
39
+ parts.push(` -d '${escaped}'`);
40
+ }
41
+ (0, logger_1.debug)(`curl:\n${parts.join(" \\\n")}`);
42
+ return cfg;
43
+ });
44
+ }
45
+ /** 请求前打 method + 完整 URL;同时懒装 curl-trace 拦截器(idempotent)。 */
46
+ function traceRequest(method, url, client) {
47
+ if (client)
48
+ installCurlTrace(client);
49
+ (0, logger_1.debug)(`http → ${method} ${clientBaseURL(client)}${url}`);
50
+ }
51
+ /** 响应后打 status + x-tt-logid。 */
52
+ function traceResponse(method, url, status, headers, client) {
53
+ const logId = headers?.get("x-tt-logid") ?? headers?.get("X-Tt-Logid") ?? "(none)";
54
+ (0, logger_1.debug)(`http ← ${method} ${clientBaseURL(client)}${url} status=${String(status)} x-tt-logid=${logId}`);
55
+ }
56
+ /**
57
+ * 网络层错误(fetch failed / DNS / TLS / 连接拒绝)兜底:response 没回来,
58
+ * 没有 logid,但要把错误原因记到 trace,避免用户只看到 "Error: fetch failed"。
59
+ */
60
+ function traceError(method, url, err, client) {
61
+ const msg = err instanceof Error ? err.message : String(err);
62
+ (0, logger_1.debug)(`http ✗ ${method} ${clientBaseURL(client)}${url} (no response, network-level error: ${msg})`);
63
+ }
@@ -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.3879164",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {