@lark-apaas/miaoda-cli 0.1.37 → 0.1.39-alpha.24bc5ff
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/README.md +10 -0
- package/dist/api/deploy/approval.js +77 -0
- package/dist/api/deploy/index.js +7 -1
- package/dist/api/deploy/schemas.js +3 -0
- package/dist/api/deploy/types.js +19 -1
- package/dist/cli/commands/deploy/index.js +30 -6
- package/dist/cli/commands/deploy/modern.js +34 -11
- package/dist/cli/commands/skills/index.js +1 -1
- package/dist/cli/handlers/app/export.js +3 -3
- package/dist/cli/handlers/app/migrate.js +4 -1
- package/dist/cli/handlers/app/sync.js +2 -2
- package/dist/cli/handlers/db/sql.js +8 -6
- package/dist/cli/handlers/deploy/deploy.js +57 -4
- package/dist/cli/handlers/deploy/format.js +13 -0
- package/dist/cli/handlers/deploy/get.js +24 -1
- package/dist/cli/handlers/deploy/helpers.js +36 -2
- package/dist/cli/handlers/deploy/modern.js +39 -3
- package/dist/cli/handlers/deploy/polling.js +13 -9
- package/dist/cli/handlers/plugin/plugin-local.js +5 -4
- package/dist/config/migrate-configs/vite-react-to-nestjs-react-fullstack.js +13 -2
- package/dist/config/sync-configs/index.js +3 -1
- package/dist/config/sync-configs/nestjs-react-fullstack.js +6 -0
- package/dist/config/sync-configs/vite-react.js +20 -0
- package/dist/services/app/init/async-install.js +13 -2
- package/dist/services/app/init/install.js +4 -3
- package/dist/services/deploy/modern/atoms/build.js +3 -3
- package/dist/services/deploy/modern/atoms/local-release.js +3 -0
- package/dist/services/deploy/modern/atoms/tosutil.js +6 -8
- package/dist/services/deploy/modern/patch/actions.js +7 -1
- package/dist/services/deploy/modern/patch/index.js +2 -1
- package/dist/services/deploy/modern/pipelines/design-local.js +7 -1
- package/dist/services/deploy/modern/pipelines/local.js +7 -1
- package/dist/utils/archive.js +103 -0
- package/dist/utils/coding-steering.js +30 -2
- package/dist/utils/file-ops.js +28 -8
- package/dist/utils/http.js +6 -6
- package/dist/utils/npm-pack.js +6 -7
- package/dist/utils/run-npm.js +102 -0
- package/dist/utils/sync-rule.js +6 -0
- package/dist/utils/typescript-exclude.js +36 -0
- package/dist/utils/which.js +27 -0
- package/package.json +6 -2
- package/upgrade/templates/nestjs-react-fullstack/templates/nest-cli.json +1 -10
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/build.sh +5 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/prune-smart.js +41 -1
- package/upgrade/templates/vite-react/templates/scripts/build.sh +61 -0
package/README.md
CHANGED
|
@@ -52,6 +52,16 @@ miaoda file ls --output json
|
|
|
52
52
|
|
|
53
53
|
完整命令通过 `miaoda --help` 或 `miaoda <domain> --help` 查看。
|
|
54
54
|
|
|
55
|
+
## 全栈应用的 MCP UI 检查
|
|
56
|
+
|
|
57
|
+
创建 MCP UI 时,Agent 在 `server/mcp/ui` 中同时生成 `tsconfig.json` 和 `eslint.config.cjs`,修改 UI 或其 shared 依赖后主动运行独立检查。模板和 CLI 不生成 UI 配置,也不将 UI 检查接入主工程 lint/type:check。
|
|
58
|
+
|
|
59
|
+
主工程保留原 client/server 检查。Agent 从工程根执行 `npx --no-install tsc --noEmit --project server/mcp/ui/tsconfig.json`,再执行 `npx --no-install eslint --config server/mcp/ui/eslint.config.cjs "server/mcp/ui/**/*.{ts,tsx,js,jsx}" --max-warnings 0`;必须检查退出码并修复错误。无需辅助脚本或新增 npm 命令。
|
|
60
|
+
|
|
61
|
+
CLI 不注入 MCP UI 检查命令;主工程保留原有 client/server 检查,UI 由 Agent 按独立配置执行标准 tsc/eslint。
|
|
62
|
+
|
|
63
|
+
存量 sync 不改写 ESLint 源码;UI 全局排除项由平台 ESLint 预设提供。TS 仅向已有显式 `exclude` 数组追加目录,保留注释和原项;依赖继承且未显式声明 `exclude` 的自定义配置保持不变并提示人工调整。自定义检查命令保留。lint.js 和 build.sh 仍由平台脚本模板维护,不依赖预设包中的脚本执行入口。
|
|
64
|
+
|
|
55
65
|
## 打包可本地访问的静态产物
|
|
56
66
|
|
|
57
67
|
`miaoda app pack`(默认 `--mode standalone`)把当前应用构建成一份自包含静态产物,可脱离妙搭平台本地打开。
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TERMINAL_NODE_STATUSES = void 0;
|
|
4
|
+
exports.approvalStatusText = approvalStatusText;
|
|
5
|
+
exports.findApproval = findApproval;
|
|
6
|
+
exports.approvalURL = approvalURL;
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
/** 流水线终态;REJECTED 为卡点审批被拒 */
|
|
9
|
+
exports.TERMINAL_NODE_STATUSES = [
|
|
10
|
+
types_1.NodeStatus.SUCCESS,
|
|
11
|
+
types_1.NodeStatus.FAILED,
|
|
12
|
+
types_1.NodeStatus.CANCELED,
|
|
13
|
+
types_1.NodeStatus.REJECTED,
|
|
14
|
+
];
|
|
15
|
+
const APPROVAL_STATUS_TEXT = {
|
|
16
|
+
[types_1.ApprovalStatus.UNSPECIFIED]: 'unknown',
|
|
17
|
+
[types_1.ApprovalStatus.PENDING]: 'pending',
|
|
18
|
+
[types_1.ApprovalStatus.APPROVED]: 'approved',
|
|
19
|
+
[types_1.ApprovalStatus.REJECTED]: 'rejected',
|
|
20
|
+
[types_1.ApprovalStatus.CANCELED]: 'canceled',
|
|
21
|
+
[types_1.ApprovalStatus.FAILED]: 'failed',
|
|
22
|
+
};
|
|
23
|
+
/** 仍需用户知道点什么的卡点:在等谁、为什么被拒、平台没把审批发出去;已通过 / 已撤回不再展示 */
|
|
24
|
+
const SURFACED_APPROVAL_STATUSES = [
|
|
25
|
+
types_1.ApprovalStatus.PENDING,
|
|
26
|
+
types_1.ApprovalStatus.REJECTED,
|
|
27
|
+
types_1.ApprovalStatus.FAILED,
|
|
28
|
+
];
|
|
29
|
+
/** 卡点审批状态数字 → 可读字符串;未知值为 unknown(n) */
|
|
30
|
+
function approvalStatusText(v) {
|
|
31
|
+
return APPROVAL_STATUS_TEXT[Number(v)] ?? `unknown(${String(v)})`;
|
|
32
|
+
}
|
|
33
|
+
function isRecord(v) {
|
|
34
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
35
|
+
}
|
|
36
|
+
function* jobsOf(stages) {
|
|
37
|
+
if (!Array.isArray(stages))
|
|
38
|
+
return;
|
|
39
|
+
for (const stage of stages) {
|
|
40
|
+
if (!isRecord(stage) || !Array.isArray(stage.jobs))
|
|
41
|
+
continue;
|
|
42
|
+
for (const group of stage.jobs) {
|
|
43
|
+
if (!Array.isArray(group))
|
|
44
|
+
continue;
|
|
45
|
+
for (const job of group)
|
|
46
|
+
if (isRecord(job))
|
|
47
|
+
yield job;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 从 CliQueryPipelineInstance 的 PipelineInstance(接口默认返回审批展示视图)中摘出需要输出的卡点:
|
|
53
|
+
* 第一个 status 为 PENDING / REJECTED / FAILED 的 Job.approval。一次发布可以被拦多次
|
|
54
|
+
* (build / db_check / deploy),已通过的卡点可能排在前面,不能取它;流水线串行,需要输出的至多一个。
|
|
55
|
+
* 与妙搭发布面板 findApprovalJob 同一口径。
|
|
56
|
+
*
|
|
57
|
+
* 原样透传,status 转为可读字符串,原始数字保留在 status_code(与 detail 各层 status 的约定一致)。
|
|
58
|
+
* 是否在审批中由顶层 status(PENDING)表达,这里只负责摘出审批链接、拒绝原因等上下文。
|
|
59
|
+
*/
|
|
60
|
+
function findApproval(instance) {
|
|
61
|
+
for (const job of jobsOf(instance.stages)) {
|
|
62
|
+
const approval = job.approval;
|
|
63
|
+
if (!isRecord(approval) || !SURFACED_APPROVAL_STATUSES.includes(approval.status))
|
|
64
|
+
continue;
|
|
65
|
+
return {
|
|
66
|
+
...approval,
|
|
67
|
+
status: approvalStatusText(approval.status),
|
|
68
|
+
status_code: approval.status,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
/** 取审批链接;缺失或不是非空字符串时返回 undefined。是否可信由调用方(Skill)判断。 */
|
|
74
|
+
function approvalURL(approval) {
|
|
75
|
+
const url = approval?.approvalURL;
|
|
76
|
+
return typeof url === 'string' && url !== '' ? url : undefined;
|
|
77
|
+
}
|
package/dist/api/deploy/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NodeStatus = exports.nodeStatusFromText = exports.nodeStatusText = exports.errorJobSchema = exports.deployGetSchema = exports.deployHistorySchema = exports.batchSavePluginInstances = exports.TosActionType = exports.LocalReleaseStatus = exports.applyTosDiff = exports.getModernLastPublishedVersion = exports.getModernReleaseStatus = exports.createModernRelease = exports.callbackStatic = exports.updateLocalRelease = exports.createLocalRelease = exports.preRelease = exports.queryPipelineInstance = exports.getErrorLog = exports.listPipelineInstances = exports.createRelease = void 0;
|
|
3
|
+
exports.TERMINAL_NODE_STATUSES = exports.approvalStatusText = exports.approvalURL = exports.findApproval = exports.ApprovalStatus = exports.NodeStatus = exports.nodeStatusFromText = exports.nodeStatusText = exports.errorJobSchema = exports.deployGetSchema = exports.deployHistorySchema = exports.batchSavePluginInstances = exports.TosActionType = exports.LocalReleaseStatus = exports.applyTosDiff = exports.getModernLastPublishedVersion = exports.getModernReleaseStatus = exports.createModernRelease = exports.callbackStatic = exports.updateLocalRelease = exports.createLocalRelease = exports.preRelease = exports.queryPipelineInstance = exports.getErrorLog = exports.listPipelineInstances = exports.createRelease = void 0;
|
|
4
4
|
var api_1 = require("./api");
|
|
5
5
|
Object.defineProperty(exports, "createRelease", { enumerable: true, get: function () { return api_1.createRelease; } });
|
|
6
6
|
Object.defineProperty(exports, "listPipelineInstances", { enumerable: true, get: function () { return api_1.listPipelineInstances; } });
|
|
@@ -28,3 +28,9 @@ Object.defineProperty(exports, "nodeStatusText", { enumerable: true, get: functi
|
|
|
28
28
|
Object.defineProperty(exports, "nodeStatusFromText", { enumerable: true, get: function () { return schemas_1.nodeStatusFromText; } });
|
|
29
29
|
var types_1 = require("./types");
|
|
30
30
|
Object.defineProperty(exports, "NodeStatus", { enumerable: true, get: function () { return types_1.NodeStatus; } });
|
|
31
|
+
Object.defineProperty(exports, "ApprovalStatus", { enumerable: true, get: function () { return types_1.ApprovalStatus; } });
|
|
32
|
+
var approval_1 = require("./approval");
|
|
33
|
+
Object.defineProperty(exports, "findApproval", { enumerable: true, get: function () { return approval_1.findApproval; } });
|
|
34
|
+
Object.defineProperty(exports, "approvalURL", { enumerable: true, get: function () { return approval_1.approvalURL; } });
|
|
35
|
+
Object.defineProperty(exports, "approvalStatusText", { enumerable: true, get: function () { return approval_1.approvalStatusText; } });
|
|
36
|
+
Object.defineProperty(exports, "TERMINAL_NODE_STATUSES", { enumerable: true, get: function () { return approval_1.TERMINAL_NODE_STATUSES; } });
|
|
@@ -13,6 +13,9 @@ const NODE_STATUS_TEXT = {
|
|
|
13
13
|
[types_1.NodeStatus.FAILED]: 'failed',
|
|
14
14
|
[types_1.NodeStatus.CANCELED]: 'canceled',
|
|
15
15
|
[types_1.NodeStatus.HOLD_ON]: 'hold_on',
|
|
16
|
+
[types_1.NodeStatus.SKIPPED]: 'skipped',
|
|
17
|
+
[types_1.NodeStatus.PENDING]: 'pending',
|
|
18
|
+
[types_1.NodeStatus.REJECTED]: 'rejected',
|
|
16
19
|
};
|
|
17
20
|
function nodeStatusText(v) {
|
|
18
21
|
if (typeof v === 'string')
|
package/dist/api/deploy/types.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// 发布单的唯一标识是 pipelineTaskID,对外即 deploy-id;error-log 等接口直接用它做
|
|
9
9
|
// URL 路径段(instanceID)。曾经的 Release/releaseID 概念已全面废弃。
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.NodeStatus = void 0;
|
|
11
|
+
exports.ApprovalStatus = exports.NodeStatus = void 0;
|
|
12
12
|
/** Pipeline 节点状态(devops_platform) */
|
|
13
13
|
var NodeStatus;
|
|
14
14
|
(function (NodeStatus) {
|
|
@@ -18,5 +18,23 @@ var NodeStatus;
|
|
|
18
18
|
NodeStatus[NodeStatus["SUCCESS"] = 3] = "SUCCESS";
|
|
19
19
|
NodeStatus[NodeStatus["FAILED"] = 4] = "FAILED";
|
|
20
20
|
NodeStatus[NodeStatus["CANCELED"] = 5] = "CANCELED";
|
|
21
|
+
/** 已废弃:服务端不再产出,保留仅为兼容历史数据 */
|
|
21
22
|
NodeStatus[NodeStatus["HOLD_ON"] = 6] = "HOLD_ON";
|
|
23
|
+
/** 流水线内部中间态 */
|
|
24
|
+
NodeStatus[NodeStatus["SKIPPED"] = 7] = "SKIPPED";
|
|
25
|
+
NodeStatus[NodeStatus["PENDING"] = 8] = "PENDING";
|
|
26
|
+
/** 卡点审批被拒(人工拒绝 / 超时自动拒绝) */
|
|
27
|
+
NodeStatus[NodeStatus["REJECTED"] = 9] = "REJECTED";
|
|
22
28
|
})(NodeStatus || (exports.NodeStatus = NodeStatus = {}));
|
|
29
|
+
// ── 发布审批(卡点)信息 ────────────
|
|
30
|
+
/** 卡点审批状态(devops_platform ApprovalStatus) */
|
|
31
|
+
var ApprovalStatus;
|
|
32
|
+
(function (ApprovalStatus) {
|
|
33
|
+
ApprovalStatus[ApprovalStatus["UNSPECIFIED"] = 0] = "UNSPECIFIED";
|
|
34
|
+
ApprovalStatus[ApprovalStatus["PENDING"] = 1] = "PENDING";
|
|
35
|
+
ApprovalStatus[ApprovalStatus["APPROVED"] = 2] = "APPROVED";
|
|
36
|
+
ApprovalStatus[ApprovalStatus["REJECTED"] = 3] = "REJECTED";
|
|
37
|
+
ApprovalStatus[ApprovalStatus["CANCELED"] = 4] = "CANCELED";
|
|
38
|
+
/** 卡点异常:发起审批失败等平台侧错误 */
|
|
39
|
+
ApprovalStatus[ApprovalStatus["FAILED"] = 5] = "FAILED";
|
|
40
|
+
})(ApprovalStatus || (exports.ApprovalStatus = ApprovalStatus = {}));
|
|
@@ -4,6 +4,18 @@ exports.registerDeployCommands = registerDeployCommands;
|
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
5
|
const shared_1 = require("../../../cli/commands/shared");
|
|
6
6
|
const index_1 = require("../../../cli/handlers/deploy/index");
|
|
7
|
+
/** deploy history --status 可选值(pipeline 节点状态);hold_on 已被服务端废弃,仅为兼容保留 */
|
|
8
|
+
const HISTORY_STATUS_CHOICES = [
|
|
9
|
+
'todo',
|
|
10
|
+
'running',
|
|
11
|
+
'success',
|
|
12
|
+
'failed',
|
|
13
|
+
'canceled',
|
|
14
|
+
'rejected',
|
|
15
|
+
'pending',
|
|
16
|
+
'skipped',
|
|
17
|
+
'hold_on',
|
|
18
|
+
];
|
|
7
19
|
const COMMON_TIME_HELP = `
|
|
8
20
|
时间格式:
|
|
9
21
|
- 相对时间:30m(30 分钟前)、1h、2d、1w
|
|
@@ -21,8 +33,9 @@ function registerDeployCommands(program) {
|
|
|
21
33
|
.description('触发发布;支持 --wait 阻塞到终态。子命令:get / history / error-log')
|
|
22
34
|
.addOption((0, shared_1.appIdOption)().hideHelp())
|
|
23
35
|
.addOption((0, shared_1.branchOption)().hideHelp())
|
|
24
|
-
.option('--wait', '
|
|
36
|
+
.option('--wait', '阻塞直到流水线终态或进入人工审批', false)
|
|
25
37
|
.option('--timeout <sec>', '--wait 最长等待秒数(默认 300)', parseTimeout, 300)
|
|
38
|
+
.option('--apply-reason <text>', '发布申请理由,写入审批单(单行,最多 1000 字符;是否必填由服务端按应用类型判定)')
|
|
26
39
|
.addHelpText('after', `
|
|
27
40
|
不要用异步模式或后台模式调用 deploy(例如 &、nohup、后台 task、detached run,或任何会让命令提前返回的包装方式),否则调用可能提前结束,Agent 会误判发布已完成。
|
|
28
41
|
部署前置检查(Agent 必须执行)
|
|
@@ -31,20 +44,29 @@ function registerDeployCommands(program) {
|
|
|
31
44
|
- 是本次任务的产物 → 先 commit + push 再 deploy
|
|
32
45
|
- 不能确定意图 → 用 AskUser 跟用户确认(不要默默继续)
|
|
33
46
|
|
|
47
|
+
发布理由与审批
|
|
48
|
+
需要审批的应用发布时须带 --apply-reason(是否必填由服务端判定,缺失时服务端会报错)。
|
|
49
|
+
理由为单行文本,最多 1000 字符,不能包含控制字符、零宽字符与双向控制字符。
|
|
50
|
+
发布进入人工审批(status=pending)后 --wait 会立即返回,退出码为 0,不会轮询到超时。
|
|
51
|
+
|
|
34
52
|
JSON 输出
|
|
35
53
|
不带 --wait:{"data": {"pipelineTaskID": "..."}}(pipelineTaskID 即 deploy-id)
|
|
36
|
-
带 --wait:data 额外包含 detail(pipeline
|
|
54
|
+
带 --wait:data 额外包含 detail(pipeline 当前状态);
|
|
55
|
+
有卡点信息时还包含 approval(如 status、approvalURL、被拒时的 message)。
|
|
37
56
|
detail/status 字段为可读字符串;对应 *_code 字段保留 BAM 原始数字枚举。
|
|
38
57
|
|
|
39
58
|
状态确认(Agent 必须执行)
|
|
40
|
-
优先使用 --wait
|
|
59
|
+
优先使用 --wait。status 为 pending 时停止轮询,告诉用户正在等待审批负责人处理;
|
|
60
|
+
用户确认审批处理完后,查询同一个 deploy-id,不要重新发起发布。
|
|
61
|
+
其余情况下,只要 deploy 调用后没有明确的 success / failed / rejected / canceled 终态或 pending,
|
|
41
62
|
都必须每 2s 轮询一次当前发布状态:拿到 pipelineTaskID 就执行 \`miaoda deploy get <pipelineTaskID> --json\`;
|
|
42
63
|
如果没有拿到 deploy-id 或输出为空,先执行 \`miaoda deploy history --limit 1 --json\` 找到对应发布记录并取得 ID,
|
|
43
|
-
再用这个 ID 每 2s 执行一次 \`miaoda deploy get <ID> --json\`,直到 status
|
|
64
|
+
再用这个 ID 每 2s 执行一次 \`miaoda deploy get <ID> --json\`,直到 status 进入终态或 pending。
|
|
44
65
|
|
|
45
66
|
示例
|
|
46
67
|
$ miaoda deploy
|
|
47
68
|
$ miaoda deploy --wait --timeout 600
|
|
69
|
+
$ miaoda deploy --wait --apply-reason "修复登录页报错"
|
|
48
70
|
$ miaoda deploy get 12345
|
|
49
71
|
$ miaoda deploy history --status failed
|
|
50
72
|
`);
|
|
@@ -55,6 +77,7 @@ JSON 输出
|
|
|
55
77
|
branch: rawOpts.branch,
|
|
56
78
|
wait: rawOpts.wait,
|
|
57
79
|
timeout: rawOpts.timeout,
|
|
80
|
+
applyReason: rawOpts.applyReason,
|
|
58
81
|
});
|
|
59
82
|
}));
|
|
60
83
|
registerDeployGet(deployCmd);
|
|
@@ -72,6 +95,7 @@ JSON 输出
|
|
|
72
95
|
{"data": {"ID": ..., "status": ..., "creator": "...", "createdAt": ..., "updatedAt": ..., ...}, "next_cursor": null, "has_more": false}
|
|
73
96
|
ID 即 deploy-id(== pipelineTaskID)
|
|
74
97
|
data.status 为可读字符串;data.status_code 为 BAM 原始数字枚举。
|
|
98
|
+
status 为 pending / rejected / failed 且存在审批单时,额外包含 approval(如 status、approvalURL、被拒时的 message)。
|
|
75
99
|
|
|
76
100
|
示例
|
|
77
101
|
$ miaoda deploy get 12345
|
|
@@ -90,8 +114,8 @@ function registerDeployHistory(parent) {
|
|
|
90
114
|
.description('查询发布历史(按时间倒序,分页)')
|
|
91
115
|
.addOption((0, shared_1.appIdOption)().hideHelp())
|
|
92
116
|
.addOption(new commander_1.Option('--status <status>', '状态过滤(pipeline 节点状态,大小写不敏感)')
|
|
93
|
-
.choices(
|
|
94
|
-
.argParser((0, shared_1.caseInsensitiveChoice)(
|
|
117
|
+
.choices(HISTORY_STATUS_CHOICES)
|
|
118
|
+
.argParser((0, shared_1.caseInsensitiveChoice)(HISTORY_STATUS_CHOICES)))
|
|
95
119
|
.option('--since <time>', '起始时间')
|
|
96
120
|
.option('--until <time>', '截止时间')
|
|
97
121
|
.option('--limit <n>', '返回条数上限(1~100)', parseLimit, 50)
|
|
@@ -18,7 +18,14 @@ function registerDeployCommandsModern(program) {
|
|
|
18
18
|
.option('--dir <path>', '项目目录', '.')
|
|
19
19
|
.option('--skip-build', '跳过 build 步骤(已构建好时使用)', false)
|
|
20
20
|
.option('--skip-release', '不创建/更新本地发布单,发布单状态由外部托管', false)
|
|
21
|
-
.option('--conf <json>', '关联元信息 JSON,仅识别 checkPointVersion / commitID
|
|
21
|
+
.option('--conf <json>', '关联元信息 JSON,仅识别 checkPointVersion / commitID / sessionID / marker / versionLevel 五字段')
|
|
22
|
+
// 这三个 flag 只在父命令声明一份,deploy 与 deploy patch 共用。
|
|
23
|
+
// 不能在 patch 上再声明一遍:commander 遇到父子同名 option 会把值判给父命令,
|
|
24
|
+
// 子命令拿到空数组,`deploy patch --create x` 会静默失效(deploy-patch-wiring 测试可复现)。
|
|
25
|
+
// patch 从 deployCmd.opts() 读,与它今天读 --dir 的方式一致。
|
|
26
|
+
.option('--create <relpath>', '新增文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
27
|
+
.option('--update <relpath>', '修改文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
28
|
+
.option('--delete <relpath>', '删除文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
22
29
|
.addHelpText('after', `
|
|
23
30
|
不要用异步模式或后台模式调用 deploy,否则调用可能提前结束,Agent 会误判发布已完成。
|
|
24
31
|
|
|
@@ -41,14 +48,27 @@ function registerDeployCommandsModern(program) {
|
|
|
41
48
|
外部据 stdout 返回的 version / preReleaseID 自行建单,并据本命令的
|
|
42
49
|
退出码决定把发布单翻成 Finished 还是 Failed —— 失败态由外部负责,CLI 不再兜底。
|
|
43
50
|
该模式下 releaseID / url 输出为 null(CLI 未建单,无从得知)。
|
|
44
|
-
注意:--conf
|
|
51
|
+
注意:--conf 的四个字段唯一消费者是 createLocalRelease,
|
|
45
52
|
本模式下不生效(静默忽略,不报错),需要关联请在外部建单时自行传递。
|
|
46
53
|
|
|
54
|
+
--create / --update / --delete(变更文件清单)
|
|
55
|
+
可重复,值为工程根相对路径(禁绝对路径与 "..")。deploy patch 消费它做增量发布;
|
|
56
|
+
在 deploy 下当前是预留口子——只校验路径并记一行日志,不改变上传行为:
|
|
57
|
+
deploy 仍然全量上传产物,latest 每次被重建为工作区的精确镜像(prune 清理远端多余对象),
|
|
58
|
+
以此保证 git 仓库与 TOS 资源不会不一致。
|
|
59
|
+
后续若实现增量上传,从这里取「要 PUT 哪些文件」即可,调用方无需改动;
|
|
60
|
+
删除仍交给 prune,因为 prune 比对的是完整本地清单,与上传了哪些文件无关。
|
|
61
|
+
|
|
47
62
|
--conf(关联元信息透传)
|
|
48
|
-
值为 JSON string
|
|
63
|
+
值为 JSON string,只识别四个字段,其余 key 忽略:
|
|
49
64
|
checkPointVersion 关联的 checkpoint 版本
|
|
50
65
|
commitID 关联的代码 commit ID
|
|
51
|
-
|
|
66
|
+
sessionID 发起本次发布的页面会话 ID,原样透传不做净化
|
|
67
|
+
marker 本次发布的变更说明,落发布单 description
|
|
68
|
+
四字段均可选;非法 JSON / 非对象 / 字段值非字符串会报错。
|
|
69
|
+
versionLevel:本次发布的版本档位。"major" 声明这是一个大版本,历史记录里强制开新组;
|
|
70
|
+
"minor" 声明是小版本,即使距上次发布超过聚合阈值也不提升。不传则由服务端按
|
|
71
|
+
时间间隔自动判定,与改造前行为一致——只有会主动声明档位的链路需要传。
|
|
52
72
|
|
|
53
73
|
JSON 输出(stdout)
|
|
54
74
|
{"data": {"appId": "...", "version": <n>, "url": "...", "releaseID": "...", "preReleaseID": "..."}}
|
|
@@ -60,6 +80,8 @@ JSON 输出(stdout)
|
|
|
60
80
|
$ miaoda deploy --skip-build
|
|
61
81
|
$ miaoda deploy --skip-release
|
|
62
82
|
$ miaoda deploy --conf '{"checkPointVersion":"v3","commitID":"a1b2c3d"}'
|
|
83
|
+
$ miaoda deploy --conf '{"commitID":"a1b2c3d","sessionID":"<uuid>","marker":"编辑保存:修改 index.html"}'
|
|
84
|
+
$ miaoda deploy --update index.html --create assets/new.js --delete old.html
|
|
63
85
|
`);
|
|
64
86
|
deployCmd.action((0, shared_1.withHelp)(deployCmd, async (rawOpts) => {
|
|
65
87
|
await (0, modern_1.handleDeployModern)({
|
|
@@ -68,17 +90,18 @@ JSON 输出(stdout)
|
|
|
68
90
|
skipBuild: rawOpts.skipBuild,
|
|
69
91
|
skipRelease: rawOpts.skipRelease,
|
|
70
92
|
conf: rawOpts.conf,
|
|
93
|
+
creates: rawOpts.create,
|
|
94
|
+
updates: rawOpts.update,
|
|
95
|
+
deletes: rawOpts.delete,
|
|
71
96
|
});
|
|
72
97
|
}));
|
|
73
98
|
const patchCmd = deployCmd
|
|
74
99
|
.command('patch')
|
|
75
100
|
.description('design-html 增量发布:按文件 create/update/delete,.html 变动时同步 routes.json')
|
|
76
|
-
.option('--create <relpath>', '新增文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
77
|
-
.option('--update <relpath>', '覆盖已有文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
78
|
-
.option('--delete <relpath>', '删除文件(可重复)', shared_1.collectRepeatedOption, [])
|
|
79
101
|
.option('--session-id <id>', '会话 ID,发布时透传给下游(可选)')
|
|
80
102
|
.addHelpText('after', `
|
|
81
|
-
--dir
|
|
103
|
+
--dir / --create / --update / --delete 声明在父命令 deploy 上,本命令共用,用法不变。
|
|
104
|
+
--dir 为项目目录,默认当前目录。
|
|
82
105
|
仅支持 design-html(design_local_deploy)。路径为工程根相对路径(= 服务端 latest 下 key),
|
|
83
106
|
禁绝对路径与 ".."。--create/--update 的本地文件必须存在。改动里含任意 .html(增 / 改 / 删)
|
|
84
107
|
时自动重算并随包更新 routes.json;纯非 .html 改动不动 routes.json。
|
|
@@ -98,9 +121,9 @@ JSON 输出
|
|
|
98
121
|
await (0, index_1.handleDeployPatch)({
|
|
99
122
|
appId: (0, shared_1.resolveAppId)({}),
|
|
100
123
|
dir: deployCmd.opts().dir ?? '.',
|
|
101
|
-
creates:
|
|
102
|
-
updates:
|
|
103
|
-
deletes:
|
|
124
|
+
creates: deployCmd.opts().create,
|
|
125
|
+
updates: deployCmd.opts().update,
|
|
126
|
+
deletes: deployCmd.opts().delete,
|
|
104
127
|
sessionID: rawOpts.sessionId,
|
|
105
128
|
});
|
|
106
129
|
}));
|
|
@@ -53,7 +53,7 @@ function registerSkillsSync(parent) {
|
|
|
53
53
|
|
|
54
54
|
JSON 输出
|
|
55
55
|
{"data": {"stack": "...", "version": "...", "syncedSkills": [...], "techSynced": true|false,
|
|
56
|
-
"claudeSkillsLink": "created|updated|noop|conflict" // 仅 --local 时}}
|
|
56
|
+
"claudeSkillsLink": "created|updated|noop|conflict|unsupported" // 仅 --local 时}}
|
|
57
57
|
|
|
58
58
|
传 --type 时:
|
|
59
59
|
{"data": {"mode": "sandbox", "type": "...", "packageName": "...", "version": "...",
|
|
@@ -38,11 +38,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.UPGRADE_MANIFEST_FILENAME = exports.DEFAULT_EXPORT_OUT = void 0;
|
|
40
40
|
exports.handleAppExport = handleAppExport;
|
|
41
|
-
const node_child_process_1 = require("node:child_process");
|
|
42
41
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
43
42
|
const node_os_1 = __importDefault(require("node:os"));
|
|
44
43
|
const node_path_1 = __importDefault(require("node:path"));
|
|
45
44
|
const api = __importStar(require("../../../api/index"));
|
|
45
|
+
const archive_1 = require("../../../utils/archive");
|
|
46
46
|
const error_1 = require("../../../utils/error");
|
|
47
47
|
const output_1 = require("../../../utils/output");
|
|
48
48
|
const logger_1 = require("../../../utils/logger");
|
|
@@ -95,11 +95,11 @@ function extractArchive(archivePath, destDir, head) {
|
|
|
95
95
|
const isZip = head.length >= 4 && head[0] === 0x50 && head[1] === 0x4b;
|
|
96
96
|
const isGzip = head.length >= 2 && head[0] === 0x1f && head[1] === 0x8b;
|
|
97
97
|
if (isZip) {
|
|
98
|
-
(0,
|
|
98
|
+
(0, archive_1.extractZip)(archivePath, destDir);
|
|
99
99
|
return;
|
|
100
100
|
}
|
|
101
101
|
if (isGzip) {
|
|
102
|
-
(0,
|
|
102
|
+
(0, archive_1.extractTarGz)(archivePath, destDir);
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
105
|
throw new error_1.AppError('EXPORT_ARCHIVE_INVALID', '无法识别归档格式(非 zip / tar.gz)', {
|
|
@@ -16,6 +16,7 @@ const error_1 = require("../../../utils/error");
|
|
|
16
16
|
const output_1 = require("../../../utils/output");
|
|
17
17
|
const env_1 = require("../../../utils/env");
|
|
18
18
|
const logger_1 = require("../../../utils/logger");
|
|
19
|
+
const run_npm_1 = require("../../../utils/run-npm");
|
|
19
20
|
/**
|
|
20
21
|
* miaoda app migrate --to <stack> [--from <stack>] [--dir <path>]
|
|
21
22
|
*
|
|
@@ -126,7 +127,7 @@ async function handleAppMigrate(opts) {
|
|
|
126
127
|
(0, logger_1.log)('migrate', `Running npm ${installArgs.join(' ')}...`);
|
|
127
128
|
let installError;
|
|
128
129
|
try {
|
|
129
|
-
(0,
|
|
130
|
+
(0, run_npm_1.execNpmSync)(installArgs, {
|
|
130
131
|
cwd: targetDir,
|
|
131
132
|
stdio: (0, output_1.isJsonMode)() ? ['ignore', 'ignore', 'inherit'] : 'inherit',
|
|
132
133
|
});
|
|
@@ -167,6 +168,8 @@ async function handleAppMigrate(opts) {
|
|
|
167
168
|
// - install 挂了不做 —— 新依赖不全, 杀旧 dev 后新 dev 起不来反而更糟,
|
|
168
169
|
// 留着旧进程让用户先处理 installError
|
|
169
170
|
// - 软失败:pkill 无匹配进程退出 1,catch 吞掉
|
|
171
|
+
// - pkill 是 POSIX-only,但整段被 isSandboxEnv() 包着(沙箱恒为 Linux),
|
|
172
|
+
// Windows 本地永远走不到这里,故不需要跨平台封装
|
|
170
173
|
let devRestarted = false;
|
|
171
174
|
if (installError === undefined && (0, env_1.isSandboxEnv)()) {
|
|
172
175
|
(0, logger_1.log)('migrate', '沙箱环境,重启 dev process(平台 supervisor 会自动拉起)...');
|
|
@@ -9,7 +9,6 @@ exports.runStackSync = runStackSync;
|
|
|
9
9
|
exports.summarizeSyncResults = summarizeSyncResults;
|
|
10
10
|
exports.snapshotManagedDepSpecs = snapshotManagedDepSpecs;
|
|
11
11
|
exports.diffManagedSpecs = diffManagedSpecs;
|
|
12
|
-
const node_child_process_1 = require("node:child_process");
|
|
13
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
14
|
const sync_configs_1 = require("../../../config/sync-configs");
|
|
@@ -19,6 +18,7 @@ const githooks_1 = require("../../../utils/githooks");
|
|
|
19
18
|
const logs_dir_1 = require("../../../utils/logs-dir");
|
|
20
19
|
const install_1 = require("../../../services/app/init/install");
|
|
21
20
|
const spark_meta_1 = require("../../../utils/spark-meta");
|
|
21
|
+
const run_npm_1 = require("../../../utils/run-npm");
|
|
22
22
|
const error_1 = require("../../../utils/error");
|
|
23
23
|
const output_1 = require("../../../utils/output");
|
|
24
24
|
const logger_1 = require("../../../utils/logger");
|
|
@@ -127,7 +127,7 @@ async function handleAppSync(opts) {
|
|
|
127
127
|
(0, logger_1.log)('sync', `Running npm ${installArgs.join(' ')}...`);
|
|
128
128
|
let installError;
|
|
129
129
|
try {
|
|
130
|
-
(0,
|
|
130
|
+
(0, run_npm_1.execNpmSync)(installArgs, {
|
|
131
131
|
cwd: targetDir,
|
|
132
132
|
stdio: (0, output_1.isJsonMode)() ? ['ignore', 'ignore', 'inherit'] : 'inherit',
|
|
133
133
|
});
|
|
@@ -47,7 +47,7 @@ const colors_1 = require("../../../utils/colors");
|
|
|
47
47
|
const fuzzy_match_1 = require("../../../utils/fuzzy-match");
|
|
48
48
|
const index_1 = require("../../../api/db/index");
|
|
49
49
|
const sql_keywords_1 = require("../../../api/db/sql-keywords");
|
|
50
|
-
const
|
|
50
|
+
const run_npm_1 = require("../../../utils/run-npm");
|
|
51
51
|
const node_fs_1 = require("node:fs");
|
|
52
52
|
const node_path_1 = __importDefault(require("node:path"));
|
|
53
53
|
/**
|
|
@@ -140,20 +140,22 @@ async function maybeSyncAgentSchema(results) {
|
|
|
140
140
|
(0, logger_1.debug)(`[db sql] DDL detected, running \`npm run gen:db-schema\` in ${projectRoot}`);
|
|
141
141
|
try {
|
|
142
142
|
await new Promise((resolve) => {
|
|
143
|
-
const proc = (0,
|
|
143
|
+
const proc = (0, run_npm_1.spawnNpm)(['run', 'gen:db-schema'], {
|
|
144
144
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
145
145
|
cwd: projectRoot,
|
|
146
146
|
});
|
|
147
147
|
// --verbose:实时透传到 stderr 看进度;默认完全静默丢弃。
|
|
148
148
|
// 任何路径下子进程输出都不会进入当前 CLI 的 stdout,--json 模式安全。
|
|
149
149
|
// 注:默认路径必须 attach 监听器,否则 pipe 缓冲区写满会反压子进程。
|
|
150
|
+
// `?.`:stdio 走数组形式时 node 类型上 stdout/stderr 可为 null。这里 fd1/fd2
|
|
151
|
+
// 显式给了 'pipe',实际恒非 null;真为 null 也意味着不是 pipe、不存在缓冲反压。
|
|
150
152
|
if (verbose) {
|
|
151
|
-
proc.stdout
|
|
152
|
-
proc.stderr
|
|
153
|
+
proc.stdout?.pipe(process.stderr);
|
|
154
|
+
proc.stderr?.pipe(process.stderr);
|
|
153
155
|
}
|
|
154
156
|
else {
|
|
155
|
-
proc.stdout
|
|
156
|
-
proc.stderr
|
|
157
|
+
proc.stdout?.on('data', drainChunk);
|
|
158
|
+
proc.stderr?.on('data', drainChunk);
|
|
157
159
|
}
|
|
158
160
|
let timedOut = false;
|
|
159
161
|
const timer = setTimeout(() => {
|
|
@@ -34,20 +34,28 @@ 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");
|
|
40
41
|
const polling_1 = require("./polling");
|
|
41
42
|
const format_1 = require("./format");
|
|
43
|
+
const helpers_1 = require("./helpers");
|
|
42
44
|
/** miaoda deploy [--branch ...] [--wait] [--timeout 300] */
|
|
43
45
|
async function handleDeploy(opts) {
|
|
44
46
|
const appID = opts.appId;
|
|
45
|
-
|
|
47
|
+
if (opts.applyReason !== undefined)
|
|
48
|
+
(0, helpers_1.validateApplyReason)(opts.applyReason);
|
|
49
|
+
const resp = await api.deploy.createRelease({
|
|
50
|
+
appID,
|
|
51
|
+
branch: opts.branch,
|
|
52
|
+
...(opts.applyReason !== undefined && { applyReason: opts.applyReason }),
|
|
53
|
+
});
|
|
46
54
|
const pipelineTaskID = resp.pipelineTaskID ?? '';
|
|
47
55
|
if (!opts.wait) {
|
|
48
56
|
if (!(0, output_1.isJsonMode)()) {
|
|
49
57
|
process.stdout.write(`✓ Deployment triggered. (deploy-id: ${pipelineTaskID})\n`);
|
|
50
|
-
process.stdout.write(` hint: No terminal status yet. Poll every 2s with \`miaoda deploy get ${pipelineTaskID} --json\`; if no deploy-id is visible, first get it from \`miaoda deploy history --limit 1 --json
|
|
58
|
+
process.stdout.write(` hint: No terminal status yet. Poll every 2s with \`miaoda deploy get ${pipelineTaskID} --json\`; if no deploy-id is visible, first get it from \`miaoda deploy history --limit 1 --json\`. Stop polling when status is pending (awaiting approval) and hand it to the user.\n`);
|
|
51
59
|
}
|
|
52
60
|
(0, output_1.emit)({
|
|
53
61
|
data: { pipelineTaskID },
|
|
@@ -64,7 +72,8 @@ async function handleDeploy(opts) {
|
|
|
64
72
|
pipelineTaskID,
|
|
65
73
|
timeoutSec: opts.timeout ?? 300,
|
|
66
74
|
});
|
|
67
|
-
|
|
75
|
+
const approval = (0, index_1.findApproval)(detail);
|
|
76
|
+
if (!(0, output_1.isJsonMode)() && !writeApprovalNotice(detail.status, approval, pipelineTaskID)) {
|
|
68
77
|
if (detail.status === index_1.NodeStatus.SUCCESS) {
|
|
69
78
|
process.stdout.write('✓ 发布成功\n');
|
|
70
79
|
}
|
|
@@ -77,8 +86,52 @@ async function handleDeploy(opts) {
|
|
|
77
86
|
}
|
|
78
87
|
}
|
|
79
88
|
(0, output_1.emit)({
|
|
80
|
-
data: { pipelineTaskID, detail: (0, format_1.formatPipelineInstance)(detail) },
|
|
89
|
+
data: { pipelineTaskID, ...(approval && { approval }), detail: (0, format_1.formatPipelineInstance)(detail) },
|
|
81
90
|
next_cursor: null,
|
|
82
91
|
has_more: false,
|
|
83
92
|
});
|
|
84
93
|
}
|
|
94
|
+
/** 控制字符与不可见的 Unicode 格式字符替换成空格,防止服务端字段改写终端输出 */
|
|
95
|
+
function toDisplay(text) {
|
|
96
|
+
return text.replace(/[\p{Cc}\p{Cf}\u2028\u2029]/gu, ' ').trim();
|
|
97
|
+
}
|
|
98
|
+
function writeReason(approval) {
|
|
99
|
+
const message = approval?.message;
|
|
100
|
+
if (typeof message === 'string' && message !== '') {
|
|
101
|
+
process.stdout.write(` 原因:${toDisplay(message)}\n`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** 卡点异常:平台没把审批发出去,没有任何一方做过裁决,不是代码问题 */
|
|
105
|
+
function writeApprovalGateFailed(approval) {
|
|
106
|
+
process.stdout.write('✗ 发布卡点异常:审批未能发起,不是代码问题,请稍后重试或联系平台\n');
|
|
107
|
+
writeReason(approval);
|
|
108
|
+
}
|
|
109
|
+
function writeRejected(approval) {
|
|
110
|
+
process.stdout.write('✗ 发布审批未通过,本次未上线\n');
|
|
111
|
+
writeReason(approval);
|
|
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
|
+
}
|
|
132
|
+
function writeApprovalHint(approval, pipelineTaskID) {
|
|
133
|
+
const url = (0, index_1.approvalURL)(approval);
|
|
134
|
+
process.stdout.write('⏸ 发布已进入人工审批,等待审批负责人处理\n');
|
|
135
|
+
process.stdout.write(url ? ` 审批链接:${toDisplay(url)}\n` : ' 服务端未返回审批链接\n');
|
|
136
|
+
process.stdout.write(` hint: 审批处理完成后运行 \`miaoda deploy get ${pipelineTaskID}\` 查询本次发布结果,不要重新发起发布\n`);
|
|
137
|
+
}
|
|
@@ -29,6 +29,7 @@ function formatPipelineInstance(detail) {
|
|
|
29
29
|
jobs: stage.jobs.map((group) => group.map((job) => ({
|
|
30
30
|
...job,
|
|
31
31
|
...formatStatusMetadata(job.status),
|
|
32
|
+
...formatJobApproval(job),
|
|
32
33
|
steps: job.steps?.map((step) => ({
|
|
33
34
|
...step,
|
|
34
35
|
...formatStatusMetadata(step.status),
|
|
@@ -37,3 +38,15 @@ function formatPipelineInstance(detail) {
|
|
|
37
38
|
})),
|
|
38
39
|
};
|
|
39
40
|
}
|
|
41
|
+
/** Job 上的卡点信息同样翻译 status,与 data.approval 的表示保持一致 */
|
|
42
|
+
function formatJobApproval(job) {
|
|
43
|
+
if (!job.approval)
|
|
44
|
+
return {};
|
|
45
|
+
return {
|
|
46
|
+
approval: {
|
|
47
|
+
...job.approval,
|
|
48
|
+
status: (0, index_1.approvalStatusText)(job.approval.status),
|
|
49
|
+
status_code: job.approval.status,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -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,16 @@ async function handleDeployGet(opts) {
|
|
|
67
79
|
next_actions: ['运行 `miaoda deploy history` 确认 deploy-id 是否在最近窗口内'],
|
|
68
80
|
});
|
|
69
81
|
}
|
|
70
|
-
|
|
82
|
+
// 列表项不含审批信息:按同一个 ID 查详情接口(默认返回审批视图)取审批单。
|
|
83
|
+
// 详情失败直接抛错,不降级为只输出列表数据,避免丢掉审批链接 / 拒绝原因却无人察觉。
|
|
84
|
+
const approval = APPROVAL_LOOKUP_STATUSES.includes(instance.status)
|
|
85
|
+
? (0, index_1.findApproval)((0, polling_1.normalizePipelineInstance)(await api.deploy.queryPipelineInstance({ appID, instanceID: opts.deployId })))
|
|
86
|
+
: undefined;
|
|
87
|
+
(0, output_1.emit)({
|
|
88
|
+
data: { ...(0, format_1.formatSimpleInstance)(instance), ...(approval && { approval }) },
|
|
89
|
+
next_cursor: null,
|
|
90
|
+
has_more: false,
|
|
91
|
+
}, index_1.deployGetSchema);
|
|
92
|
+
if (!(0, output_1.isJsonMode)())
|
|
93
|
+
(0, deploy_1.writeApprovalNotice)(instance.status, approval, opts.deployId);
|
|
71
94
|
}
|