@lark-apaas/miaoda-cli 0.1.0-alpha.8825a76 → 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 +0 -10
- 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 -17
- package/dist/cli/commands/db/index.js +1 -1
- package/dist/cli/commands/file/index.js +15 -1
- package/dist/cli/commands/shared.js +8 -3
- 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 -21
- package/dist/api/file/_debug_trace.js +0 -21
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,9 +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
|
-
(0, _debug_trace_1.traceRequest)("POST", url);
|
|
30
27
|
const response = await client.post(url, { sql: opts.sql });
|
|
31
|
-
(0, _debug_trace_1.traceResponse)("POST", url, response.status, response.headers);
|
|
32
28
|
if (!response.ok) {
|
|
33
29
|
// 4xx / 5xx:尝试解析 body 取 status_code 映射业务错误
|
|
34
30
|
let body = null;
|
|
@@ -61,9 +57,7 @@ async function getSchema(opts) {
|
|
|
61
57
|
includeStats: opts.includeStats ? "true" : undefined,
|
|
62
58
|
dbBranch: opts.dbBranch,
|
|
63
59
|
});
|
|
64
|
-
(0, _debug_trace_1.traceRequest)("GET", url);
|
|
65
60
|
const response = await client.get(url);
|
|
66
|
-
(0, _debug_trace_1.traceResponse)("GET", url, response.status, response.headers);
|
|
67
61
|
if (!response.ok) {
|
|
68
62
|
let body = null;
|
|
69
63
|
try {
|
|
@@ -95,14 +89,12 @@ async function importData(opts) {
|
|
|
95
89
|
});
|
|
96
90
|
const contentType = opts.format === "csv" ? "text/csv" : "application/json";
|
|
97
91
|
const ab = opts.body.buffer.slice(opts.body.byteOffset, opts.body.byteOffset + opts.body.byteLength);
|
|
98
|
-
(0, _debug_trace_1.traceRequest)("POST", url);
|
|
99
92
|
const response = await client.request({
|
|
100
93
|
method: "POST",
|
|
101
94
|
url,
|
|
102
95
|
headers: { "Content-Type": contentType },
|
|
103
96
|
body: ab,
|
|
104
97
|
});
|
|
105
|
-
(0, _debug_trace_1.traceResponse)("POST", url, response.status, response.headers);
|
|
106
98
|
if (!response.ok) {
|
|
107
99
|
let body = null;
|
|
108
100
|
try {
|
|
@@ -139,9 +131,7 @@ async function exportData(opts) {
|
|
|
139
131
|
dbBranch: opts.dbBranch,
|
|
140
132
|
});
|
|
141
133
|
const reqBody = { limit: opts.limit ?? 5000 };
|
|
142
|
-
(0, _debug_trace_1.traceRequest)("POST", url);
|
|
143
134
|
const response = await client.post(url, reqBody);
|
|
144
|
-
(0, _debug_trace_1.traceResponse)("POST", url, response.status, response.headers);
|
|
145
135
|
if (!response.ok) {
|
|
146
136
|
// 错误路径:body 是 JSON envelope
|
|
147
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,15 +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);
|
|
137
134
|
const response = await client.get(url);
|
|
138
|
-
(0, _debug_trace_1.traceResponse)("GET", url, response.status, response.headers);
|
|
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);
|
|
144
|
-
}
|
|
145
138
|
await mapHttpError(err, opts);
|
|
146
139
|
throw err; // 不可达,mapHttpError 必定 throw
|
|
147
140
|
}
|
|
@@ -149,15 +142,10 @@ async function doGet(url, opts = {}, client = (0, http_1.getHttpClient)()) {
|
|
|
149
142
|
/** 包一层 client.post + HttpError 统一映射。默认 admin innerapi,可显式切 runtime。 */
|
|
150
143
|
async function doPost(url, body, opts = {}, client = (0, http_1.getHttpClient)()) {
|
|
151
144
|
try {
|
|
152
|
-
(0, _debug_trace_1.traceRequest)("POST", url);
|
|
153
145
|
const response = await client.post(url, body);
|
|
154
|
-
(0, _debug_trace_1.traceResponse)("POST", url, response.status, response.headers);
|
|
155
146
|
return (await response.json());
|
|
156
147
|
}
|
|
157
148
|
catch (err) {
|
|
158
|
-
if (err instanceof http_client_1.HttpError && err.response) {
|
|
159
|
-
(0, _debug_trace_1.traceResponse)("POST", url, err.response.status, err.response.headers);
|
|
160
|
-
}
|
|
161
149
|
await mapHttpError(err, opts);
|
|
162
150
|
throw err;
|
|
163
151
|
}
|
|
@@ -165,15 +153,10 @@ async function doPost(url, body, opts = {}, client = (0, http_1.getHttpClient)()
|
|
|
165
153
|
/** 包一层 client.request + HttpError 统一映射(DELETE 带 body 的场景)。 */
|
|
166
154
|
async function doRequest(cfg, opts = {}, client = (0, http_1.getHttpClient)()) {
|
|
167
155
|
try {
|
|
168
|
-
(0, _debug_trace_1.traceRequest)(cfg.method, cfg.url);
|
|
169
156
|
const response = await client.request(cfg);
|
|
170
|
-
(0, _debug_trace_1.traceResponse)(cfg.method, cfg.url, response.status, response.headers);
|
|
171
157
|
return (await response.json());
|
|
172
158
|
}
|
|
173
159
|
catch (err) {
|
|
174
|
-
if (err instanceof http_client_1.HttpError && err.response) {
|
|
175
|
-
(0, _debug_trace_1.traceResponse)(cfg.method, cfg.url, err.response.status, err.response.headers);
|
|
176
|
-
}
|
|
177
160
|
await mapHttpError(err, opts);
|
|
178
161
|
throw err;
|
|
179
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) => {
|
|
@@ -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
|
-
/**
|
|
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: [
|
|
@@ -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,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.traceRequest = traceRequest;
|
|
4
|
-
exports.traceResponse = traceResponse;
|
|
5
|
-
/**
|
|
6
|
-
* ⚠️ TODO(REMOVE-BEFORE-RELEASE): 调试用 HTTP trace。
|
|
7
|
-
*
|
|
8
|
-
* 仅在 `--verbose` 下把 db 域 admin-inner 请求的 URL 与后端 logid 打到 stderr,
|
|
9
|
-
* 方便联调时把 logid 贴给后端查链路。正式版本前请整文件删除,并把
|
|
10
|
-
* `src/api/db/{api,client}.ts` 里的 `traceRequest()` / `traceResponse()` 调用一并移除。
|
|
11
|
-
*/
|
|
12
|
-
const logger_1 = require("../../utils/logger");
|
|
13
|
-
/** 请求前打 method + 完整 URL(不含敏感 header)。 */
|
|
14
|
-
function traceRequest(method, url) {
|
|
15
|
-
(0, logger_1.debug)(`http → ${method} ${url}`);
|
|
16
|
-
}
|
|
17
|
-
/** 响应后打 status + x-tt-logid(后端追踪 ID)。 */
|
|
18
|
-
function traceResponse(method, url, status, headers) {
|
|
19
|
-
const logId = headers?.get("x-tt-logid") ?? headers?.get("X-Tt-Logid") ?? "(none)";
|
|
20
|
-
(0, logger_1.debug)(`http ← ${method} ${url} status=${String(status)} x-tt-logid=${logId}`);
|
|
21
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.traceRequest = traceRequest;
|
|
4
|
-
exports.traceResponse = traceResponse;
|
|
5
|
-
/**
|
|
6
|
-
* ⚠️ TODO(REMOVE-BEFORE-RELEASE): 调试用 HTTP trace。
|
|
7
|
-
*
|
|
8
|
-
* 仅在 `--verbose` 下把 file 域请求的 URL 与后端 logid 打到 stderr,
|
|
9
|
-
* 方便联调时把 logid 贴给后端查链路。正式版本前请整文件删除,并把
|
|
10
|
-
* `src/api/file/client.ts` 里的 `traceRequest()` / `traceResponse()` 调用一并移除。
|
|
11
|
-
*/
|
|
12
|
-
const logger_1 = require("../../utils/logger");
|
|
13
|
-
/** 请求前打 method + 完整 URL(不含敏感 header)。 */
|
|
14
|
-
function traceRequest(method, url) {
|
|
15
|
-
(0, logger_1.debug)(`http → ${method} ${url}`);
|
|
16
|
-
}
|
|
17
|
-
/** 响应后打 status + x-tt-logid(后端追踪 ID)。 */
|
|
18
|
-
function traceResponse(method, url, status, headers) {
|
|
19
|
-
const logId = headers?.get("x-tt-logid") ?? headers?.get("X-Tt-Logid") ?? "(none)";
|
|
20
|
-
(0, logger_1.debug)(`http ← ${method} ${url} status=${String(status)} x-tt-logid=${logId}`);
|
|
21
|
-
}
|