@lark-apaas/miaoda-cli 0.1.0-alpha.7235c35 → 0.1.0-alpha.8dcc262
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.
- package/dist/api/db/api.js +9 -47
- package/dist/api/db/client.js +17 -4
- package/dist/api/db/parsers.js +10 -6
- package/dist/api/file/api.js +2 -2
- package/dist/api/file/client.js +0 -26
- package/dist/cli/commands/db/index.js +1 -1
- package/dist/cli/commands/file/index.js +15 -1
- package/dist/cli/handlers/db/schema.js +6 -6
- package/dist/cli/handlers/db/sql.js +94 -19
- package/dist/cli/handlers/file/ls.js +2 -1
- package/dist/cli/handlers/file/rm.js +27 -7
- package/dist/utils/error.js +3 -0
- package/dist/utils/output.js +8 -5
- package/dist/utils/render.js +32 -2
- package/package.json +1 -1
- package/dist/api/db/_debug_trace.js +0 -31
- package/dist/api/file/_debug_trace.js +0 -26
package/dist/api/db/api.js
CHANGED
|
@@ -5,8 +5,6 @@ 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");
|
|
10
8
|
const error_1 = require("../../utils/error");
|
|
11
9
|
const client_1 = require("./client");
|
|
12
10
|
// CLI 不再为 dbBranch 设默认值:
|
|
@@ -26,16 +24,7 @@ async function execSql(opts) {
|
|
|
26
24
|
const url = (0, client_1.buildInnerUrl)(opts.appId, "/db/sql", {
|
|
27
25
|
dbBranch: opts.dbBranch,
|
|
28
26
|
});
|
|
29
|
-
|
|
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
|
-
}
|
|
27
|
+
const response = await client.post(url, { sql: opts.sql });
|
|
39
28
|
if (!response.ok) {
|
|
40
29
|
// 4xx / 5xx:尝试解析 body 取 status_code 映射业务错误
|
|
41
30
|
let body = null;
|
|
@@ -68,16 +57,7 @@ async function getSchema(opts) {
|
|
|
68
57
|
includeStats: opts.includeStats ? "true" : undefined,
|
|
69
58
|
dbBranch: opts.dbBranch,
|
|
70
59
|
});
|
|
71
|
-
|
|
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
|
-
}
|
|
60
|
+
const response = await client.get(url);
|
|
81
61
|
if (!response.ok) {
|
|
82
62
|
let body = null;
|
|
83
63
|
try {
|
|
@@ -109,21 +89,12 @@ async function importData(opts) {
|
|
|
109
89
|
});
|
|
110
90
|
const contentType = opts.format === "csv" ? "text/csv" : "application/json";
|
|
111
91
|
const ab = opts.body.buffer.slice(opts.body.byteOffset, opts.body.byteOffset + opts.body.byteLength);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
}
|
|
92
|
+
const response = await client.request({
|
|
93
|
+
method: "POST",
|
|
94
|
+
url,
|
|
95
|
+
headers: { "Content-Type": contentType },
|
|
96
|
+
body: ab,
|
|
97
|
+
});
|
|
127
98
|
if (!response.ok) {
|
|
128
99
|
let body = null;
|
|
129
100
|
try {
|
|
@@ -160,16 +131,7 @@ async function exportData(opts) {
|
|
|
160
131
|
dbBranch: opts.dbBranch,
|
|
161
132
|
});
|
|
162
133
|
const reqBody = { limit: opts.limit ?? 5000 };
|
|
163
|
-
|
|
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
|
-
}
|
|
134
|
+
const response = await client.post(url, reqBody);
|
|
173
135
|
if (!response.ok) {
|
|
174
136
|
// 错误路径:body 是 JSON envelope
|
|
175
137
|
let body = null;
|
package/dist/api/db/client.js
CHANGED
|
@@ -19,12 +19,16 @@ function ensureInnerSuccess(body) {
|
|
|
19
19
|
const code = body.status_code ?? body.ErrorCode ?? "0";
|
|
20
20
|
if (code === "0" || code === "")
|
|
21
21
|
return;
|
|
22
|
-
const message = body.error_msg ?? body.ErrorMessage ?? `dataloom API error [${code}]
|
|
22
|
+
const message = stripPgPrefix(body.error_msg ?? body.ErrorMessage ?? `dataloom API error [${code}]`);
|
|
23
|
+
// PRD 多语句失败:后端在 envelope 顶层透出 errorStatementIndex(从 0 起计),
|
|
24
|
+
// 单语句 / 单元执行不会带这个字段。下面的 AppError 都把它带上,让最终
|
|
25
|
+
// CLI JSON envelope 写到 error.statement_index。
|
|
26
|
+
const stmtIdx = typeof body.errorStatementIndex === "number" ? body.errorStatementIndex : undefined;
|
|
23
27
|
// k_dl_1300002 是 PG 执行透传错误;error_msg 里常带 SQLSTATE,优先按 SQLSTATE 映射
|
|
24
28
|
if (code === "k_dl_1300002") {
|
|
25
29
|
const sqlstate = extractSqlstate(message);
|
|
26
30
|
if (sqlstate && exports.SQLSTATE_MAP[sqlstate]) {
|
|
27
|
-
throw new error_1.AppError(exports.SQLSTATE_MAP[sqlstate], message);
|
|
31
|
+
throw new error_1.AppError(exports.SQLSTATE_MAP[sqlstate], message, { statement_index: stmtIdx });
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
34
|
// k_dl_1600000 是 dataloom 通用参数错误,不能整体映射;
|
|
@@ -35,7 +39,7 @@ function ensureInnerSuccess(body) {
|
|
|
35
39
|
if (code === "k_dl_1600000" && message.startsWith("Invalid DB Branch")) {
|
|
36
40
|
throw new error_1.AppError("MULTI_ENV_NOT_INITIALIZED", "--env is not available (multi-env not initialized)", {
|
|
37
41
|
next_actions: [
|
|
38
|
-
"Verify the --env value matches an existing dbBranch
|
|
42
|
+
"Verify the --env value matches an existing dbBranch.",
|
|
39
43
|
],
|
|
40
44
|
});
|
|
41
45
|
}
|
|
@@ -44,10 +48,19 @@ function ensureInnerSuccess(body) {
|
|
|
44
48
|
if (mapped) {
|
|
45
49
|
throw new error_1.AppError(mapped.code, mapped.message ?? message, {
|
|
46
50
|
next_actions: mapped.hint ? [mapped.hint] : undefined,
|
|
51
|
+
statement_index: stmtIdx,
|
|
47
52
|
});
|
|
48
53
|
}
|
|
49
54
|
// 兜底:dataloom 未映射的 code 原样透传
|
|
50
|
-
throw new error_1.AppError(`DB_API_${code}`, message);
|
|
55
|
+
throw new error_1.AppError(`DB_API_${code}`, message, { statement_index: stmtIdx });
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 剥掉 PG 透传错误的 "ERROR:" 前缀:CLI 输出本身就会前缀 "Error:",
|
|
59
|
+
* 不去掉就会变成 `Error: ERROR: relation ...` 双重前缀,PRD 不要这种冗余。
|
|
60
|
+
* 大小写敏感,只匹配 PG 标准格式。
|
|
61
|
+
*/
|
|
62
|
+
function stripPgPrefix(msg) {
|
|
63
|
+
return msg.replace(/^ERROR:\s*/, "");
|
|
51
64
|
}
|
|
52
65
|
/** 从 PG 执行错误消息里提取 "(SQLSTATE XXXXX)"。 */
|
|
53
66
|
function extractSqlstate(msg) {
|
package/dist/api/db/parsers.js
CHANGED
|
@@ -27,16 +27,23 @@ function parseSqlResult(r) {
|
|
|
27
27
|
recordCount: r.recordCount ?? rows.length,
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
if (r.sqlType === "INSERT" ||
|
|
30
|
+
if (r.sqlType === "INSERT" ||
|
|
31
|
+
r.sqlType === "UPDATE" ||
|
|
32
|
+
r.sqlType === "DELETE" ||
|
|
33
|
+
r.sqlType === "MERGE" ||
|
|
34
|
+
r.sqlType === "DML") {
|
|
31
35
|
const affected = r.affectedRows ?? extractRowCount(r.data);
|
|
32
36
|
return {
|
|
33
37
|
kind: "dml",
|
|
38
|
+
// 上面已 narrow,这里 cast 是为了 SqlType 联合里的 (string & {}) 让 TS 无法
|
|
39
|
+
// 自动收窄到字面量集合,不影响运行时安全
|
|
34
40
|
sqlType: r.sqlType,
|
|
35
41
|
affectedRows: affected,
|
|
36
42
|
};
|
|
37
43
|
}
|
|
38
|
-
// DDL or unknown
|
|
39
|
-
|
|
44
|
+
// DDL or unknown — sqlType 透传后端给的细粒度(CREATE_TABLE / DROP_TABLE / ...
|
|
45
|
+
// / 笼统 "DDL"),CLI JSON 输出直接当 command 用
|
|
46
|
+
return { kind: "ddl", sqlType: r.sqlType };
|
|
40
47
|
}
|
|
41
48
|
/** DML 的 data 通常是 `[{"rowCount": N}]`;兜底从这里读影响行数。 */
|
|
42
49
|
function extractRowCount(data) {
|
|
@@ -82,7 +89,6 @@ function toSummary(t, stats) {
|
|
|
82
89
|
columns: (t.fields ?? []).length,
|
|
83
90
|
estimated_row_count: typeof stats?.estimatedRowCount === "number" ? stats.estimatedRowCount : null,
|
|
84
91
|
size_bytes: typeof stats?.sizeBytes === "number" ? stats.sizeBytes : null,
|
|
85
|
-
updated_at: t.updatedAt,
|
|
86
92
|
};
|
|
87
93
|
}
|
|
88
94
|
/**
|
|
@@ -118,8 +124,6 @@ function toDetail(t, stats) {
|
|
|
118
124
|
indexes: rawIndexes.map(toIndex),
|
|
119
125
|
estimated_row_count: typeof stats?.estimatedRowCount === "number" ? stats.estimatedRowCount : null,
|
|
120
126
|
size_bytes: typeof stats?.sizeBytes === "number" ? stats.sizeBytes : null,
|
|
121
|
-
created_at: t.createdAt,
|
|
122
|
-
updated_at: t.updatedAt,
|
|
123
127
|
};
|
|
124
128
|
}
|
|
125
129
|
function toColumn(f) {
|
package/dist/api/file/api.js
CHANGED
|
@@ -214,7 +214,7 @@ async function resolveByName(appId, input) {
|
|
|
214
214
|
error: {
|
|
215
215
|
code: "FILE_NOT_FOUND",
|
|
216
216
|
message: `File '${input}' does not exist`,
|
|
217
|
-
hint: "Run `miaoda file ls` to
|
|
217
|
+
hint: "Run `miaoda file ls` to see available files.",
|
|
218
218
|
},
|
|
219
219
|
};
|
|
220
220
|
}
|
|
@@ -225,7 +225,7 @@ async function resolveByName(appId, input) {
|
|
|
225
225
|
error: {
|
|
226
226
|
code: "AMBIGUOUS_FILE_NAME",
|
|
227
227
|
message: `Multiple files match name '${input}' (${String(matches.length)} found)`,
|
|
228
|
-
hint: `Use
|
|
228
|
+
hint: `Use path instead. Run \`miaoda file ls --name ${input}\` to see candidates.`,
|
|
229
229
|
},
|
|
230
230
|
};
|
|
231
231
|
}
|
package/dist/api/file/client.js
CHANGED
|
@@ -8,8 +8,6 @@ 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");
|
|
13
11
|
const http_client_1 = require("@lark-apaas/http-client");
|
|
14
12
|
/** 进程内 bucket 缓存:{appId: bucketId}。不跨进程。 */
|
|
15
13
|
const bucketCache = new Map();
|
|
@@ -133,18 +131,10 @@ async function mapHttpError(err, opts) {
|
|
|
133
131
|
*/
|
|
134
132
|
async function doGet(url, opts = {}, client = (0, http_1.getHttpClient)()) {
|
|
135
133
|
try {
|
|
136
|
-
(0, _debug_trace_1.traceRequest)("GET", url, client);
|
|
137
134
|
const response = await client.get(url);
|
|
138
|
-
(0, _debug_trace_1.traceResponse)("GET", url, response.status, response.headers, client);
|
|
139
135
|
return (await response.json());
|
|
140
136
|
}
|
|
141
137
|
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
|
-
}
|
|
148
138
|
await mapHttpError(err, opts);
|
|
149
139
|
throw err; // 不可达,mapHttpError 必定 throw
|
|
150
140
|
}
|
|
@@ -152,18 +142,10 @@ async function doGet(url, opts = {}, client = (0, http_1.getHttpClient)()) {
|
|
|
152
142
|
/** 包一层 client.post + HttpError 统一映射。默认 admin innerapi,可显式切 runtime。 */
|
|
153
143
|
async function doPost(url, body, opts = {}, client = (0, http_1.getHttpClient)()) {
|
|
154
144
|
try {
|
|
155
|
-
(0, _debug_trace_1.traceRequest)("POST", url, client);
|
|
156
145
|
const response = await client.post(url, body);
|
|
157
|
-
(0, _debug_trace_1.traceResponse)("POST", url, response.status, response.headers, client);
|
|
158
146
|
return (await response.json());
|
|
159
147
|
}
|
|
160
148
|
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
|
-
}
|
|
167
149
|
await mapHttpError(err, opts);
|
|
168
150
|
throw err;
|
|
169
151
|
}
|
|
@@ -171,18 +153,10 @@ async function doPost(url, body, opts = {}, client = (0, http_1.getHttpClient)()
|
|
|
171
153
|
/** 包一层 client.request + HttpError 统一映射(DELETE 带 body 的场景)。 */
|
|
172
154
|
async function doRequest(cfg, opts = {}, client = (0, http_1.getHttpClient)()) {
|
|
173
155
|
try {
|
|
174
|
-
(0, _debug_trace_1.traceRequest)(cfg.method, cfg.url, client);
|
|
175
156
|
const response = await client.request(cfg);
|
|
176
|
-
(0, _debug_trace_1.traceResponse)(cfg.method, cfg.url, response.status, response.headers, client);
|
|
177
157
|
return (await response.json());
|
|
178
158
|
}
|
|
179
159
|
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
|
-
}
|
|
186
160
|
await mapHttpError(err, opts);
|
|
187
161
|
throw err;
|
|
188
162
|
}
|
|
@@ -28,7 +28,7 @@ function registerDbCommands(program) {
|
|
|
28
28
|
});
|
|
29
29
|
schemaCmd
|
|
30
30
|
.command("list")
|
|
31
|
-
.description("列出应用所有表(
|
|
31
|
+
.description("列出应用所有表(estimated_row_count / size / columns)")
|
|
32
32
|
.addOption((0, shared_1.appIdOption)())
|
|
33
33
|
.option("--env <env>", "目标环境(main / dev);不传由后端按多环境状态兜底")
|
|
34
34
|
.action(async (opts) => {
|
|
@@ -3,6 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerFileCommands = registerFileCommands;
|
|
4
4
|
const index_1 = require("../../../cli/handlers/file/index");
|
|
5
5
|
const shared_1 = require("../../../cli/commands/shared");
|
|
6
|
+
const error_1 = require("../../../utils/error");
|
|
7
|
+
/**
|
|
8
|
+
* commander option 校验器:把 --limit <n> 解析成正整数(≥1)。
|
|
9
|
+
* 默认值(如 "50")会先经过这里被规范化成 number。
|
|
10
|
+
* 非整数 / 负数 / 0 抛 AppError("ARGS_INVALID"),由 main.ts 的全局 catch
|
|
11
|
+
* 走 emitError,同时 process.exitCode 由 commander 自然为 1。
|
|
12
|
+
*/
|
|
13
|
+
function parsePositiveInt(raw) {
|
|
14
|
+
const n = Number(raw);
|
|
15
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
16
|
+
throw new error_1.AppError("ARGS_INVALID", `--limit must be a positive integer (got '${raw}')`);
|
|
17
|
+
}
|
|
18
|
+
return n;
|
|
19
|
+
}
|
|
6
20
|
function registerFileCommands(program) {
|
|
7
21
|
const fileCmd = program
|
|
8
22
|
.command("file")
|
|
@@ -21,7 +35,7 @@ function registerFileCommands(program) {
|
|
|
21
35
|
.option("--size-gt <size>", "大小大于,支持 B/KB/MB/GB")
|
|
22
36
|
.option("--size-lt <size>", "大小小于")
|
|
23
37
|
.option("--uploaded-since <time>", "上传时间晚于(ISO 8601)")
|
|
24
|
-
.option("--limit <n>", "
|
|
38
|
+
.option("--limit <n>", "返回条数上限(正整数,默认 50)", parsePositiveInt, 50)
|
|
25
39
|
.option("--cursor <token>", "分页游标")
|
|
26
40
|
.option("--all", "自动翻页聚合全部结果")
|
|
27
41
|
.action(async (query, opts) => {
|
|
@@ -55,14 +55,14 @@ async function handleDbSchemaList(opts) {
|
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
const tty = (0, render_1.isStdoutTty)();
|
|
58
|
-
// PRD 对齐:TTY 表头用 `size`(友好格式),non-TTY 用 `size_bytes
|
|
58
|
+
// PRD 对齐:TTY 表头用 `size`(友好格式),non-TTY 用 `size_bytes`(原始整数)。
|
|
59
|
+
// updated_at 暂时不展示——PG pg_catalog 不存真实表时间,详见 renderDetail 注释。
|
|
59
60
|
const headers = [
|
|
60
61
|
"name",
|
|
61
62
|
"description",
|
|
62
63
|
"estimated_row_count",
|
|
63
64
|
tty ? "size" : "size_bytes",
|
|
64
65
|
"columns",
|
|
65
|
-
"updated_at",
|
|
66
66
|
];
|
|
67
67
|
const rows = tables.map((t) => [
|
|
68
68
|
t.name,
|
|
@@ -70,7 +70,6 @@ async function handleDbSchemaList(opts) {
|
|
|
70
70
|
t.estimated_row_count === null ? "—" : String(t.estimated_row_count),
|
|
71
71
|
t.size_bytes === null ? "—" : (tty ? (0, render_1.formatSize)(t.size_bytes) : String(t.size_bytes)),
|
|
72
72
|
String(t.columns),
|
|
73
|
-
(0, render_1.formatTime)(t.updated_at, tty),
|
|
74
73
|
]);
|
|
75
74
|
(0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows));
|
|
76
75
|
}
|
|
@@ -123,7 +122,10 @@ async function handleDbSchemaGet(table, opts) {
|
|
|
123
122
|
function renderDetail(d, tty) {
|
|
124
123
|
const systemFields = d.columns.filter((c) => c.name.startsWith("_"));
|
|
125
124
|
const userFields = d.columns.filter((c) => !c.name.startsWith("_"));
|
|
126
|
-
//
|
|
125
|
+
// header 布局:Name / Description / Columns(含"+ N system") / Estimated Rows / Size。
|
|
126
|
+
// 不展示 Created / Updated:PG pg_catalog 不存表创建时间,dataloom 用 OID
|
|
127
|
+
// 构造的伪时间戳(baseTime=2020-01-01 + OID 秒偏移),仅保排序意义、绝对值
|
|
128
|
+
// 误导性强,先去掉。后续如果接 ddl_change_log 取真实时间再加回。
|
|
127
129
|
const header = [
|
|
128
130
|
["Name", d.name],
|
|
129
131
|
["Description", d.description ?? "—"],
|
|
@@ -135,8 +137,6 @@ function renderDetail(d, tty) {
|
|
|
135
137
|
],
|
|
136
138
|
["Estimated Rows", d.estimated_row_count === null ? "—" : String(d.estimated_row_count)],
|
|
137
139
|
["Size", d.size_bytes === null ? "—" : (0, render_1.formatSize)(d.size_bytes)],
|
|
138
|
-
["Created", (0, render_1.formatTime)(d.created_at, tty)],
|
|
139
|
-
["Updated", (0, render_1.formatTime)(d.updated_at, tty)],
|
|
140
140
|
];
|
|
141
141
|
const colHeaders = ["column", "type", "nullable", "default", "comment"];
|
|
142
142
|
const colRows = userFields.map((c) => [
|
|
@@ -37,6 +37,7 @@ exports.handleDbSql = handleDbSql;
|
|
|
37
37
|
const api = __importStar(require("../../../api/index"));
|
|
38
38
|
const error_1 = require("../../../utils/error");
|
|
39
39
|
const output_1 = require("../../../utils/output");
|
|
40
|
+
const config_1 = require("../../../utils/config");
|
|
40
41
|
const shared_1 = require("../../../cli/commands/shared");
|
|
41
42
|
const render_1 = require("../../../utils/render");
|
|
42
43
|
const index_1 = require("../../../api/db/index");
|
|
@@ -47,7 +48,8 @@ const index_1 = require("../../../api/db/index");
|
|
|
47
48
|
* - --env 透传给后端 admin-inner(dbBranch 参数),不传时由后端按 workspace
|
|
48
49
|
* 多环境状态兜底(多环境 → dev / 单环境 → main);后端检测到环境不存在
|
|
49
50
|
* 会返 k_dl_1600000 + "Invalid DB Branch:...",CLI 侧映射为 MULTI_ENV_NOT_INITIALIZED
|
|
50
|
-
* -
|
|
51
|
+
* - 多语句行为对齐 PRD:每条 statement 一个独立结果元素,pretty 逐条 +
|
|
52
|
+
* 末尾汇总,--json 输出 data 数组。
|
|
51
53
|
*/
|
|
52
54
|
async function handleDbSql(query, opts) {
|
|
53
55
|
const appId = (0, shared_1.resolveAppId)(opts);
|
|
@@ -65,22 +67,16 @@ async function handleDbSql(query, opts) {
|
|
|
65
67
|
(0, output_1.emit)("✓ No results");
|
|
66
68
|
return;
|
|
67
69
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
const tty = (0, render_1.isStdoutTty)();
|
|
76
|
-
(0, output_1.emit)(tty
|
|
77
|
-
? `✓ ${String(results.length)} statements executed`
|
|
78
|
-
: `OK ${String(results.length)} statements executed`);
|
|
70
|
+
if (results.length === 1) {
|
|
71
|
+
renderSingle(results[0]);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// 多语句:每条 statement 独立结果
|
|
75
|
+
if ((0, output_1.isJsonMode)()) {
|
|
76
|
+
(0, output_1.emit)({ data: results.map((r) => toMultiElement((0, index_1.parseSqlResult)(r))) });
|
|
79
77
|
return;
|
|
80
78
|
}
|
|
81
|
-
|
|
82
|
-
const last = results[results.length - 1];
|
|
83
|
-
renderSingle(last);
|
|
79
|
+
renderMultiPretty(results);
|
|
84
80
|
}
|
|
85
81
|
/** 读取 stdin 并返回完整 SQL 文本(stdin 不是 TTY 即认为被 pipe)。 */
|
|
86
82
|
async function readSql(inline) {
|
|
@@ -130,23 +126,102 @@ function renderSingle(raw) {
|
|
|
130
126
|
}
|
|
131
127
|
function toJson(parsed) {
|
|
132
128
|
if (parsed.kind === "select") {
|
|
133
|
-
|
|
129
|
+
// PRD 单 SELECT:data 直接是行数组(按 --json 字段投影裁剪)
|
|
130
|
+
return { data: projectRows(parsed.rows) };
|
|
134
131
|
}
|
|
135
132
|
if (parsed.kind === "dml") {
|
|
133
|
+
// PRD 单 DML:data = {command, rows_affected}
|
|
136
134
|
return {
|
|
137
135
|
data: {
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
command: parsed.sqlType,
|
|
137
|
+
rows_affected: parsed.affectedRows,
|
|
140
138
|
},
|
|
141
139
|
};
|
|
142
140
|
}
|
|
143
|
-
|
|
141
|
+
// PRD 单 DDL:data = {command, target?}。command 直接用后端给的细粒度
|
|
142
|
+
// (CREATE_TABLE / DROP_TABLE / ...),target 待后端给对象名后再加。
|
|
143
|
+
return { data: { command: parsed.sqlType } };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 多语句 --json 元素:与单 DDL/DML 形状一致,但 SELECT 包成
|
|
147
|
+
* {command:"SELECT", rows:[...]}(PRD 约定,避免数组里嵌套数组造成歧义)。
|
|
148
|
+
*/
|
|
149
|
+
function toMultiElement(parsed) {
|
|
150
|
+
if (parsed.kind === "select") {
|
|
151
|
+
return { command: "SELECT", rows: projectRows(parsed.rows) };
|
|
152
|
+
}
|
|
153
|
+
if (parsed.kind === "dml") {
|
|
154
|
+
return { command: parsed.sqlType, rows_affected: parsed.affectedRows };
|
|
155
|
+
}
|
|
156
|
+
// DDL:用后端给的细粒度 command
|
|
157
|
+
return { command: parsed.sqlType };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* PRD:`--json id,name` 字段投影。--json 不带值(boolean true)等价于不裁剪。
|
|
161
|
+
* 字段不存在时按 undefined 处理(JSON.stringify 会忽略 undefined value 的 key),
|
|
162
|
+
* 这样 Agent 拿到的 row 永远只含请求过的列。
|
|
163
|
+
*/
|
|
164
|
+
function projectRows(rows) {
|
|
165
|
+
const fields = parseJsonFields();
|
|
166
|
+
if (!fields)
|
|
167
|
+
return rows;
|
|
168
|
+
return rows.map((r) => {
|
|
169
|
+
const out = {};
|
|
170
|
+
for (const f of fields) {
|
|
171
|
+
out[f] = r[f];
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/** 读取 --json [fields]:返回字段列表;boolean true 或 undefined 返回 null(不裁剪)。 */
|
|
177
|
+
function parseJsonFields() {
|
|
178
|
+
const v = (0, config_1.getConfig)().json;
|
|
179
|
+
if (typeof v !== "string" || v === "")
|
|
180
|
+
return null;
|
|
181
|
+
return v.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
182
|
+
}
|
|
183
|
+
/** 多语句 pretty:逐条 `Statement N: ...` + 末尾 `✓ N statements executed`。 */
|
|
184
|
+
function renderMultiPretty(results) {
|
|
185
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
186
|
+
const lines = [];
|
|
187
|
+
for (let i = 0; i < results.length; i++) {
|
|
188
|
+
const parsed = (0, index_1.parseSqlResult)(results[i]);
|
|
189
|
+
const idx = i + 1;
|
|
190
|
+
if (parsed.kind === "select") {
|
|
191
|
+
const n = parsed.rows.length;
|
|
192
|
+
lines.push(`Statement ${String(idx)}: SELECT (${String(n)} row${n === 1 ? "" : "s"})`);
|
|
193
|
+
if (n > 0) {
|
|
194
|
+
const cols = collectColumns(parsed.rows);
|
|
195
|
+
const tbl = parsed.rows.map((r) => cols.map((c) => formatCell(r[c], tty)));
|
|
196
|
+
lines.push(tty ? (0, render_1.renderAlignedTable)(cols, tbl) : (0, render_1.renderTsv)(cols, tbl));
|
|
197
|
+
}
|
|
198
|
+
// 块间空行(最后一条不留)
|
|
199
|
+
if (i < results.length - 1)
|
|
200
|
+
lines.push("");
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (parsed.kind === "dml") {
|
|
204
|
+
const verb = dmlVerb(parsed.sqlType);
|
|
205
|
+
const n = parsed.affectedRows;
|
|
206
|
+
lines.push(`Statement ${String(idx)}: ${tty ? "✓ " : ""}${String(n)} row${n === 1 ? "" : "s"} ${verb}`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
// DDL
|
|
210
|
+
lines.push(`Statement ${String(idx)}: ${tty ? "✓ " : ""}DDL executed`);
|
|
211
|
+
}
|
|
212
|
+
// 汇总行:所有 statement 都跑完了
|
|
213
|
+
lines.push(tty
|
|
214
|
+
? `✓ ${String(results.length)} statements executed`
|
|
215
|
+
: `OK ${String(results.length)} statements executed`);
|
|
216
|
+
(0, output_1.emit)(lines.join("\n"));
|
|
144
217
|
}
|
|
145
218
|
function dmlVerb(type) {
|
|
146
219
|
switch (type) {
|
|
147
220
|
case "INSERT": return "inserted";
|
|
148
221
|
case "UPDATE": return "updated";
|
|
149
222
|
case "DELETE": return "deleted";
|
|
223
|
+
case "MERGE": return "merged";
|
|
224
|
+
case "DML": return "affected"; // 未识别子类的兜底
|
|
150
225
|
}
|
|
151
226
|
}
|
|
152
227
|
/** 从第一行收集列顺序;缺失列保留空白(兼容稀疏行)。 */
|
|
@@ -66,7 +66,8 @@ function resolveQueryRouting(opts) {
|
|
|
66
66
|
}
|
|
67
67
|
async function handleFileLs(opts) {
|
|
68
68
|
const appId = (0, shared_1.resolveAppId)(opts);
|
|
69
|
-
|
|
69
|
+
// commander 已经把 --limit 解析为 number;保留 ?? undefined 兼容老调用
|
|
70
|
+
const limit = opts.limit;
|
|
70
71
|
const sizeGt = opts.sizeGt ? (0, render_1.parseSize)(opts.sizeGt) : undefined;
|
|
71
72
|
const sizeLt = opts.sizeLt ? (0, render_1.parseSize)(opts.sizeLt) : undefined;
|
|
72
73
|
const { path, name } = resolveQueryRouting(opts);
|
|
@@ -44,7 +44,7 @@ const shared_1 = require("../../../cli/commands/shared");
|
|
|
44
44
|
const index_1 = require("../../../api/file/index");
|
|
45
45
|
const render_1 = require("../../../utils/render");
|
|
46
46
|
const node_readline_1 = __importDefault(require("node:readline"));
|
|
47
|
-
const MAX_BATCH =
|
|
47
|
+
const MAX_BATCH = 100;
|
|
48
48
|
/**
|
|
49
49
|
* 解析位置参数(自动识别 path / file_name)与 `--name`(强制 file_name)两类输入。
|
|
50
50
|
*
|
|
@@ -103,13 +103,20 @@ async function resolveDeleteInputs(appId, paths, names) {
|
|
|
103
103
|
}
|
|
104
104
|
return { resolved, errors };
|
|
105
105
|
}
|
|
106
|
-
|
|
106
|
+
/**
|
|
107
|
+
* 删除前 TTY 二次确认。
|
|
108
|
+
* PRD 单文件场景下提示带具体路径:`? Delete '/path'? (y/N)`,
|
|
109
|
+
* 多文件场景汇总条数:`? Delete N files? (y/N)`。
|
|
110
|
+
* `firstInput` 是用户传入的第一个值(可能是 path 或 file_name),
|
|
111
|
+
* 单文件时直接展示给用户,方便核对目标。
|
|
112
|
+
*/
|
|
113
|
+
async function confirm(count, firstInput) {
|
|
107
114
|
const rl = node_readline_1.default.createInterface({
|
|
108
115
|
input: process.stdin,
|
|
109
116
|
output: process.stderr,
|
|
110
117
|
});
|
|
111
118
|
return new Promise((resolve) => {
|
|
112
|
-
const prompt = count === 1 ? `? Delete
|
|
119
|
+
const prompt = count === 1 ? `? Delete '${firstInput}'? (y/N) ` : `? Delete ${String(count)} files? (y/N) `;
|
|
113
120
|
rl.question(prompt, (answer) => {
|
|
114
121
|
rl.close();
|
|
115
122
|
resolve(answer.trim().toLowerCase() === "y");
|
|
@@ -123,13 +130,16 @@ async function handleFileRm(paths, opts) {
|
|
|
123
130
|
throw new error_1.AppError("ARGS_INVALID", "No file specified (give a /path or --name <name>)");
|
|
124
131
|
}
|
|
125
132
|
if (totalCount > MAX_BATCH) {
|
|
126
|
-
throw new error_1.AppError("FILE_BATCH_TOO_MANY", `Batch size ${String(totalCount)} exceeds the
|
|
133
|
+
throw new error_1.AppError("FILE_BATCH_TOO_MANY", `Batch size ${String(totalCount)} exceeds the 100 limit`);
|
|
127
134
|
}
|
|
128
135
|
const appId = (0, shared_1.resolveAppId)(opts);
|
|
129
136
|
// destructive guardrail
|
|
130
137
|
const tty = (0, render_1.isStdoutTty)();
|
|
131
138
|
if (tty && !opts.yes) {
|
|
132
|
-
|
|
139
|
+
// 单文件提示带具体目标方便用户核对;多文件传第一个 input 占位(实际只显示 N files)
|
|
140
|
+
// 上面已校验 totalCount > 0,paths/names 至少有一个非空
|
|
141
|
+
const firstInput = paths.length > 0 ? paths[0] : names[0];
|
|
142
|
+
const ok = await confirm(totalCount, firstInput);
|
|
133
143
|
if (!ok) {
|
|
134
144
|
throw new error_1.AppError("DESTRUCTIVE_CANCELLED", "Cancelled by user");
|
|
135
145
|
}
|
|
@@ -173,6 +183,7 @@ async function handleFileRm(paths, opts) {
|
|
|
173
183
|
input,
|
|
174
184
|
code: "INTERNAL_ERROR",
|
|
175
185
|
message: "Delete request returned success but file still exists (server-side issue)",
|
|
186
|
+
hint: undefined,
|
|
176
187
|
};
|
|
177
188
|
}
|
|
178
189
|
catch (err) {
|
|
@@ -181,12 +192,14 @@ async function handleFileRm(paths, opts) {
|
|
|
181
192
|
input,
|
|
182
193
|
code: "FILE_NOT_FOUND",
|
|
183
194
|
message: `File '${input}' does not exist at delete time`,
|
|
195
|
+
hint: "Run `miaoda file ls` to see available files.",
|
|
184
196
|
};
|
|
185
197
|
}
|
|
186
198
|
return {
|
|
187
199
|
input,
|
|
188
200
|
code: "INTERNAL_ERROR",
|
|
189
201
|
message: err instanceof Error ? err.message : "verification failed",
|
|
202
|
+
hint: undefined,
|
|
190
203
|
};
|
|
191
204
|
}
|
|
192
205
|
}));
|
|
@@ -194,7 +207,9 @@ async function handleFileRm(paths, opts) {
|
|
|
194
207
|
results.push({
|
|
195
208
|
status: "error",
|
|
196
209
|
input: c.input,
|
|
197
|
-
error:
|
|
210
|
+
error: c.hint
|
|
211
|
+
? { code: c.code, message: c.message, hint: c.hint }
|
|
212
|
+
: { code: c.code, message: c.message },
|
|
198
213
|
});
|
|
199
214
|
}
|
|
200
215
|
}
|
|
@@ -233,7 +248,12 @@ async function handleFileRm(paths, opts) {
|
|
|
233
248
|
else
|
|
234
249
|
lines.push(`FAIL\t${r.input}\t${r.error.message}`);
|
|
235
250
|
}
|
|
236
|
-
|
|
251
|
+
if (failCount === 0) {
|
|
252
|
+
lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files`);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files (${String(failCount)} failed)`);
|
|
256
|
+
}
|
|
237
257
|
(0, output_1.emit)(lines.join("\n"));
|
|
238
258
|
}
|
|
239
259
|
// 退出码:任一失败 → 1;全成功 → 0
|
package/dist/utils/error.js
CHANGED
|
@@ -5,12 +5,14 @@ class AppError extends Error {
|
|
|
5
5
|
code;
|
|
6
6
|
retryable;
|
|
7
7
|
next_actions;
|
|
8
|
+
statement_index;
|
|
8
9
|
constructor(code, message, opts) {
|
|
9
10
|
super(message);
|
|
10
11
|
this.name = "AppError";
|
|
11
12
|
this.code = code;
|
|
12
13
|
this.retryable = opts?.retryable ?? false;
|
|
13
14
|
this.next_actions = opts?.next_actions ?? [];
|
|
15
|
+
this.statement_index = opts?.statement_index;
|
|
14
16
|
}
|
|
15
17
|
toJSON() {
|
|
16
18
|
return {
|
|
@@ -18,6 +20,7 @@ class AppError extends Error {
|
|
|
18
20
|
message: this.message,
|
|
19
21
|
retryable: this.retryable,
|
|
20
22
|
next_actions: this.next_actions,
|
|
23
|
+
statement_index: this.statement_index,
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
26
|
}
|
package/dist/utils/output.js
CHANGED
|
@@ -32,19 +32,22 @@ function emit(data) {
|
|
|
32
32
|
/**
|
|
33
33
|
* 输出错误(写入 stderr)
|
|
34
34
|
* - pretty 模式:Error: message\n hint: ...
|
|
35
|
-
* - json 模式:{
|
|
35
|
+
* - json 模式:PRD 约定 envelope 为 `{error: {code, message, hint?}}`
|
|
36
36
|
*/
|
|
37
37
|
function emitError(err) {
|
|
38
38
|
const info = toErrorInfo(err);
|
|
39
39
|
if (isJsonMode()) {
|
|
40
|
-
const
|
|
41
|
-
|
|
40
|
+
const errObj = {
|
|
41
|
+
code: info.code,
|
|
42
42
|
message: info.message,
|
|
43
43
|
};
|
|
44
44
|
if (info.next_actions && info.next_actions.length > 0) {
|
|
45
|
-
|
|
45
|
+
errObj.hint = info.next_actions.join(" ");
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
if (typeof info.statement_index === "number") {
|
|
48
|
+
errObj.statement_index = info.statement_index;
|
|
49
|
+
}
|
|
50
|
+
process.stderr.write(JSON.stringify({ error: errObj }) + "\n");
|
|
48
51
|
}
|
|
49
52
|
else {
|
|
50
53
|
process.stderr.write(`Error: ${info.message}\n`);
|
package/dist/utils/render.js
CHANGED
|
@@ -71,9 +71,39 @@ function formatTime(iso, isTty) {
|
|
|
71
71
|
* 算进列宽(比如 dim "NULL" 实际占 4 字符但 .length=11),导致表格对齐错位。
|
|
72
72
|
*/
|
|
73
73
|
const ANSI_SGR_RE = /\[[0-9;]*m/g;
|
|
74
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* 单字符的终端列宽:CJK 全角字符(汉字/假名/全角符号等)占 2 列,其它占 1 列。
|
|
76
|
+
* 对齐 Unicode East Asian Width 的 W/F 类,等价于 wcwidth 简化版。
|
|
77
|
+
* 不实现合字 / 零宽字符(ZWJ / 变体选择符)等极端情况,CLI 表格场景够用。
|
|
78
|
+
*/
|
|
79
|
+
function charWidth(cp) {
|
|
80
|
+
if ((cp >= 0x1100 && cp <= 0x115F) || // Hangul Jamo
|
|
81
|
+
cp === 0x2329 || cp === 0x232A ||
|
|
82
|
+
(cp >= 0x2E80 && cp <= 0x303E) || // CJK Radicals / Punctuation
|
|
83
|
+
(cp >= 0x3041 && cp <= 0x33FF) || // Hiragana / Katakana / CJK Symbols
|
|
84
|
+
(cp >= 0x3400 && cp <= 0x4DBF) || // CJK Ext A
|
|
85
|
+
(cp >= 0x4E00 && cp <= 0x9FFF) || // CJK Unified
|
|
86
|
+
(cp >= 0xA000 && cp <= 0xA4CF) || // Yi
|
|
87
|
+
(cp >= 0xAC00 && cp <= 0xD7A3) || // Hangul Syllables
|
|
88
|
+
(cp >= 0xF900 && cp <= 0xFAFF) || // CJK Compat Ideographs
|
|
89
|
+
(cp >= 0xFE30 && cp <= 0xFE4F) || // CJK Compat Forms
|
|
90
|
+
(cp >= 0xFF00 && cp <= 0xFF60) || // Fullwidth Forms
|
|
91
|
+
(cp >= 0xFFE0 && cp <= 0xFFE6) ||
|
|
92
|
+
(cp >= 0x20000 && cp <= 0x2FFFD) || // CJK Ext B-F
|
|
93
|
+
(cp >= 0x30000 && cp <= 0x3FFFD) // CJK Ext G-H
|
|
94
|
+
) {
|
|
95
|
+
return 2;
|
|
96
|
+
}
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
/** cell 可见字符宽度:剥离 ANSI 转义后按终端列宽逐字符累加(CJK 算 2 列)。 */
|
|
75
100
|
function visibleWidth(s) {
|
|
76
|
-
|
|
101
|
+
const stripped = s.replace(ANSI_SGR_RE, "");
|
|
102
|
+
let w = 0;
|
|
103
|
+
for (const c of stripped) {
|
|
104
|
+
w += charWidth(c.codePointAt(0) ?? 0);
|
|
105
|
+
}
|
|
106
|
+
return w;
|
|
77
107
|
}
|
|
78
108
|
function padVisibleEnd(s, targetWidth) {
|
|
79
109
|
const w = visibleWidth(s);
|
package/package.json
CHANGED
|
@@ -1,31 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
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
|
-
}
|