@lark-apaas/miaoda-cli 0.1.39-alpha.7cb46f9 → 0.1.39-alpha.9cc8d31

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.
@@ -50,9 +50,11 @@ async function getErrorLog(req) {
50
50
  * GET /v1/pipeline/app/:appID/instance/:instanceID/detail — 轮询发布状态
51
51
  *
52
52
  * 走 lark.apaas.devops_platform PSM;URL 前缀不同但共用同一管理端 gateway。
53
+ * 显式带 withTransformApprovalData=true 取审批展示视图(job 上挂 approval);IDL 默认 false,
54
+ * 不带时拿不到审批链接与拒绝原因。该参数只改返回投影,无副作用。
53
55
  */
54
56
  async function queryPipelineInstance(req) {
55
- const url = `/v1/pipeline/app/${encodeURIComponent(req.appID)}/instance/${encodeURIComponent(req.instanceID)}/detail`;
57
+ const url = `/v1/pipeline/app/${encodeURIComponent(req.appID)}/instance/${encodeURIComponent(req.instanceID)}/detail?withTransformApprovalData=true`;
56
58
  return (0, http_1.getInnerApi)(url, {
57
59
  errPrefix: 'Failed to query pipeline',
58
60
  defaultErrCode: DEFAULT_ERR_CODE,
@@ -49,7 +49,7 @@ function* jobsOf(stages) {
49
49
  }
50
50
  }
51
51
  /**
52
- * 从 CliQueryPipelineInstance 的 PipelineInstance(接口默认返回审批展示视图)中摘出需要输出的卡点:
52
+ * 从 CliQueryPipelineInstance 的 PipelineInstance(请求带 withTransformApprovalData=true,返回审批展示视图)中摘出需要输出的卡点:
53
53
  * 第一个 status 为 PENDING / REJECTED / FAILED 的 Job.approval。一次发布可以被拦多次
54
54
  * (build / db_check / deploy),已通过的卡点可能排在前面,不能取它;流水线串行,需要输出的至多一个。
55
55
  * 与妙搭发布面板 findApprovalJob 同一口径。
@@ -95,6 +95,7 @@ JSON 输出
95
95
  {"data": {"ID": ..., "status": ..., "creator": "...", "createdAt": ..., "updatedAt": ..., ...}, "next_cursor": null, "has_more": false}
96
96
  ID 即 deploy-id(== pipelineTaskID)
97
97
  data.status 为可读字符串;data.status_code 为 BAM 原始数字枚举。
98
+ status 为 pending / rejected / failed 且存在审批单时,额外包含 approval(如 status、approvalURL、被拒时的 message)。
98
99
 
99
100
  示例
100
101
  $ miaoda deploy get 12345
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.handleDeploy = handleDeploy;
37
+ exports.writeApprovalNotice = writeApprovalNotice;
37
38
  const api = __importStar(require("../../../api/index"));
38
39
  const output_1 = require("../../../utils/output");
39
40
  const index_1 = require("../../../api/deploy/index");
@@ -72,23 +73,14 @@ async function handleDeploy(opts) {
72
73
  timeoutSec: opts.timeout ?? 300,
73
74
  });
74
75
  const approval = (0, index_1.findApproval)(detail);
75
- if (!(0, output_1.isJsonMode)()) {
76
- if (detail.status === index_1.NodeStatus.PENDING) {
77
- writeApprovalHint(approval, pipelineTaskID);
78
- }
79
- else if (detail.status === index_1.NodeStatus.SUCCESS) {
76
+ if (!(0, output_1.isJsonMode)() && !writeApprovalNotice(detail.status, approval, pipelineTaskID)) {
77
+ if (detail.status === index_1.NodeStatus.SUCCESS) {
80
78
  process.stdout.write('✓ 发布成功\n');
81
79
  }
82
- else if (detail.status === index_1.NodeStatus.FAILED && approval?.status === 'failed') {
83
- writeApprovalGateFailed(approval);
84
- }
85
80
  else if (detail.status === index_1.NodeStatus.FAILED) {
86
81
  process.stdout.write('✗ 发布失败\n');
87
82
  process.stdout.write(` hint: Run \`miaoda deploy error-log ${pipelineTaskID}\` to view error logs\n`);
88
83
  }
89
- else if (detail.status === index_1.NodeStatus.REJECTED) {
90
- writeRejected(approval);
91
- }
92
84
  else {
93
85
  process.stdout.write('• 发布已取消\n');
94
86
  }
@@ -118,6 +110,25 @@ function writeRejected(approval) {
118
110
  process.stdout.write('✗ 发布审批未通过,本次未上线\n');
119
111
  writeReason(approval);
120
112
  }
113
+ /**
114
+ * 审批相关状态的 pretty 提示:审批中 / 审批被拒 / 卡点异常。
115
+ * 不属于这三类时不输出并返回 false,由调用方按普通状态处理。
116
+ */
117
+ function writeApprovalNotice(status, approval, pipelineTaskID) {
118
+ if (status === index_1.NodeStatus.PENDING) {
119
+ writeApprovalHint(approval, pipelineTaskID);
120
+ }
121
+ else if (status === index_1.NodeStatus.REJECTED) {
122
+ writeRejected(approval);
123
+ }
124
+ else if (status === index_1.NodeStatus.FAILED && approval?.status === 'failed') {
125
+ writeApprovalGateFailed(approval);
126
+ }
127
+ else {
128
+ return false;
129
+ }
130
+ return true;
131
+ }
121
132
  function writeApprovalHint(approval, pipelineTaskID) {
122
133
  const url = (0, index_1.approvalURL)(approval);
123
134
  process.stdout.write('⏸ 发布已进入人工审批,等待审批负责人处理\n');
@@ -41,16 +41,28 @@ const error_1 = require("../../../utils/error");
41
41
  const index_1 = require("../../../api/deploy/index");
42
42
  const helpers_1 = require("./helpers");
43
43
  const format_1 = require("./format");
44
+ const polling_1 = require("./polling");
45
+ const deploy_1 = require("./deploy");
44
46
  /**
45
47
  * 单页拉取上限:BAM listPipelineInstances 限制 1~500,取 500 让 deploy get
46
48
  * 在最近 500 条记录里按 ID 命中。
47
49
  */
48
50
  const DEPLOY_GET_LOOKUP_LIMIT = 500;
51
+ /**
52
+ * 可能挂着审批单的状态:审批中、审批被拒,以及卡点异常(平台没能发起审批,顶层表现为 FAILED)。
53
+ * 只有这些状态才额外查详情取审批单,其余状态保持单次列表请求。
54
+ */
55
+ const APPROVAL_LOOKUP_STATUSES = [
56
+ index_1.NodeStatus.PENDING,
57
+ index_1.NodeStatus.REJECTED,
58
+ index_1.NodeStatus.FAILED,
59
+ ];
49
60
  /**
50
61
  * miaoda deploy get <deploy-id>
51
62
  *
52
63
  * BAM 没有 by-ID 的查询接口,复用 listPipelineInstances 在最近窗口里按 ID 筛一条。
53
64
  * 命中失败时抛 DEPLOY_NOT_FOUND,提示用户用 history 查窗口。
65
+ * status 为 pending / rejected / failed 时额外查详情,有审批单则输出 approval(审批链接、拒绝原因等)。
54
66
  */
55
67
  async function handleDeployGet(opts) {
56
68
  if (!opts.deployId)
@@ -67,5 +79,33 @@ async function handleDeployGet(opts) {
67
79
  next_actions: ['运行 `miaoda deploy history` 确认 deploy-id 是否在最近窗口内'],
68
80
  });
69
81
  }
70
- (0, output_1.emit)({ data: (0, format_1.formatSimpleInstance)(instance), next_cursor: null, has_more: false }, index_1.deployGetSchema);
82
+ const approval = APPROVAL_LOOKUP_STATUSES.includes(instance.status)
83
+ ? await lookupApproval(appID, opts.deployId, instance.status)
84
+ : undefined;
85
+ (0, output_1.emit)({
86
+ data: { ...(0, format_1.formatSimpleInstance)(instance), ...(approval && { approval }) },
87
+ next_cursor: null,
88
+ has_more: false,
89
+ }, index_1.deployGetSchema);
90
+ if (!(0, output_1.isJsonMode)())
91
+ (0, deploy_1.writeApprovalNotice)(instance.status, approval, opts.deployId);
92
+ }
93
+ /**
94
+ * 列表项不含审批信息:按同一个 ID 查详情接口(审批展示视图)取审批单。
95
+ *
96
+ * pending / rejected 时详情失败直接抛错:丢掉审批链接 / 拒绝原因却无人察觉,比报错更糟。
97
+ * failed 时降级为只输出列表数据并在 stderr 提示:普通构建失败远多于卡点异常,不能因为详情接口
98
+ * 出错就让调用方连 status 都拿不到,卡住失败排查。
99
+ */
100
+ async function lookupApproval(appID, deployId, status) {
101
+ try {
102
+ return (0, index_1.findApproval)((0, polling_1.normalizePipelineInstance)(await api.deploy.queryPipelineInstance({ appID, instanceID: deployId })));
103
+ }
104
+ catch (err) {
105
+ if (status !== index_1.NodeStatus.FAILED)
106
+ throw err;
107
+ const reason = err instanceof Error ? err.message : String(err);
108
+ process.stderr.write(`warning: 查询审批单失败,输出不含 approval:${reason}\n`);
109
+ return undefined;
110
+ }
71
111
  }
@@ -43,18 +43,12 @@ function parseDeployId(text) {
43
43
  /** 发布理由上限,按 Unicode code point 计,对齐 lark-cli `apps +release-create` */
44
44
  const MAX_APPLY_REASON_CODE_POINTS = 1000;
45
45
  /** C0 / C1 控制字符(含换行、制表符) */
46
- function isControlCodePoint(cp) {
47
- return cp <= 0x1f || (cp >= 0x7f && cp <= 0x9f);
48
- }
49
- /** 零宽、BOM、双向嵌入/覆盖/隔离、行/段分隔符:肉眼不可见,却会改变审批单里的展示 */
50
- function isDangerousCodePoint(cp) {
51
- return ((cp >= 0x200b && cp <= 0x200d) ||
52
- cp === 0xfeff ||
53
- (cp >= 0x202a && cp <= 0x202e) ||
54
- (cp >= 0x2066 && cp <= 0x2069) ||
55
- cp === 0x2028 ||
56
- cp === 0x2029);
57
- }
46
+ const CONTROL_CHARS = /\p{Cc}/u;
47
+ /**
48
+ * 不可见格式字符(零宽、BOM、双向控制、词连接符等)与行/段分隔符:肉眼不可见,却会改变审批单里的展示。
49
+ * 与 deploy 输出服务端文本时的过滤口径一致。
50
+ */
51
+ const INVISIBLE_CHARS = /[\p{Cf}\u2028\u2029]/u;
58
52
  /**
59
53
  * 校验 --apply-reason 的格式;通过后调用方原样透传,不 trim。
60
54
  * 是否必填由服务端按应用类型判定,这里不管。
@@ -62,14 +56,13 @@ function isDangerousCodePoint(cp) {
62
56
  function validateApplyReason(value) {
63
57
  if (value.trim() === '')
64
58
  (0, args_1.failArgs)('--apply-reason 不能为空');
65
- const codePoints = Array.from(value, (ch) => ch.codePointAt(0) ?? 0);
66
- if (codePoints.some(isControlCodePoint)) {
59
+ if (CONTROL_CHARS.test(value)) {
67
60
  (0, args_1.failArgs)('--apply-reason 不能包含换行、制表符等控制字符,需为单行文本');
68
61
  }
69
- if (codePoints.some(isDangerousCodePoint)) {
62
+ if (INVISIBLE_CHARS.test(value)) {
70
63
  (0, args_1.failArgs)('--apply-reason 不能包含零宽字符、双向控制字符或行/段分隔符');
71
64
  }
72
- if (codePoints.length > MAX_APPLY_REASON_CODE_POINTS) {
65
+ if (Array.from(value).length > MAX_APPLY_REASON_CODE_POINTS) {
73
66
  (0, args_1.failArgs)(`--apply-reason 最多 ${String(MAX_APPLY_REASON_CODE_POINTS)} 个字符`);
74
67
  }
75
68
  }
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.DEFAULT_POLL_INTERVAL_MS = void 0;
40
40
  exports.waitForPipeline = waitForPipeline;
41
+ exports.normalizePipelineInstance = normalizePipelineInstance;
41
42
  const ora_1 = __importDefault(require("ora"));
42
43
  const api = __importStar(require("../../../api/index"));
43
44
  const error_1 = require("../../../utils/error");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.39-alpha.7cb46f9",
3
+ "version": "0.1.39-alpha.9cc8d31",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {