@lark-apaas/miaoda-cli 0.1.1 → 0.1.2-alpha.1df8e52

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 (64) hide show
  1. package/dist/api/app/api.js +25 -0
  2. package/dist/api/app/index.js +15 -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 +83 -6
  6. package/dist/api/db/client.js +40 -29
  7. package/dist/api/db/parsers.js +33 -20
  8. package/dist/api/db/sql-keywords.js +123 -0
  9. package/dist/api/deploy/api.js +60 -0
  10. package/dist/api/deploy/index.js +16 -0
  11. package/dist/api/deploy/schemas.js +103 -0
  12. package/dist/api/deploy/types.js +22 -0
  13. package/dist/api/file/api.js +78 -24
  14. package/dist/api/file/client.js +1 -5
  15. package/dist/api/file/parsers.js +1 -5
  16. package/dist/api/index.js +7 -1
  17. package/dist/api/observability/api.js +52 -0
  18. package/dist/api/observability/index.js +16 -0
  19. package/dist/api/observability/schemas.js +39 -0
  20. package/dist/api/observability/types.js +27 -0
  21. package/dist/api/plugin/api.js +8 -3
  22. package/dist/cli/commands/app/index.js +62 -0
  23. package/dist/cli/commands/db/index.js +1 -0
  24. package/dist/cli/commands/deploy/index.js +140 -0
  25. package/dist/cli/commands/index.js +6 -0
  26. package/dist/cli/commands/observability/index.js +229 -0
  27. package/dist/cli/commands/plugin/index.js +18 -6
  28. package/dist/cli/commands/shared.js +62 -6
  29. package/dist/cli/handlers/app/get.js +48 -0
  30. package/dist/cli/handlers/app/index.js +7 -0
  31. package/dist/cli/handlers/app/update.js +59 -0
  32. package/dist/cli/handlers/db/data.js +22 -2
  33. package/dist/cli/handlers/db/schema.js +22 -8
  34. package/dist/cli/handlers/db/sql.js +304 -16
  35. package/dist/cli/handlers/deploy/deploy.js +83 -0
  36. package/dist/cli/handlers/deploy/error-log.js +61 -0
  37. package/dist/cli/handlers/deploy/get.js +70 -0
  38. package/dist/cli/handlers/deploy/helpers.js +41 -0
  39. package/dist/cli/handlers/deploy/history.js +70 -0
  40. package/dist/cli/handlers/deploy/index.js +14 -0
  41. package/dist/cli/handlers/deploy/polling.js +139 -0
  42. package/dist/cli/handlers/file/cp.js +39 -17
  43. package/dist/cli/handlers/file/ls.js +1 -3
  44. package/dist/cli/handlers/file/rm.js +4 -3
  45. package/dist/cli/handlers/observability/analytics.js +190 -0
  46. package/dist/cli/handlers/observability/helpers.js +66 -0
  47. package/dist/cli/handlers/observability/index.js +12 -0
  48. package/dist/cli/handlers/observability/log.js +94 -0
  49. package/dist/cli/handlers/observability/metric.js +208 -0
  50. package/dist/cli/handlers/observability/trace.js +102 -0
  51. package/dist/cli/handlers/plugin/plugin-local.js +23 -9
  52. package/dist/cli/handlers/plugin/plugin.js +21 -7
  53. package/dist/cli/help.js +5 -2
  54. package/dist/utils/colors.js +98 -0
  55. package/dist/utils/devops-error.js +28 -0
  56. package/dist/utils/error.js +11 -0
  57. package/dist/utils/fuzzy-match.js +91 -0
  58. package/dist/utils/git.js +29 -0
  59. package/dist/utils/http.js +120 -4
  60. package/dist/utils/index.js +13 -1
  61. package/dist/utils/output.js +397 -12
  62. package/dist/utils/render.js +61 -41
  63. package/dist/utils/time.js +132 -0
  64. package/package.json +16 -6
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NodeStatus = exports.nodeStatusFromText = exports.nodeStatusText = exports.errorJobSchema = exports.deployGetSchema = exports.deployHistorySchema = exports.queryPipelineInstance = exports.getErrorLog = exports.listPipelineInstances = 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, "listPipelineInstances", { enumerable: true, get: function () { return api_1.listPipelineInstances; } });
7
+ Object.defineProperty(exports, "getErrorLog", { enumerable: true, get: function () { return api_1.getErrorLog; } });
8
+ Object.defineProperty(exports, "queryPipelineInstance", { enumerable: true, get: function () { return api_1.queryPipelineInstance; } });
9
+ var schemas_1 = require("./schemas");
10
+ Object.defineProperty(exports, "deployHistorySchema", { enumerable: true, get: function () { return schemas_1.deployHistorySchema; } });
11
+ Object.defineProperty(exports, "deployGetSchema", { enumerable: true, get: function () { return schemas_1.deployGetSchema; } });
12
+ Object.defineProperty(exports, "errorJobSchema", { enumerable: true, get: function () { return schemas_1.errorJobSchema; } });
13
+ Object.defineProperty(exports, "nodeStatusText", { enumerable: true, get: function () { return schemas_1.nodeStatusText; } });
14
+ Object.defineProperty(exports, "nodeStatusFromText", { enumerable: true, get: function () { return schemas_1.nodeStatusFromText; } });
15
+ var types_1 = require("./types");
16
+ Object.defineProperty(exports, "NodeStatus", { enumerable: true, get: function () { return types_1.NodeStatus; } });
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.errorJobSchema = exports.deployGetSchema = exports.deployHistorySchema = void 0;
4
+ exports.nodeStatusText = nodeStatusText;
5
+ exports.nodeStatusFromText = nodeStatusFromText;
6
+ const output_1 = require("../../utils/output");
7
+ const types_1 = require("./types");
8
+ const NODE_STATUS_TEXT = {
9
+ [types_1.NodeStatus.UNSPECIFIED]: "unknown",
10
+ [types_1.NodeStatus.TODO]: "todo",
11
+ [types_1.NodeStatus.RUNNING]: "running",
12
+ [types_1.NodeStatus.SUCCESS]: "success",
13
+ [types_1.NodeStatus.FAILED]: "failed",
14
+ [types_1.NodeStatus.CANCELED]: "canceled",
15
+ [types_1.NodeStatus.HOLD_ON]: "hold_on",
16
+ };
17
+ function nodeStatusText(v) {
18
+ if (typeof v !== "number")
19
+ return undefined;
20
+ return NODE_STATUS_TEXT[v];
21
+ }
22
+ /** NodeStatus 文本(CLI flag 字符串)→ 枚举数值 */
23
+ function nodeStatusFromText(text) {
24
+ const lower = text.toLowerCase();
25
+ for (const [num, label] of Object.entries(NODE_STATUS_TEXT)) {
26
+ if (label === lower)
27
+ return Number(num);
28
+ }
29
+ return undefined;
30
+ }
31
+ function pipelineDurationMs(row) {
32
+ // SimpleInstance.createdAt / updatedAt:BAM IDL 未注单位,e2e 看是 ms。
33
+ const start = Number(row.createdAt);
34
+ const end = Number(row.updatedAt);
35
+ if (!Number.isFinite(start) || !Number.isFinite(end))
36
+ return undefined;
37
+ return end - start;
38
+ }
39
+ /**
40
+ * deploy history 列表的 pretty 渲染契约。
41
+ *
42
+ * BAM 返回 SimpleInstance(pipeline 实例),状态走 NodeStatus。
43
+ * 默认列:deploy-id(=ID)/ status / creator / created-at / duration(updatedAt-createdAt)。
44
+ */
45
+ exports.deployHistorySchema = {
46
+ columns: [
47
+ { key: "ID", label: "deploy-id" },
48
+ {
49
+ key: "status_text",
50
+ label: "status",
51
+ derive: (row) => nodeStatusText(row.status),
52
+ },
53
+ { key: "creator", label: "creator" },
54
+ { key: "createdAt", label: "created-at", format: output_1.fmt.ms() },
55
+ {
56
+ key: "duration",
57
+ label: "duration",
58
+ format: output_1.fmt.durationMs(),
59
+ derive: pipelineDurationMs,
60
+ },
61
+ ],
62
+ strict: true,
63
+ };
64
+ /**
65
+ * deploy get 单条详情的 key-value 渲染契约(基于 SimpleInstance)。
66
+ *
67
+ * 由于 BAM 没有 by-ID 的查询接口,handler 用 listPipelineInstances 在最近窗口
68
+ * 里筛一条 SimpleInstance 透传过来。
69
+ */
70
+ exports.deployGetSchema = {
71
+ columns: [
72
+ { key: "ID", label: "deploy-id" },
73
+ {
74
+ key: "status_text",
75
+ label: "status",
76
+ derive: (row) => nodeStatusText(row.status),
77
+ },
78
+ { key: "creator", label: "creator" },
79
+ { key: "updater", label: "updater" },
80
+ { key: "createdAt", label: "created-at", format: output_1.fmt.ms() },
81
+ { key: "updatedAt", label: "updated-at", format: output_1.fmt.ms() },
82
+ {
83
+ key: "duration",
84
+ label: "duration",
85
+ format: output_1.fmt.durationMs(),
86
+ derive: pipelineDurationMs,
87
+ },
88
+ { key: "templateID", label: "template" },
89
+ { key: "description" },
90
+ { key: "parameters" },
91
+ { key: "envVariables", label: "env-vars" },
92
+ ],
93
+ strict: true,
94
+ };
95
+ /** deploy error-log 表格渲染契约 */
96
+ exports.errorJobSchema = {
97
+ columns: [
98
+ { key: "jobID", label: "job-id" },
99
+ { key: "componentName", label: "component" },
100
+ { key: "errorMsg", label: "error", format: output_1.fmt.truncate(120) },
101
+ ],
102
+ strict: true,
103
+ };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ // 与 BAM lark.apaas.devops v1.0.326 / 1.0.327 + lark.apaas.devops_platform v1.0.247 对齐:
3
+ // - CLICreateForceRelease (4070318) 创建发布 lark.apaas.devops
4
+ // - CliQueryPipelineInstance (4069872) 轮询发布状态 lark.apaas.devops_platform
5
+ // - CliListPipelineInstances (4073969) 分页获取发布历史 lark.apaas.devops_platform
6
+ // - CliGetToolInstanceErrorJobs (4073972) 获取发布错误日志 lark.apaas.devops_platform
7
+ //
8
+ // 发布单的唯一标识是 pipelineTaskID,对外即 deploy-id;error-log 等接口直接用它做
9
+ // URL 路径段(instanceID)。曾经的 Release/releaseID 概念已全面废弃。
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.NodeStatus = void 0;
12
+ /** Pipeline 节点状态(devops_platform) */
13
+ var NodeStatus;
14
+ (function (NodeStatus) {
15
+ NodeStatus[NodeStatus["UNSPECIFIED"] = 0] = "UNSPECIFIED";
16
+ NodeStatus[NodeStatus["TODO"] = 1] = "TODO";
17
+ NodeStatus[NodeStatus["RUNNING"] = 2] = "RUNNING";
18
+ NodeStatus[NodeStatus["SUCCESS"] = 3] = "SUCCESS";
19
+ NodeStatus[NodeStatus["FAILED"] = 4] = "FAILED";
20
+ NodeStatus[NodeStatus["CANCELED"] = 5] = "CANCELED";
21
+ NodeStatus[NodeStatus["HOLD_ON"] = 6] = "HOLD_ON";
22
+ })(NodeStatus || (exports.NodeStatus = NodeStatus = {}));
@@ -209,9 +209,7 @@ async function resolveInputs(opts) {
209
209
  return [];
210
210
  return Promise.all(opts.inputs.map((input) => {
211
211
  const kind = opts.forceAs ?? ((0, detect_1.looksLikePath)(input) ? "path" : "name");
212
- return kind === "path"
213
- ? resolveByPath(opts.appId, input)
214
- : resolveByName(opts.appId, input);
212
+ return kind === "path" ? resolveByPath(opts.appId, input) : resolveByName(opts.appId, input);
215
213
  }));
216
214
  }
