@lark-apaas/miaoda-cli 0.1.1-alpha.b2bef50 → 0.1.1-alpha.c294fef

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.
Files changed (63) hide show
  1. package/dist/api/app/api.js +47 -0
  2. package/dist/api/app/index.js +16 -0
  3. package/dist/api/app/schemas.js +79 -0
  4. package/dist/api/app/types.js +58 -0
  5. package/dist/api/db/api.js +6 -83
  6. package/dist/api/db/client.js +29 -40
  7. package/dist/api/db/parsers.js +20 -33
  8. package/dist/api/deploy/api.js +66 -0
  9. package/dist/api/deploy/index.js +20 -0
  10. package/dist/api/deploy/schemas.js +128 -0
  11. package/dist/api/deploy/types.js +30 -0
  12. package/dist/api/file/api.js +23 -67
  13. package/dist/api/file/client.js +5 -1
  14. package/dist/api/file/parsers.js +5 -1
  15. package/dist/api/index.js +7 -1
  16. package/dist/api/observability/api.js +52 -0
  17. package/dist/api/observability/index.js +17 -0
  18. package/dist/api/observability/schemas.js +47 -0
  19. package/dist/api/observability/types.js +27 -0
  20. package/dist/api/plugin/api.js +3 -8
  21. package/dist/cli/commands/app/index.js +62 -0
  22. package/dist/cli/commands/db/index.js +0 -1
  23. package/dist/cli/commands/deploy/index.js +132 -0
  24. package/dist/cli/commands/index.js +6 -0
  25. package/dist/cli/commands/observability/index.js +223 -0
  26. package/dist/cli/commands/plugin/index.js +6 -18
  27. package/dist/cli/commands/shared.js +40 -4
  28. package/dist/cli/handlers/app/get.js +48 -0
  29. package/dist/cli/handlers/app/index.js +7 -0
  30. package/dist/cli/handlers/app/update.js +59 -0
  31. package/dist/cli/handlers/db/data.js +2 -22
  32. package/dist/cli/handlers/db/schema.js +8 -22
  33. package/dist/cli/handlers/db/sql.js +16 -300
  34. package/dist/cli/handlers/deploy/deploy.js +83 -0
  35. package/dist/cli/handlers/deploy/error-log.js +60 -0
  36. package/dist/cli/handlers/deploy/get.js +72 -0
  37. package/dist/cli/handlers/deploy/helpers.js +36 -0
  38. package/dist/cli/handlers/deploy/history.js +70 -0
  39. package/dist/cli/handlers/deploy/index.js +14 -0
  40. package/dist/cli/handlers/deploy/polling.js +143 -0
  41. package/dist/cli/handlers/file/cp.js +17 -39
  42. package/dist/cli/handlers/file/ls.js +3 -1
  43. package/dist/cli/handlers/file/rm.js +3 -4
  44. package/dist/cli/handlers/observability/analytics.js +194 -0
  45. package/dist/cli/handlers/observability/helpers.js +66 -0
  46. package/dist/cli/handlers/observability/index.js +12 -0
  47. package/dist/cli/handlers/observability/log.js +94 -0
  48. package/dist/cli/handlers/observability/metric.js +194 -0
  49. package/dist/cli/handlers/observability/trace.js +102 -0
  50. package/dist/cli/handlers/plugin/plugin-local.js +9 -23
  51. package/dist/cli/handlers/plugin/plugin.js +7 -21
  52. package/dist/cli/help.js +2 -5
  53. package/dist/utils/error.js +0 -11
  54. package/dist/utils/git.js +29 -0
  55. package/dist/utils/http.js +32 -0
  56. package/dist/utils/index.js +13 -1
  57. package/dist/utils/output.js +340 -63
  58. package/dist/utils/render.js +41 -61
  59. package/dist/utils/time.js +129 -0
  60. package/package.json +7 -13
  61. package/dist/api/db/sql-keywords.js +0 -123
  62. package/dist/utils/colors.js +0 -98
  63. package/dist/utils/fuzzy-match.js +0 -91
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mapDevopsError = mapDevopsError;
4
+ exports.getAppInfo = getAppInfo;
5
+ exports.updateAppMeta = updateAppMeta;
6
+ const http_1 = require("../../utils/http");
7
+ const error_1 = require("../../utils/error");
8
+ const DEFAULT_ERR_CODE = "INTERNAL_DEVOPS_ERROR";
9
+ /**
10
+ * 把 BAM lark.apaas.devops 的业务码(数字字符串)映射成 CLI 错误码。
11
+ * 来源:服务端技术方案 wiki L7P8wlNQ5iyShLkxzlOcCo8LnTg「业务错误码映射表」。
12
+ */
13
+ function mapDevopsError(bizCode, message) {
14
+ switch (bizCode) {
15
+ case "400002577":
16
+ case "400002569":
17
+ return new error_1.AppError("APP_NOT_FOUND", message, {
18
+ next_actions: ["Run `miaoda app list` to see available apps"],
19
+ });
20
+ case "400002579":
21
+ return new error_1.AppError("APP_PERMISSION_DENIED", message);
22
+ case "400002575":
23
+ return new error_1.AppError("INVALID_ARG_VALUE", message, {
24
+ next_actions: ["缩短 --name / --description 后重试(后端有长度限制)"],
25
+ });
26
+ default:
27
+ return undefined;
28
+ }
29
+ }
30
+ function envelopeOpts(errPrefix) {
31
+ return {
32
+ errPrefix,
33
+ defaultErrCode: DEFAULT_ERR_CODE,
34
+ mapErr: mapDevopsError,
35
+ };
36
+ }
37
+ /** GET /v1/devops/app/:appID — 获取应用详情 */
38
+ async function getAppInfo(appID) {
39
+ const url = `/v1/devops/app/${encodeURIComponent(appID)}`;
40
+ return (0, http_1.getInnerApi)(url, envelopeOpts("Failed to get app info"));
41
+ }
42
+ /** POST /v1/devops/app/:appID/meta — 更新应用元数据 */
43
+ async function updateAppMeta(req) {
44
+ const url = `/v1/devops/app/${encodeURIComponent(req.appID)}/meta`;
45
+ // 路径已带 appID,body 里也保留以匹配 BAM IDL 的 sensitive:"no" 透传约定
46
+ return (0, http_1.postInnerApi)(url, req, envelopeOpts("Failed to update app"));
47
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ArchType = exports.Source = exports.BizType = exports.AppType = exports.AppMode = exports.AppStatus = exports.appMetaSchema = exports.mapDevopsError = exports.updateAppMeta = exports.getAppInfo = void 0;
4
+ var api_1 = require("./api");
5
+ Object.defineProperty(exports, "getAppInfo", { enumerable: true, get: function () { return api_1.getAppInfo; } });
6
+ Object.defineProperty(exports, "updateAppMeta", { enumerable: true, get: function () { return api_1.updateAppMeta; } });
7
+ Object.defineProperty(exports, "mapDevopsError", { enumerable: true, get: function () { return api_1.mapDevopsError; } });
8
+ var schemas_1 = require("./schemas");
9
+ Object.defineProperty(exports, "appMetaSchema", { enumerable: true, get: function () { return schemas_1.appMetaSchema; } });
10
+ var types_1 = require("./types");
11
+ Object.defineProperty(exports, "AppStatus", { enumerable: true, get: function () { return types_1.AppStatus; } });
12
+ Object.defineProperty(exports, "AppMode", { enumerable: true, get: function () { return types_1.AppMode; } });
13
+ Object.defineProperty(exports, "AppType", { enumerable: true, get: function () { return types_1.AppType; } });
14
+ Object.defineProperty(exports, "BizType", { enumerable: true, get: function () { return types_1.BizType; } });
15
+ Object.defineProperty(exports, "Source", { enumerable: true, get: function () { return types_1.Source; } });
16
+ Object.defineProperty(exports, "ArchType", { enumerable: true, get: function () { return types_1.ArchType; } });
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.appMetaSchema = void 0;
4
+ const output_1 = require("../../utils/output");
5
+ const types_1 = require("./types");
6
+ // Partial<Record<...>>:未来 BAM 加新枚举值时这里没声明也不报错,运行期 lookup 自然得 undefined
7
+ const STATUS_TEXT = {
8
+ [types_1.AppStatus.UNSPECIFIED]: "unknown",
9
+ [types_1.AppStatus.PREPARING]: "preparing",
10
+ [types_1.AppStatus.UNRELEASED]: "unreleased",
11
+ [types_1.AppStatus.RELEASED]: "released",
12
+ [types_1.AppStatus.DELETED]: "deleted",
13
+ [types_1.AppStatus.UPGRADING]: "upgrading",
14
+ };
15
+ const MODE_TEXT = {
16
+ [types_1.AppMode.UNSPECIFIED]: "unknown",
17
+ [types_1.AppMode.STANDARD]: "standard",
18
+ [types_1.AppMode.EXPERT]: "expert",
19
+ };
20
+ const TYPE_TEXT = {
21
+ [types_1.AppType.UNSPECIFIED]: "unknown",
22
+ [types_1.AppType.STANDARD]: "standard",
23
+ [types_1.AppType.PROTOTYPE]: "prototype",
24
+ [types_1.AppType.APPLICATION]: "application",
25
+ [types_1.AppType.DESIGN]: "design",
26
+ [types_1.AppType.OPENCLAW]: "openclaw",
27
+ [types_1.AppType.CLAW_SUB]: "claw_sub",
28
+ };
29
+ const BIZ_TYPE_TEXT = {
30
+ [types_1.BizType.UNSPECIFIED]: "unknown",
31
+ [types_1.BizType.MIAODA]: "miaoda",
32
+ [types_1.BizType.FORCE]: "force",
33
+ };
34
+ const ARCH_TYPE_TEXT = {
35
+ [types_1.ArchType.UNSPECIFIED]: "unknown",
36
+ [types_1.ArchType.FULL_STACK]: "full_stack",
37
+ [types_1.ArchType.NOT_FULL_STACK]: "not_full_stack",
38
+ };
39
+ const SOURCE_TEXT = {
40
+ [types_1.Source.UNSPECIFIED]: "unknown",
41
+ [types_1.Source.CHAT]: "chat",
42
+ [types_1.Source.TEMPLATE]: "template",
43
+ [types_1.Source.IMPORT_ZIP]: "import_zip",
44
+ [types_1.Source.IMPORT_BASE]: "import_base",
45
+ };
46
+ function lookup(table) {
47
+ return (v) => {
48
+ if (typeof v !== "number")
49
+ return undefined;
50
+ return table[v];
51
+ };
52
+ }
53
+ /**
54
+ * AppMeta 的 pretty 渲染契约。
55
+ *
56
+ * 注:BAM 没给 createdAt / updatedAt 的单位注解;先按 ms(fmt.ms)渲染,
57
+ * 待 e2e 真值确认后调整。enum 字段通过 derive 翻译成可读字符串,JSON 模式
58
+ * 仍是 BAM 原值(数字枚举),与 BAM 接口契约保持一致。
59
+ */
60
+ exports.appMetaSchema = {
61
+ columns: [
62
+ { key: "appID", label: "app-id" },
63
+ { key: "name" },
64
+ { key: "description" },
65
+ { key: "status_text", label: "status", derive: (row) => lookup(STATUS_TEXT)(row.status) },
66
+ { key: "appType_text", label: "type", derive: (row) => lookup(TYPE_TEXT)(row.appType) },
67
+ { key: "appMode_text", label: "mode", derive: (row) => lookup(MODE_TEXT)(row.appMode) },
68
+ { key: "bizType_text", label: "biz-type", derive: (row) => lookup(BIZ_TYPE_TEXT)(row.bizType) },
69
+ { key: "archType_text", label: "arch", derive: (row) => lookup(ARCH_TYPE_TEXT)(row.archType) },
70
+ { key: "source_text", label: "source", derive: (row) => lookup(SOURCE_TEXT)(row.source) },
71
+ { key: "ownedBy", label: "owner" },
72
+ { key: "createdBy", label: "creator" },
73
+ { key: "dataBranchID", label: "branch" },
74
+ { key: "parentAppID", label: "parent-app" },
75
+ { key: "createdAt", label: "created-at", format: output_1.fmt.ms() },
76
+ { key: "updatedAt", label: "updated-at", format: output_1.fmt.ms() },
77
+ ],
78
+ strict: true,
79
+ };
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ // 与 BAM lark.apaas.devops v1.0.326 对齐:
3
+ // - CLIGetAppInfo (4070322) → AppInfo / AppMeta
4
+ // - CLIUpdateAppMeta (4070323) → 入参 name? / description?
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ArchType = exports.Source = exports.BizType = exports.AppType = exports.AppMode = exports.AppStatus = void 0;
7
+ /** 应用状态 */
8
+ var AppStatus;
9
+ (function (AppStatus) {
10
+ AppStatus[AppStatus["UNSPECIFIED"] = 0] = "UNSPECIFIED";
11
+ AppStatus[AppStatus["PREPARING"] = 1] = "PREPARING";
12
+ AppStatus[AppStatus["UNRELEASED"] = 2] = "UNRELEASED";
13
+ AppStatus[AppStatus["RELEASED"] = 3] = "RELEASED";
14
+ /** BAM 注释为 Deprecated;保留枚举以兼容历史响应数据 */
15
+ AppStatus[AppStatus["DELETED"] = 4] = "DELETED";
16
+ AppStatus[AppStatus["UPGRADING"] = 5] = "UPGRADING";
17
+ })(AppStatus || (exports.AppStatus = AppStatus = {}));
18
+ /** 应用模式 */
19
+ var AppMode;
20
+ (function (AppMode) {
21
+ AppMode[AppMode["UNSPECIFIED"] = 0] = "UNSPECIFIED";
22
+ AppMode[AppMode["STANDARD"] = 1] = "STANDARD";
23
+ AppMode[AppMode["EXPERT"] = 2] = "EXPERT";
24
+ })(AppMode || (exports.AppMode = AppMode = {}));
25
+ /** 应用类型 */
26
+ var AppType;
27
+ (function (AppType) {
28
+ AppType[AppType["UNSPECIFIED"] = 0] = "UNSPECIFIED";
29
+ AppType[AppType["STANDARD"] = 1] = "STANDARD";
30
+ AppType[AppType["PROTOTYPE"] = 2] = "PROTOTYPE";
31
+ AppType[AppType["APPLICATION"] = 3] = "APPLICATION";
32
+ AppType[AppType["DESIGN"] = 4] = "DESIGN";
33
+ AppType[AppType["OPENCLAW"] = 5] = "OPENCLAW";
34
+ AppType[AppType["CLAW_SUB"] = 6] = "CLAW_SUB";
35
+ })(AppType || (exports.AppType = AppType = {}));
36
+ /** 业务线 */
37
+ var BizType;
38
+ (function (BizType) {
39
+ BizType[BizType["UNSPECIFIED"] = 0] = "UNSPECIFIED";
40
+ BizType[BizType["MIAODA"] = 1] = "MIAODA";
41
+ BizType[BizType["FORCE"] = 2] = "FORCE";
42
+ })(BizType || (exports.BizType = BizType = {}));
43
+ /** 应用来源 */
44
+ var Source;
45
+ (function (Source) {
46
+ Source[Source["UNSPECIFIED"] = 0] = "UNSPECIFIED";
47
+ Source[Source["CHAT"] = 1] = "CHAT";
48
+ Source[Source["TEMPLATE"] = 2] = "TEMPLATE";
49
+ Source[Source["IMPORT_ZIP"] = 3] = "IMPORT_ZIP";
50
+ Source[Source["IMPORT_BASE"] = 4] = "IMPORT_BASE";
51
+ })(Source || (exports.Source = Source = {}));
52
+ /** 架构类型 */
53
+ var ArchType;
54
+ (function (ArchType) {
55
+ ArchType[ArchType["UNSPECIFIED"] = 0] = "UNSPECIFIED";
56
+ ArchType[ArchType["FULL_STACK"] = 1] = "FULL_STACK";
57
+ ArchType[ArchType["NOT_FULL_STACK"] = 2] = "NOT_FULL_STACK";
58
+ })(ArchType || (exports.ArchType = ArchType = {}));
@@ -16,13 +16,7 @@ const client_1 = require("./client");
16
16
  * 配合调用点的 try/catch + traceHttp,让 --verbose 在错误路径上也能拿到
17
17
  * x-tt-logid 与 status,方便定位线上问题。
18
18
  */
19
- async function mapDbHttpError(err, url, ctx,
20
- /**
21
- * 可选 hook:解析到响应 body 后,如果 envelope 命中业务错误并抛出 AppError,
22
- * 调用方可以借此把 body 里的额外字段(如 multi-statement 的 partial results)
23
- * 挂到 AppError 上。
24
- */
25
- onErrorBody) {
19
+ async function mapDbHttpError(err, url, ctx) {
26
20
  if (err instanceof error_1.AppError)
27
21
  throw err;
28
22
  if (err instanceof http_client_1.HttpError) {
@@ -30,17 +24,8 @@ onErrorBody) {
30
24
  const statusText = err.response?.statusText ?? "";
31
25
  try {
32
26
  const body = (await err.response?.json());
33
- if (body) {
34
- try {
35
- (0, client_1.extractData)(body); // 业务 code 命中 → 抛 AppError;不命中走兜底
36
- }
37
- catch (appErr) {
38
- if (appErr instanceof error_1.AppError && onErrorBody) {
39
- onErrorBody(body, appErr);
40
- }
41
- throw appErr;
42
- }
43
- }
27
+ if (body)
28
+ (0, client_1.extractData)(body); // 业务 code 命中 → 抛 AppError;不命中走兜底
44
29
  }
45
30
  catch (innerErr) {
46
31
  if (innerErr instanceof error_1.AppError)
@@ -51,20 +36,6 @@ onErrorBody) {
51
36
  }
52
37
  throw err;
53
38
  }
54
- /**
55
- * 多语句 SQL 失败时把已成功 statement 的 results 挂到 AppError.partial_results 上,
56
- * 供上层 handler 构造 PRD 要求的 `completed` 数组。
57
- *
58
- * 注意参数顺序:(body, appErr) — 与 mapDbHttpError 的 onErrorBody hook 签名对齐。
59
- */
60
- function attachSqlPartialResults(body, appErr) {
61
- const data = body.data;
62
- if (!data)
63
- return;
64
- if (!Array.isArray(data.results) || data.results.length === 0)
65
- return;
66
- appErr.partial_results = data.results;
67
- }
68
39
  // CLI 不再为 dbBranch 设默认值:
69
40
  // 用户没传 --env 就完全不携带 dbBranch query 参数,由后端 admin-inner 中间件
70
41
  // 按 workspace 多环境状态决定(多环境 → dev / 单环境 → main)。
@@ -90,60 +61,12 @@ async function execSql(opts) {
90
61
  }
91
62
  catch (err) {
92
63
  (0, client_1.traceHttp)("POST", url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
93
- await mapDbHttpError(err, url, "Failed to execute SQL", attachSqlPartialResults);
64
+ await mapDbHttpError(err, url, "Failed to execute SQL");
94
65
  throw err; // 不可达
95
66
  }
96
67
  const body = (await response.json());
97
- try {
98
- const data = (0, client_1.extractData)(body);
99
- const results = data.results ?? [];
100
- // 新协议(dataloom 端 InnerAdminExecuteSQL 错误统一走 success envelope):
101
- // results 末尾追加一条 SqlType="ERROR" 的哨兵,data 字段是 {code,message} JSON。
102
- // 这样网关在错误路径不会吞业务字段,CLI 拿到完整 partial_results + statement_index。
103
- const last = results.at(-1);
104
- if (last?.sqlType === "ERROR") {
105
- const appErr = parseSqlErrorSentinel(last.data);
106
- appErr.partial_results = results.slice(0, -1);
107
- const stmtIdx = data.errorStatementIndex;
108
- if (typeof stmtIdx === "number")
109
- appErr.statement_index = stmtIdx;
110
- throw appErr;
111
- }
112
- return results;
113
- }
114
- catch (appErr) {
115
- // 旧协议兼容(dataloom 服务端未部署新协议时):HTTP 2xx 但 envelope status_code != 0
116
- // 走 mapDbHttpError → AppError,partial_results 已被网关吞,仅保留 code+message。
117
- //
118
- // 新协议下上面的 try 块里已经手动 slice 设置了 partial_results(剥离了 ERROR 哨兵),
119
- // 这里必须跳过 attach,否则会被 body.data.results 完整数组(含哨兵)覆盖回去。
120
- if (appErr instanceof error_1.AppError && appErr.partial_results === undefined) {
121
- attachSqlPartialResults(body, appErr);
122
- }
123
- throw appErr;
124
- }
125
- }
126
- /**
127
- * 解析 InnerAdminExecuteSQL 错误哨兵(results 末尾那条 SqlType="ERROR" 项)的 data 字段。
128
- * 服务端约定:data 是 `{"code":"k_dl_xxx","message":"..."}` 形态的 JSON 字符串。
129
- *
130
- * 拿到 code+message 后复用 mapDataloomBizError 走与旧协议(ensureInnerSuccess)
131
- * 完全一致的映射规则——k_dl_1300015 → RESULT_SET_TOO_LARGE、SQLSTATE 透传等
132
- * hint / 语义 code 重写不能丢。否则新协议下 SERVER_ERROR_HINTS 会因为 code key
133
- * 不匹配而 miss,CLI 用户看不到 LIMIT 引导这种关键 hint。
134
- *
135
- * 解析失败则降级为 INTERNAL_DB_ERROR + 原始字符串,避免哨兵格式异常时整条调用炸掉。
136
- */
137
- function parseSqlErrorSentinel(payload) {
138
- try {
139
- const parsed = JSON.parse(payload);
140
- const code = typeof parsed.code === "string" && parsed.code !== "" ? parsed.code : "INTERNAL_DB_ERROR";
141
- const message = typeof parsed.message === "string" ? parsed.message : payload;
142
- return (0, client_1.mapDataloomBizError)(code, message);
143
- }
144
- catch {
145
- return new error_1.AppError("INTERNAL_DB_ERROR", payload);
146
- }
68
+ const data = (0, client_1.extractData)(body);
69
+ return data.results ?? [];
147
70
  }
148
71
  // ── db schema → InnerGetSchema ──
149
72
  /**
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SQLSTATE_MAP = void 0;
4
4
  exports.traceHttp = traceHttp;
5
5
  exports.ensureInnerSuccess = ensureInnerSuccess;
6
- exports.mapDataloomBizError = mapDataloomBizError;
7
6
  exports.extractData = extractData;
8
7
  exports.buildInnerUrl = buildInnerUrl;
9
8
  const error_1 = require("../../utils/error");
@@ -27,7 +26,11 @@ function traceHttp(method, url, start, response, err) {
27
26
  const status = response?.status ?? 0;
28
27
  const logid = response?.headers?.get?.("x-tt-logid") ?? "-";
29
28
  if (err !== undefined) {
30
- const errMsg = err instanceof Error ? err.message : typeof err === "string" ? err : JSON.stringify(err);
29
+ const errMsg = err instanceof Error
30
+ ? err.message
31
+ : typeof err === "string"
32
+ ? err
33
+ : JSON.stringify(err);
31
34
  (0, logger_1.debug)(`http ${method} ${url} ${String(status)} cost=${String(cost)}ms x-tt-logid=${logid} err=${errMsg}`);
32
35
  return;
33
36
  }
@@ -51,48 +54,40 @@ function ensureInnerSuccess(body) {
51
54
  const code = body.status_code ?? body.ErrorCode ?? "0";
52
55
  if (code === "0" || code === "")
53
56
  return;
54
- const appErr = mapDataloomBizError(code, body.error_msg ?? body.ErrorMessage ?? `dataloom API error [${code}]`);
57
+ const message = stripPgPrefix(body.error_msg ?? body.ErrorMessage ?? `dataloom API error [${code}]`);
55
58
  // PRD 多语句失败:后端在 envelope 顶层透出 errorStatementIndex(从 0 起计),
56
- // 单语句 / 单元执行不会带这个字段。挂到 AppError 上让 emitError 落到错误信封
57
- // statement_index 字段。
58
- if (typeof body.errorStatementIndex === "number") {
59
- appErr.statement_index = body.errorStatementIndex;
60
- }
61
- throw appErr;
62
- }
63
- /**
64
- * 把 dataloom 业务错误码 + 原始 message 映射为语义化 AppError。
65
- *
66
- * 同时被两条错误路径共用:
67
- * - ensureInnerSuccess(旧协议:BaseResp.status_code != "0")
68
- * - api.ts 的 parseSqlErrorSentinel(新协议:success envelope + ERROR 哨兵)
69
- *
70
- * 优先级:
71
- * 1. k_dl_1300002 + SQLSTATE 透传 → SQLSTATE_MAP
72
- * 2. k_dl_1600000 + "Invalid DB Branch" → MULTI_ENV_NOT_INITIALIZED
73
- * 3. BIZ_ERR_MAP 命中 → 语义 code(可能带 hint)
74
- * 4. 兜底 DB_API_<code>
75
- *
76
- * stripPgPrefix 在这里做一次,调用方传 raw message 即可。
77
- */
78
- function mapDataloomBizError(code, rawMessage) {
79
- const message = stripPgPrefix(rawMessage);
59
+ // 单语句 / 单元执行不会带这个字段。下面的 AppError 都把它带上,让最终
60
+ // CLI JSON envelope 写到 error.statement_index
61
+ const stmtIdx = typeof body.errorStatementIndex === "number" ? body.errorStatementIndex : undefined;
62
+ // k_dl_1300002 是 PG 执行透传错误;error_msg 里常带 SQLSTATE,优先按 SQLSTATE 映射
80
63
  if (code === "k_dl_1300002") {
81
64
  const sqlstate = extractSqlstate(message);
82
65
  if (sqlstate && exports.SQLSTATE_MAP[sqlstate]) {
83
- return new error_1.AppError(exports.SQLSTATE_MAP[sqlstate], message);
66
+ throw new error_1.AppError(exports.SQLSTATE_MAP[sqlstate], message, { statement_index: stmtIdx });
84
67
  }
85
68
  }
69
+ // k_dl_1600000 是 dataloom 通用参数错误,不能整体映射;
70
+ // message 以 "Invalid DB Branch" 开头的两种触发场景:
71
+ // 1) 单环境应用使用 --env(多环境未初始化)
72
+ // 2) 多环境应用传入了不存在的 env 名(如 --env staging)
73
+ // 两者外部表象一致:dbBranch 在 db_branch 表里查不到,固定映射为 MULTI_ENV_NOT_INITIALIZED。
86
74
  if (code === "k_dl_1600000" && message.startsWith("Invalid DB Branch")) {
87
- return new error_1.AppError("MULTI_ENV_NOT_INITIALIZED", "--env is not available (multi-env not initialized)", { next_actions: ["Verify the --env value matches an existing dbBranch."] });
75
+ throw new error_1.AppError("MULTI_ENV_NOT_INITIALIZED", "--env is not available (multi-env not initialized)", {
76
+ next_actions: [
77
+ "Verify the --env value matches an existing dbBranch.",
78
+ ],
79
+ });
88
80
  }
81
+ // 业务 code 优先映射到语义 CLI code;未知 code 透传为 DB_API_<code> 样式
89
82
  const mapped = BIZ_ERR_MAP.get(code);
90
83
  if (mapped) {
91
- return new error_1.AppError(mapped.code, mapped.message ?? message, {
84
+ throw new error_1.AppError(mapped.code, mapped.message ?? message, {
92
85
  next_actions: mapped.hint ? [mapped.hint] : undefined,
86
+ statement_index: stmtIdx,
93
87
  });
94
88
  }
95
- return new error_1.AppError(`DB_API_${code}`, message);
89
+ // 兜底:dataloom 未映射的 code 原样透传
90
+ throw new error_1.AppError(`DB_API_${code}`, message, { statement_index: stmtIdx });
96
91
  }
97
92
  /**
98
93
  * 剥掉 PG 透传错误的 "ERROR:" 前缀:CLI 输出本身就会前缀 "Error:",
@@ -116,19 +111,13 @@ function extractSqlstate(msg) {
116
111
  const BIZ_ERR_MAP = new Map(Object.entries({
117
112
  // 来源:真实环境探测(dataloom InnerExecuteSQL / InnerGetSchema)
118
113
  // k_dl_000001:DB 连接 / QPS 限流 等基础设施层错误
119
- k_dl_000001: {
120
- code: "INTERNAL_DB_ERROR",
121
- hint: "检查 dbBranch 与应用 PG 实例状态,或稍后重试",
122
- },
114
+ "k_dl_000001": { code: "INTERNAL_DB_ERROR", hint: "检查 dbBranch 与应用 PG 实例状态,或稍后重试" },
123
115
  // k_dl_000002:SQL 解析 / 不支持的 SQL 类型(VACUUM、COPY、SET、SHOW 等)
124
- k_dl_000002: { code: "SQL_SYNTAX_ERROR" },
116
+ "k_dl_000002": { code: "SQL_SYNTAX_ERROR" },
125
117
  // k_dl_000003:查询命中系统表(pg_tables / pg_user 等)被拒
126
- k_dl_000003: { code: "SQL_OPERATION_FORBIDDEN" },
118
+ "k_dl_000003": { code: "SQL_OPERATION_FORBIDDEN" },
127
119
  // k_dl_1300002:PG 执行错误;实际 SQLSTATE 由 extractSqlstate 单独映射
128
120
  // 未匹配到 SQLSTATE 时走下面兜底 DB_API_<code>
129
- // k_dl_1300015:SELECT 结果超过 1000 行硬拦;多行 hint 由 output.ts 的
130
- // SERVER_ERROR_HINTS 按语义 code 兜底,这里只做 code 改名
131
- k_dl_1300015: { code: "RESULT_SET_TOO_LARGE" },
132
121
  }));
133
122
  /** PG SQLSTATE → CLI code(当前 dataloom 不一定透传,预留未来使用) */
134
123
  exports.SQLSTATE_MAP = {
@@ -117,35 +117,11 @@ function toDetail(t, stats) {
117
117
  // 优先用 tableStats.indexes(PRIMARY + UNIQUE + INDEX 统一视图);
118
118
  // 回退到 TableVO.indexes(仅二级索引,老后端没返回 tableStats 时使用)
119
119
  const rawIndexes = stats?.indexes ?? t.indexes ?? [];
120
- const constraints = [];
121
- const indexes = [];
122
- for (const i of rawIndexes) {
123
- const cols = i.indexColumns ?? [];
124
- // 过滤掉空列条目(后端偶发返 indexColumns=[] 的占位项),渲染成 "UNIQUE ()" 看起来像 bug
125
- if (cols.length === 0)
126
- continue;
127
- const kind = i.indexType.toLowerCase();
128
- if (kind === "primary") {
129
- constraints.push({ type: "PRIMARY KEY", columns: cols });
130
- }
131
- else if (kind === "unique") {
132
- constraints.push({ type: "UNIQUE", columns: cols });
133
- }
134
- else {
135
- // foreign / normal / 其它统一进 indexes 段;method 从 indexDef 的 USING 段解析
136
- indexes.push({
137
- name: i.indexName,
138
- columns: cols,
139
- method: parseIndexMethod(i.indexDef) ?? "btree",
140
- });
141
- }
142
- }
143
120
  return {
144
121
  name: t.tableName,
145
122
  description: t.comment && t.comment.length > 0 ? t.comment : null,
146
123
  columns: (t.fields ?? []).map(toColumn),
147
- constraints,
148
- indexes,
124
+ indexes: rawIndexes.map(toIndex),
149
125
  estimated_row_count: typeof stats?.estimatedRowCount === "number" ? stats.estimatedRowCount : null,
150
126
  size_bytes: typeof stats?.sizeBytes === "number" ? stats.sizeBytes : null,
151
127
  };
@@ -161,14 +137,25 @@ function toColumn(f) {
161
137
  comment: f.comment && f.comment.length > 0 ? f.comment : null,
162
138
  };
163
139
  }
140
+ function toIndex(i) {
141
+ return {
142
+ name: i.indexName,
143
+ type: translateIndexType(i.indexType),
144
+ columns: i.indexColumns ?? [],
145
+ };
146
+ }
164
147
  /**
165
- * PG CREATE INDEX 语句里提取访问方法(btree / gin / gist / hash 等)。
166
- * indexDef 示例: "CREATE INDEX idx_users_name ON public.users USING btree (name)"
167
- * 解析失败返 null,由调用方兜底成 PG 默认的 "btree"。
148
+ * 把后端原始 IndexType(小写:primary / unique / normal / foreign)翻译为 PRD 约定的展示值。
149
+ * 规则:
150
+ * primary → "PRIMARY KEY"
151
+ * unique → "UNIQUE"
152
+ * 其它 → "INDEX"(foreign 外键通过 relationships 表达,不进 indexes;真的遇到也归 INDEX)
168
153
  */
169
- function parseIndexMethod(indexDef) {
170
- if (!indexDef)
171
- return null;
172
- const m = /USING\s+(\w+)/i.exec(indexDef);
173
- return m ? m[1].toLowerCase() : null;
154
+ function translateIndexType(raw) {
155
+ const s = raw.toLowerCase();
156
+ if (s === "primary")
157
+ return "PRIMARY KEY";
158
+ if (s === "unique")
159
+ return "UNIQUE";
160
+ return "INDEX";
174
161
  }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRelease = createRelease;
4
+ exports.getRelease = getRelease;
5
+ exports.listPipelineInstances = listPipelineInstances;
6
+ exports.getReleaseLogs = getReleaseLogs;
7
+ exports.queryPipelineInstance = queryPipelineInstance;
8
+ const http_1 = require("../../utils/http");
9
+ const index_1 = require("../../api/app/index");
10
+ const DEFAULT_ERR_CODE = "INTERNAL_DEVOPS_ERROR";
11
+ function envelopeOpts(errPrefix) {
12
+ return {
13
+ errPrefix,
14
+ defaultErrCode: DEFAULT_ERR_CODE,
15
+ // deploy 与 app 走同一个 PSM;业务码映射复用
16
+ mapErr: index_1.mapDevopsError,
17
+ };
18
+ }
19
+ /** POST /v1/devops/app/:appID/release — 触发发布 */
20
+ async function createRelease(req) {
21
+ const url = `/v1/devops/app/${encodeURIComponent(req.appID)}/release`;
22
+ return (0, http_1.postInnerApi)(url, req, envelopeOpts("Failed to create release"));
23
+ }
24
+ /**
25
+ * GET /v1/devops/app/:appID/release — 获取发布单详情
26
+ *
27
+ * BAM 路径不带 releaseID,IDL 也未在 request body 暴露字段;按 query string 透传。
28
+ * 如 BAM 真没接收 releaseID,会返回最新发布单(待 e2e 验证)。
29
+ */
30
+ async function getRelease(req) {
31
+ const base = `/v1/devops/app/${encodeURIComponent(req.appID)}/release`;
32
+ const url = req.releaseID
33
+ ? `${base}?releaseID=${encodeURIComponent(req.releaseID)}`
34
+ : base;
35
+ return (0, http_1.getInnerApi)(url, envelopeOpts("Failed to get release"));
36
+ }
37
+ /**
38
+ * POST /v1/pipeline/app/:appID/instance/list — 分页查询发布历史
39
+ *
40
+ * 走 lark.apaas.devops_platform PSM(同 queryPipelineInstance)。
41
+ * 不复用 mapDevopsError——那是 lark.apaas.devops 的业务码映射。
42
+ */
43
+ async function listPipelineInstances(req) {
44
+ const url = `/v1/pipeline/app/${encodeURIComponent(req.appID)}/instance/list`;
45
+ return (0, http_1.postInnerApi)(url, req, {
46
+ errPrefix: "Failed to list deploys",
47
+ defaultErrCode: DEFAULT_ERR_CODE,
48
+ });
49
+ }
50
+ /** POST /v1/devops/app/:appID/release/logs — 获取发布错误日志 */
51
+ async function getReleaseLogs(req) {
52
+ const url = `/v1/devops/app/${encodeURIComponent(req.appID)}/release/logs`;
53
+ return (0, http_1.postInnerApi)(url, req, envelopeOpts("Failed to get release logs"));
54
+ }
55
+ /**
56
+ * GET /v1/pipeline/app/:appID/instance/:instanceID/detail — 轮询发布状态
57
+ *
58
+ * 走 lark.apaas.devops_platform PSM;URL 前缀不同但共用同一管理端 gateway。
59
+ */
60
+ async function queryPipelineInstance(req) {
61
+ const url = `/v1/pipeline/app/${encodeURIComponent(req.appID)}/instance/${encodeURIComponent(req.instanceID)}/detail`;
62
+ return (0, http_1.getInnerApi)(url, {
63
+ errPrefix: "Failed to query pipeline",
64
+ defaultErrCode: DEFAULT_ERR_CODE,
65
+ });
66
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NodeStatus = exports.ReleaseStatus = exports.nodeStatusFromText = exports.nodeStatusText = exports.releaseStatusFromText = exports.releaseStatusText = exports.errorJobSchema = exports.deployGetSchema = exports.deployHistorySchema = exports.queryPipelineInstance = exports.getReleaseLogs = exports.listPipelineInstances = exports.getRelease = exports.createRelease = void 0;
4
+ var api_1 = require("./api");
5
+ Object.defineProperty(exports, "createRelease", { enumerable: true, get: function () { return api_1.createRelease; } });
6
+ Object.defineProperty(exports, "getRelease", { enumerable: true, get: function () { return api_1.getRelease; } });
7
+ Object.defineProperty(exports, "listPipelineInstances", { enumerable: true, get: function () { return api_1.listPipelineInstances; } });
8
+ Object.defineProperty(exports, "getReleaseLogs", { enumerable: true, get: function () { return api_1.getReleaseLogs; } });
9
+ Object.defineProperty(exports, "queryPipelineInstance", { enumerable: true, get: function () { return api_1.queryPipelineInstance; } });
10
+ var schemas_1 = require("./schemas");
11
+ Object.defineProperty(exports, "deployHistorySchema", { enumerable: true, get: function () { return schemas_1.deployHistorySchema; } });
12
+ Object.defineProperty(exports, "deployGetSchema", { enumerable: true, get: function () { return schemas_1.deployGetSchema; } });
13
+ Object.defineProperty(exports, "errorJobSchema", { enumerable: true, get: function () { return schemas_1.errorJobSchema; } });
14
+ Object.defineProperty(exports, "releaseStatusText", { enumerable: true, get: function () { return schemas_1.releaseStatusText; } });
15
+ Object.defineProperty(exports, "releaseStatusFromText", { enumerable: true, get: function () { return schemas_1.releaseStatusFromText; } });
16
+ Object.defineProperty(exports, "nodeStatusText", { enumerable: true, get: function () { return schemas_1.nodeStatusText; } });
17
+ Object.defineProperty(exports, "nodeStatusFromText", { enumerable: true, get: function () { return schemas_1.nodeStatusFromText; } });
18
+ var types_1 = require("./types");
19
+ Object.defineProperty(exports, "ReleaseStatus", { enumerable: true, get: function () { return types_1.ReleaseStatus; } });
20
+ Object.defineProperty(exports, "NodeStatus", { enumerable: true, get: function () { return types_1.NodeStatus; } });