217
215
  /** path 输入:HEAD 判存在性,path 在 bucket 内 unique 无需反查。 */
@@ -297,14 +295,44 @@ async function preUpload(appId, req) {
297
295
  const body = await (0, client_1.doPost)(url, { bucketID: bucketId, appID: appId, ...req }, { errorContext: "pre-upload" });
298
296
  return extractEnvelope(body);
299
297
  }
298
+ /**
299
+ * 调用 upload callback 拿到对象元数据。
300
+ *
301
+ * 网关 IDL 在 metadata 字段加了 api.response.converter = "decode",正常路径下
302
+ * HTTP 响应里的 metadata 已经被网关从字符串解码成对象;这里两种形态都兼容:
303
+ * - object → 直接当 CallbackObjectVO 用(网关解码场景)
304
+ * - string → JSON.parse 出来用(后端原始形态 / 网关行为变化兜底)
305
+ *
306
+ * 解析失败 / metadata 缺失时返回空对象,由 uploadFile 用本地兜底字段填充。
307
+ */
300
308
  async function uploadCallback(appId, req) {
301
309
  const url = `/v1/storage/app/${encodeURIComponent(appId)}/object/callback`;
302
- const body = await (0, client_1.doPost)(url, req, { errorContext: "upload callback" });
303
- (0, client_1.ensureSuccess)(body);
310
+ const body = await (0, client_1.doPost)(url, req, {
311
+ errorContext: "upload callback",
312
+ });
313
+ const data = extractEnvelope(body);
314
+ const metadata = data.metadata;
315
+ if (!metadata) {
316
+ return {};
317
+ }
318
+ if (typeof metadata === "object") {
319
+ return metadata;
320
+ }
321
+ // string 形态兜底
322
+ try {
323
+ return JSON.parse(metadata);
324
+ }
325
+ catch (err) {
326
+ (0, logger_1.debug)(`upload callback metadata json parse failed: ${err instanceof Error ? err.message : String(err)}`);
327
+ return {};
328
+ }
304
329
  }
305
330
  /**
306
331
  * 上传文件(3 步:preUpload → PUT uploadURL → callback)。
307
332
  * PUT 步骤直接 fetch uploadURL(对象存储直传,绕开框架 HttpClient)。
333
+ *
334
+ * 注意:opts.remotePath 仅作为目录前缀传给服务端;最终对象 key 由服务端唯一生成
335
+ * (形如 "<前缀>/<16位ID>.<扩展名>"),从 preUpload 响应的 filePath 字段取回。
308
336
  */
309
337
  async function uploadFile(opts) {
310
338
  const pre = await preUpload(opts.appId, {
@@ -323,11 +351,15 @@ async function uploadFile(opts) {
323
351
  // 这里复制到独立 ArrayBuffer 以满足 lib.dom.d.ts 的 BodyInit 约束
324
352
  const ab = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength);
325
353
  const uploadStart = Date.now();
354
+ // Content-Disposition 用 attachment + filename 编码原始文件名。TOS 会把这个
355
+ // header 作为对象 metadata 存住,服务端 callback 阶段 HeadObject 读回并解析
356
+ // filename 写入 DB。我们要不传 header,服务端走兜底会把 storage key 当文件名。
326
357
  const res = await fetch(pre.uploadURL, {
327
358
  method: "PUT",
328
359
  headers: {
329
360
  "Content-Type": opts.contentType,
330
361
  "Content-Length": String(opts.fileSize),
362
+ "Content-Disposition": `attachment; filename="${sanitizeFileName(opts.fileName)}"`,
331
363
  },
332
364
  body: ab,
333
365
  });
@@ -345,34 +377,56 @@ async function uploadFile(opts) {
345
377
  if (!etag) {
346
378
  throw new error_1.AppError("FILE_UPLOAD_FAILED", "Upload PUT returned no ETag");
347
379
  }
348
- // callback 失败仅 warning,文件已经上传到对象存储
380
+ // callback 返回服务端实际生成的对象元数据(filePath / file_name / download_url)。
381
+ // path 末段是平台生成的 16 位 GID,CLI 无法靠本地信息推断;callback 失败 /
382
+ // metadata.filePath 缺失时直接抛 FILE_UPLOAD_CALLBACK_INCOMPLETE:对象已落
383
+ // TOS(PUT 已成功 + ETag 拿到),但 CLI 拿不到真实 path。把 fileName 写到
384
+ // hint 里引导用户走 `file ls --name` 查实际 path,避免返一个假 path 让后续
385
+ // stat/download/rm 全部 FILE_NOT_FOUND。
386
+ let metadata;
349
387
  try {
350
- await uploadCallback(opts.appId, { uploadID: pre.uploadID, eTag: etag });
388
+ metadata = await uploadCallback(opts.appId, { uploadID: pre.uploadID, eTag: etag });
351
389
  }
352
390
  catch (err) {
353
- (0, logger_1.debug)(`upload callback failed: ${err instanceof Error ? err.message : String(err)}`);
391
+ const reason = err instanceof Error ? err.message : String(err);
392
+ throw new error_1.AppError("FILE_UPLOAD_CALLBACK_INCOMPLETE", `Upload callback failed: ${reason}; file may already exist in storage`, {
393
+ next_actions: [
394
+ `Run \`miaoda file ls --name ${opts.fileName}\` to locate the uploaded file.`,
395
+ ],
396
+ });
354
397
  }
355
- const remotePath = opts.remotePath ?? `/${opts.fileName}`;
398
+ if (!metadata.filePath) {
399
+ throw new error_1.AppError("FILE_UPLOAD_CALLBACK_INCOMPLETE", "Upload callback returned no filePath; file may already exist in storage", {
400
+ next_actions: [
401
+ `Run \`miaoda file ls --name ${opts.fileName}\` to locate the uploaded file.`,
402
+ ],
403
+ });
404
+ }
405
+ const path = metadata.filePath.startsWith("/") ? metadata.filePath : "/" + metadata.filePath;
356
406
  const result = {
357
- file_name: opts.fileName,
358
- path: remotePath,
359
- size: opts.fileSize,
360
- type: opts.contentType,
407
+ // 优先取服务端 ObjectVO.name(来自 PUT 时带的 Content-Disposition),
408
+ // 与后续 file ls / file stat 返回的展示名保持一致;缺失时降级用本地 fileName。
409
+ file_name: metadata.name ?? opts.fileName,
410
+ path,
411
+ size: metadata.metadata?.contentLength ?? opts.fileSize,
412
+ type: metadata.metadata?.mimeType ?? opts.contentType,
361
413
  };
362
- // 上传完成后补一次 stat,拿到后端返回的 download_url(preUpload/callback 都不返)
363
- // 非关键路径:拿不到就降级,只是 JSON 输出里缺 download_url 字段
364
- try {
365
- const info = await statFile({ appId: opts.appId, filePath: remotePath });
366
- if (info.download_url)
367
- result.download_url = info.download_url;
368
- if (info.file_name)
369
- result.file_name = info.file_name;
370
- }
371
- catch (err) {
372
- (0, logger_1.debug)(`post-upload stat failed: ${err instanceof Error ? err.message : String(err)}`);
414
+ if (metadata.downloadURL) {
415
+ result.download_url = metadata.downloadURL;
373
416
  }
374
417
  return result;
375
418
  }
419
+ /**
420
+ * 把文件名清理成可安全放进 Content-Disposition `filename="..."` 的形态。
421
+ * 与 fullstack-plugin 的 sanitizeFileName 行为一致:
422
+ * 1. 去掉对 TOS / 文件系统不友好的字符 [: " \ / * ? < > | , ;]
423
+ * 2. encodeURIComponent 把非 ASCII(中文等)做百分号编码,保证 header 合法
424
+ * 3. 处理后为空时退回 "download_file" 兜底
425
+ */
426
+ function sanitizeFileName(fileName) {
427
+ const illegalChars = /[:"\\/*?<>|,;]/g;
428
+ return encodeURIComponent(fileName.replace(illegalChars, "")) || "download_file";
429
+ }
376
430
  // ── 预签下载 URL ──
377
431
  /**
378
432
  * 获取预签下载 URL。
@@ -22,11 +22,7 @@ function traceHttp(method, url, start, response, err) {
22
22
  const status = response?.status ?? 0;
23
23
  const logid = response?.headers?.get?.("x-tt-logid") ?? "-";
24
24
  if (err !== undefined) {
25
- const errMsg = err instanceof Error
26
- ? err.message
27
- : typeof err === "string"
28
- ? err
29
- : JSON.stringify(err);
25
+ const errMsg = err instanceof Error ? err.message : typeof err === "string" ? err : JSON.stringify(err);
30
26
  (0, logger_1.debug)(`http ${method} ${url} ${String(status)} cost=${String(cost)}ms x-tt-logid=${logid} err=${errMsg}`);
31
27
  return;
32
28
  }
@@ -16,11 +16,7 @@ function int(value) {
16
16
  function readSize(meta) {
17
17
  if (!meta)
18
18
  return 0;
19
- const v = meta.size ??
20
- meta.fileSize ??
21
- meta.file_size ??
22
- meta.contentLength ??
23
- 0;
19
+ const v = meta.size ?? meta.fileSize ?? meta.file_size ?? meta.contentLength ?? 0;
24
20
  return int(v);
25
21
  }
26
22
  /**
package/dist/api/index.js CHANGED
@@ -33,10 +33,16 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.db = exports.file = exports.plugin = void 0;
36
+ exports.deploy = exports.app = exports.observability = exports.db = exports.file = exports.plugin = void 0;
37
37
  const _plugin = __importStar(require("../api/plugin/index"));
38
38
  const _file = __importStar(require("../api/file/index"));
39
39
  const _db = __importStar(require("../api/db/index"));
40
+ const _observability = __importStar(require("../api/observability/index"));
41
+ const _app = __importStar(require("../api/app/index"));
42
+ const _deploy = __importStar(require("../api/deploy/index"));
40
43
  exports.plugin = { ..._plugin };
41
44
  exports.file = { ..._file };
42
45
  exports.db = { ..._db };
46
+ exports.observability = { ..._observability };
47
+ exports.app = { ..._app };
48
+ exports.deploy = { ..._deploy };
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.searchLogs = searchLogs;
4
+ exports.searchTraces = searchTraces;
5
+ exports.getTraces = getTraces;
6
+ exports.queryMetricsData = queryMetricsData;
7
+ exports.queryAnalyticsData = queryAnalyticsData;
8
+ const http_1 = require("../../utils/http");
9
+ const DEFAULT_ERR_CODE = "INTERNAL_OB_ERROR";
10
+ // ── 日志 ──
11
+ async function searchLogs(req) {
12
+ const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/logs/search`;
13
+ return (0, http_1.postInnerApi)(url, req, {
14
+ errPrefix: "Failed to search logs",
15
+ defaultErrCode: DEFAULT_ERR_CODE,
16
+ });
17
+ }
18
+ // ── 链路 ──
19
+ async function searchTraces(req) {
20
+ const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/traces/search`;
21
+ return (0, http_1.postInnerApi)(url, req, {
22
+ errPrefix: "Failed to search traces",
23
+ defaultErrCode: DEFAULT_ERR_CODE,
24
+ });
25
+ }
26
+ async function getTraces(req) {
27
+ const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/traces/${encodeURIComponent(req.traceID)}`;
28
+ return (0, http_1.postInnerApi)(url, req, {
29
+ errPrefix: "Failed to get trace",
30
+ defaultErrCode: DEFAULT_ERR_CODE,
31
+ });
32
+ }
33
+ // ── 监控指标 ──
34
+ async function queryMetricsData(req) {
35
+ const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/metrics/data/query`;
36
+ return (0, http_1.postInnerApi)(url, req, {
37
+ errPrefix: "Failed to query metrics",
38
+ defaultErrCode: DEFAULT_ERR_CODE,
39
+ });
40
+ }
41
+ // ── 运营指标 ──
42
+ //
43
+ // 走 BAM v1.0.122 起的批量端点 CLIBatchQueryAnalyticsData:
44
+ // - 路径 /analytics/data/batch_query(旧版 /analytics/data/query 已废弃)
45
+ // - 支持 metricTypes 数组,单次拉多个指标
46
+ async function queryAnalyticsData(req) {
47
+ const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/analytics/data/batch_query`;
48
+ return (0, http_1.postInnerApi)(url, req, {
49
+ errPrefix: "Failed to query analytics",
50
+ defaultErrCode: DEFAULT_ERR_CODE,
51
+ });
52
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StatusCode = exports.SpanKind = exports.WordOperator = exports.spanSchema = exports.logItemSchema = exports.queryAnalyticsData = exports.queryMetricsData = exports.getTraces = exports.searchTraces = exports.searchLogs = void 0;
4
+ var api_1 = require("./api");
5
+ Object.defineProperty(exports, "searchLogs", { enumerable: true, get: function () { return api_1.searchLogs; } });
6
+ Object.defineProperty(exports, "searchTraces", { enumerable: true, get: function () { return api_1.searchTraces; } });
7
+ Object.defineProperty(exports, "getTraces", { enumerable: true, get: function () { return api_1.getTraces; } });
8
+ Object.defineProperty(exports, "queryMetricsData", { enumerable: true, get: function () { return api_1.queryMetricsData; } });
9
+ Object.defineProperty(exports, "queryAnalyticsData", { enumerable: true, get: function () { return api_1.queryAnalyticsData; } });
10
+ var schemas_1 = require("./schemas");
11
+ Object.defineProperty(exports, "logItemSchema", { enumerable: true, get: function () { return schemas_1.logItemSchema; } });
12
+ Object.defineProperty(exports, "spanSchema", { enumerable: true, get: function () { return schemas_1.spanSchema; } });
13
+ var types_1 = require("./types");
14
+ Object.defineProperty(exports, "WordOperator", { enumerable: true, get: function () { return types_1.WordOperator; } });
15
+ Object.defineProperty(exports, "SpanKind", { enumerable: true, get: function () { return types_1.SpanKind; } });
16
+ Object.defineProperty(exports, "StatusCode", { enumerable: true, get: function () { return types_1.StatusCode; } });
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.spanSchema = exports.logItemSchema = void 0;
4
+ const output_1 = require("../../utils/output");
5
+ /** LogItem 渲染契约(log search) */
6
+ exports.logItemSchema = {
7
+ columns: [
8
+ { key: "timestampNs", label: "time", format: output_1.fmt.ns("yyyy-MM-dd HH:mm:ss.SSS") },
9
+ { key: "severityText", label: "level" },
10
+ { key: "body" },
11
+ ],
12
+ strict: true,
13
+ };
14
+ /** Span 渲染契约(trace list / trace get 复用)。
15
+ * duration 是 client-derived(BAM 只给 start/end,没有原生 durationNs 字段)。 */
16
+ exports.spanSchema = {
17
+ columns: [
18
+ { key: "startTimeUnixNano", label: "start-time", format: output_1.fmt.ns("yyyy-MM-dd HH:mm:ss.SSS") },
19
+ { key: "name", label: "root-span" },
20
+ {
21
+ key: "user-id",
22
+ derive: (row) => {
23
+ return row.attributes?.user_id;
24
+ },
25
+ },
26
+ {
27
+ key: "duration",
28
+ label: "duration",
29
+ format: output_1.fmt.durationMs(),
30
+ derive: (row) => {
31
+ return row.attributes?.duration_ms;
32
+ },
33
+ },
34
+ { key: "traceID", label: "trace-id" },
35
+ ],
36
+ strict: true,
37
+ };
38
+ // metric / analytics 都改用 BAM v1.0.123 起的合并响应(points: {timestamp, values}),
39
+ // pretty 渲染走 handler 内 pivot 后的临时 schema;不再导出固定 schema。
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ // ── 通用结构 ──
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.StatusCode = exports.SpanKind = exports.WordOperator = void 0;
5
+ /** 模糊搜索词运算符(对应后端 WordOperator) */
6
+ var WordOperator;
7
+ (function (WordOperator) {
8
+ WordOperator[WordOperator["AND"] = 0] = "AND";
9
+ WordOperator[WordOperator["OR"] = 1] = "OR";
10
+ })(WordOperator || (exports.WordOperator = WordOperator = {}));
11
+ /** OpenTelemetry SpanKind(BAM 透传) */
12
+ var SpanKind;
13
+ (function (SpanKind) {
14
+ SpanKind[SpanKind["UNSPECIFIED"] = 0] = "UNSPECIFIED";
15
+ SpanKind[SpanKind["INTERNAL"] = 1] = "INTERNAL";
16
+ SpanKind[SpanKind["SERVER"] = 2] = "SERVER";
17
+ SpanKind[SpanKind["CLIENT"] = 3] = "CLIENT";
18
+ SpanKind[SpanKind["PRODUCER"] = 4] = "PRODUCER";
19
+ SpanKind[SpanKind["CONSUMER"] = 5] = "CONSUMER";
20
+ })(SpanKind || (exports.SpanKind = SpanKind = {}));
21
+ /** Span 状态码(BAM 透传) */
22
+ var StatusCode;
23
+ (function (StatusCode) {
24
+ StatusCode[StatusCode["UNSET"] = 0] = "UNSET";
25
+ StatusCode[StatusCode["OK"] = 1] = "OK";
26
+ StatusCode[StatusCode["ERROR"] = 2] = "ERROR";
27
+ })(StatusCode || (exports.StatusCode = StatusCode = {}));
@@ -38,7 +38,9 @@ async function getPluginVersion(pluginKey, requestedVersion) {
38
38
  const versions = await getPluginVersions([pluginKey], isLatest);
39
39
  const pluginVersions = versions[pluginKey];
40
40
  if (!pluginVersions || pluginVersions.length === 0) {
41
- throw new error_1.AppError("PLUGIN_NOT_FOUND", `Plugin not found: ${pluginKey}`, { next_actions: ["检查包名拼写,或确认该插件已在插件市场发布"] });
41
+ throw new error_1.AppError("PLUGIN_NOT_FOUND", `Plugin not found: ${pluginKey}`, {
42
+ next_actions: ["检查包名拼写,或确认该插件已在插件市场发布"],
43
+ });
42
44
  }
43
45
  if (isLatest) {
44
46
  return pluginVersions[0];
@@ -53,7 +55,9 @@ async function getPluginVersion(pluginKey, requestedVersion) {
53
55
  function parsePluginKey(key) {
54
56
  const match = /^(@[^/]+)\/(.+)$/.exec(key);
55
57
  if (!match) {
56
- throw new error_1.AppError("INVALID_PLUGIN_KEY", `Invalid plugin key format: ${key}`, { next_actions: ["插件 key 必须形如 @scope/name"] });
58
+ throw new error_1.AppError("INVALID_PLUGIN_KEY", `Invalid plugin key format: ${key}`, {
59
+ next_actions: ["插件 key 必须形如 @scope/name"],
60
+ });
57
61
  }
58
62
  return { scope: match[1], name: match[2] };
59
63
  }
@@ -91,7 +95,8 @@ async function withRetry(operation, description, maxRetries = MAX_RETRIES) {
91
95
  }
92
96
  }
93
97
  }
94
- throw lastError ?? new error_1.AppError("INTERNAL_RETRY_EXHAUSTED", `${description} failed after ${String(maxRetries)} retries`, { retryable: true, next_actions: ["检查网络后重试,--verbose 可查看重试日志"] });
98
+ throw (lastError ??
99
+ new error_1.AppError("INTERNAL_RETRY_EXHAUSTED", `${description} failed after ${String(maxRetries)} retries`, { retryable: true, next_actions: ["检查网络后重试,--verbose 可查看重试日志"] }));
95
100
  }
96
101
  /** 插件缓存目录 */
97
102
  const PLUGIN_CACHE_DIR = "node_modules/.cache/miaoda-cli/plugins";
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAppCommands = registerAppCommands;
4
+ const shared_1 = require("../../../cli/commands/shared");
5
+ const index_1 = require("../../../cli/handlers/app/index");
6
+ function registerAppCommands(program) {
7
+ const appCmd = program.command("app").description("应用元数据管理:查看 / 修改");
8
+ appCmd.action(() => {
9
+ appCmd.outputHelp();
10
+ });
11
+ appCmd.addHelpText("after", `
12
+ 作用范围
13
+ 仅供 Agent 在妙搭沙箱内调用。
14
+ 应用上下文:沙箱内的默认应用。
15
+ `);
16
+ registerAppGet(appCmd);
17
+ registerAppUpdate(appCmd);
18
+ }
19
+ function registerAppGet(parent) {
20
+ const cmd = parent
21
+ .command("get")
22
+ .description("查看应用元数据(status / type / mode / branch / 时间戳 等)")
23
+ .addOption((0, shared_1.appIdOption)().hideHelp())
24
+ .addHelpText("after", `
25
+ JSON 输出
26
+ {"data": {"appID": "...", "name": "...", ...}}
27
+
28
+ 示例
29
+ $ miaoda app get
30
+ $ miaoda app get --json
31
+ `);
32
+ cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
33
+ (0, shared_1.rejectCliOverride)(cmd, "appId");
34
+ await (0, index_1.handleAppGet)({
35
+ appId: rawOpts.appId,
36
+ });
37
+ }));
38
+ }
39
+ function registerAppUpdate(parent) {
40
+ const cmd = parent
41
+ .command("update")
42
+ .description("修改应用名称 / 描述(至少传 --name 或 --description)")
43
+ .addOption((0, shared_1.appIdOption)().hideHelp())
44
+ .option("--name <name>", "新应用名称")
45
+ .option("--description <desc>", "新应用描述")
46
+ .addHelpText("after", `
47
+ JSON 输出
48
+ 与 'app get' 一致:{"data": {...新 meta...}, "next_cursor": null, "has_more": false}
49
+
50
+ 示例
51
+ $ miaoda app update --name my-store
52
+ $ miaoda app update --description "在线商城应用"
53
+ `);
54
+ cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
55
+ (0, shared_1.rejectCliOverride)(cmd, "appId");
56
+ await (0, index_1.handleAppUpdate)({
57
+ appId: rawOpts.appId,
58
+ name: rawOpts.name,
59
+ description: rawOpts.description,
60
+ });
61
+ }));
62
+ }
@@ -179,6 +179,7 @@ Examples:
179
179
  .option("--format <fmt>", "导出格式 csv / json / sql,默认 csv(sql 输出 INSERT 语句,可用 db sql < file.sql 回放)")
180
180
  .option("-f, --file <path>", "输出文件路径,默认 <表名>.<格式>")
181
181
  .option("--limit <n>", "最多导出行数(不超过 5000)")
182
+ .option("--force", "输出文件已存在时覆盖(默认报错)")
182
183
  .action(async function (table) {
183
184
  await (0, index_1.handleDbDataExport)(table, this.optsWithGlobals());
184
185
  })