@optima-chat/dev-skills 0.11.0 → 0.13.0
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/.claude/skills/cn-deploy/SKILL.md +52 -0
- package/.codex/skills/cn-deploy/SKILL.md +52 -0
- package/AGENTS.md +2 -0
- package/README.md +2 -0
- package/bin/helpers/cn-deploy.ts +187 -0
- package/dist/bin/helpers/cn-deploy.js +228 -0
- package/package.json +4 -3
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cn-deploy
|
|
3
|
+
description: 当用户请求把服务发布/部署/构建到 cn-stage、跑云效流水线、cn-stage 发版、部署某个分支到 cn-stage、触发 cn 构建、yunxiao 部署、"把 xx 发到 cn"时,使用此技能。覆盖 cn-stage 全部 20 个服务(构建→DB迁移→SAE 发布一条龙)。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# cn-deploy - 云效 cn-stage 流水线发布
|
|
7
|
+
|
|
8
|
+
## 用途
|
|
9
|
+
|
|
10
|
+
把服务的 GitHub 代码经云效 Flow 流水线发布到阿里云 cn-stage(SAE)。一条命令完成:
|
|
11
|
+
Codeup mirror 同步 → 构建(含 NEXT_PUBLIC 自动烤/境内镜像源改写)→ DB 迁移 → SAE 滚动发布 → 部署 sha 校验。
|
|
12
|
+
|
|
13
|
+
## 命令
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
optima-cn-deploy <service> # main 全链发布
|
|
17
|
+
optima-cn-deploy <service> --branch feat/xxx # 非 main 分支构建+部署
|
|
18
|
+
optima-cn-deploy <service> --no-wait # 只触发不等待
|
|
19
|
+
optima-cn-deploy --list # 列出全部可发服务
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 使用要点
|
|
23
|
+
|
|
24
|
+
1. **先 `--list` 确认服务名**(20 个服务,含 commerce-backend / optima-scout)。
|
|
25
|
+
2. 全链约 3-8 分钟(agentic-chat 最重)。触发后会输出云效 run 链接,可发给用户自行围观。
|
|
26
|
+
3. **凭证零配置**:构建凭证由云效变量组 `cn-stage-build-credentials`(41970) 供给,不需要注入任何 secret。
|
|
27
|
+
4. 分支构建会把该分支代码部署到 cn-stage(替换现运行版本)——用户明确要求部署分支时才用。
|
|
28
|
+
5. 失败排查:命令输出的 flow.aliyun.com 链接里看具体 step 日志;常见原因是该分支/仓的 Codeup mirror 凭证问题(命令会在 mirror 步就报错并中止,不会构建旧代码)。
|
|
29
|
+
6. 成功标准:最后一行 `✓ SAE ImageUrl tag=<sha>` —— 流水线绿且 SAE 真的钉到了目标 commit。只看流水线绿不算完成。
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## cn-prod vtag 发版(⚠️ 待 svenyang 首次验证通过后再日常使用)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
optima-cn-deploy <service> --vtag cn-v1.2.3 # 先 stage 同 tag 验证
|
|
36
|
+
optima-cn-deploy <service> --env prod --vtag cn-v1.2.3 # prod 发版
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- prod 是 vtag 制:必须先 `git tag cn-vX.Y.Z && git push`,工具拒绝无 vtag / 裸 v* / 带 --branch 的 prod 请求
|
|
40
|
+
- prod 流水线构建后**停在人工卡点**(xbfool/svenyang 在云效控制台审批),之后 DB 迁移 + digest 钉死部署自动走完
|
|
41
|
+
- 成功标准:prod SAE ImageUrl 为 `@sha256:` digest 寻址(工具最后一行校验)
|
|
42
|
+
- 用户请求发 cn-prod 时:确认已有 stage 验证过的 cn-v tag;没有则先引导走 stage
|
|
43
|
+
|
|
44
|
+
## 前置依赖
|
|
45
|
+
|
|
46
|
+
- `aliyun` CLI 已配置(profile 默认 `aliyun-optima`,可用环境变量 `OPTIMA_ALIYUN_PROFILE` 覆盖为自己的 RAM 用户 profile)
|
|
47
|
+
- `gh` 已登录(mirror 同步需要 GitHub token)
|
|
48
|
+
|
|
49
|
+
## 边界
|
|
50
|
+
|
|
51
|
+
- cn-prod 仅走 vtag 制(见上),且有人工卡点闸门;日常无 vtag 的构建只发 cn-stage。
|
|
52
|
+
- 服务注册表是 optima-terraform `alicloud/stacks/cn-prod-buildbox/yunxiao/` 的快照;新增服务先在那边 gen-pipelines 建好流水线,再同步本工具的 SERVICES 表。
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cn-deploy
|
|
3
|
+
description: 当用户请求把服务发布/部署/构建到 cn-stage、跑云效流水线、cn-stage 发版、部署某个分支到 cn-stage、触发 cn 构建、yunxiao 部署、"把 xx 发到 cn"时,使用此技能。覆盖 cn-stage 全部 20 个服务(构建→DB迁移→SAE 发布一条龙)。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# cn-deploy - 云效 cn-stage 流水线发布
|
|
7
|
+
|
|
8
|
+
## 用途
|
|
9
|
+
|
|
10
|
+
把服务的 GitHub 代码经云效 Flow 流水线发布到阿里云 cn-stage(SAE)。一条命令完成:
|
|
11
|
+
Codeup mirror 同步 → 构建(含 NEXT_PUBLIC 自动烤/境内镜像源改写)→ DB 迁移 → SAE 滚动发布 → 部署 sha 校验。
|
|
12
|
+
|
|
13
|
+
## 命令
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
optima-cn-deploy <service> # main 全链发布
|
|
17
|
+
optima-cn-deploy <service> --branch feat/xxx # 非 main 分支构建+部署
|
|
18
|
+
optima-cn-deploy <service> --no-wait # 只触发不等待
|
|
19
|
+
optima-cn-deploy --list # 列出全部可发服务
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 使用要点
|
|
23
|
+
|
|
24
|
+
1. **先 `--list` 确认服务名**(20 个服务,含 commerce-backend / optima-scout)。
|
|
25
|
+
2. 全链约 3-8 分钟(agentic-chat 最重)。触发后会输出云效 run 链接,可发给用户自行围观。
|
|
26
|
+
3. **凭证零配置**:构建凭证由云效变量组 `cn-stage-build-credentials`(41970) 供给,不需要注入任何 secret。
|
|
27
|
+
4. 分支构建会把该分支代码部署到 cn-stage(替换现运行版本)——用户明确要求部署分支时才用。
|
|
28
|
+
5. 失败排查:命令输出的 flow.aliyun.com 链接里看具体 step 日志;常见原因是该分支/仓的 Codeup mirror 凭证问题(命令会在 mirror 步就报错并中止,不会构建旧代码)。
|
|
29
|
+
6. 成功标准:最后一行 `✓ SAE ImageUrl tag=<sha>` —— 流水线绿且 SAE 真的钉到了目标 commit。只看流水线绿不算完成。
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## cn-prod vtag 发版(⚠️ 待 svenyang 首次验证通过后再日常使用)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
optima-cn-deploy <service> --vtag cn-v1.2.3 # 先 stage 同 tag 验证
|
|
36
|
+
optima-cn-deploy <service> --env prod --vtag cn-v1.2.3 # prod 发版
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- prod 是 vtag 制:必须先 `git tag cn-vX.Y.Z && git push`,工具拒绝无 vtag / 裸 v* / 带 --branch 的 prod 请求
|
|
40
|
+
- prod 流水线构建后**停在人工卡点**(xbfool/svenyang 在云效控制台审批),之后 DB 迁移 + digest 钉死部署自动走完
|
|
41
|
+
- 成功标准:prod SAE ImageUrl 为 `@sha256:` digest 寻址(工具最后一行校验)
|
|
42
|
+
- 用户请求发 cn-prod 时:确认已有 stage 验证过的 cn-v tag;没有则先引导走 stage
|
|
43
|
+
|
|
44
|
+
## 前置依赖
|
|
45
|
+
|
|
46
|
+
- `aliyun` CLI 已配置(profile 默认 `aliyun-optima`,可用环境变量 `OPTIMA_ALIYUN_PROFILE` 覆盖为自己的 RAM 用户 profile)
|
|
47
|
+
- `gh` 已登录(mirror 同步需要 GitHub token)
|
|
48
|
+
|
|
49
|
+
## 边界
|
|
50
|
+
|
|
51
|
+
- cn-prod 仅走 vtag 制(见上),且有人工卡点闸门;日常无 vtag 的构建只发 cn-stage。
|
|
52
|
+
- 服务注册表是 optima-terraform `alicloud/stacks/cn-prod-buildbox/yunxiao/` 的快照;新增服务先在那边 gen-pipelines 建好流水线,再同步本工具的 SERVICES 表。
|
package/AGENTS.md
CHANGED
|
@@ -13,6 +13,7 @@ Prefer the installed CLI tools over reimplementing long shell workflows:
|
|
|
13
13
|
- `optima-grant-credits <email|phone|userId> --credits <n> [options]`
|
|
14
14
|
- `optima-product <create|update|add-channel|toggle-channel|show> [options]` — manage paid-plugin marketplace Products + Stripe channels (Wave 1.5 admin endpoints; stage default)
|
|
15
15
|
- `optima-entitlement <grant|revoke|list> [options]` — admin-grant / revoke / list paid-plugin entitlements (refuses revoke of PAYMENT / PARTNER source)
|
|
16
|
+
- `optima-cn-deploy <service> [--branch feat/xxx] [--no-wait]` — 云效 Flow 发布到 cn-stage(mirror 同步→构建→DB 迁移→SAE 发布→sha 校验;20 服务,凭证由云效变量组供给零配置)
|
|
16
17
|
- `optima-plugin <show|set-paid|set-default> [options]` — flip a plugin's skills-side paid/free state (isPaid) + defaultForUser (the user-facing gate; pairs with optima-product for the billing side)
|
|
17
18
|
|
|
18
19
|
For code-reading tasks across Optima repositories, use `gh` commands against `Optima-Chat/<repo>`.
|
|
@@ -30,6 +31,7 @@ After `npm install -g @optima-chat/dev-skills`, this package installs skills und
|
|
|
30
31
|
- `grant-credits`
|
|
31
32
|
- `restart-ecs`
|
|
32
33
|
- `use-commerce-cli`
|
|
34
|
+
- `cn-deploy`
|
|
33
35
|
|
|
34
36
|
## Tooling Assumptions
|
|
35
37
|
|
package/README.md
CHANGED
|
@@ -88,6 +88,7 @@ Claude:
|
|
|
88
88
|
| `/query-db` | 查询数据库 | `/query-db user-auth "SELECT COUNT(*) FROM users"` | ✅ |
|
|
89
89
|
| `/generate-test-token` | 生成测试 token | `/generate-test-token` | 🔧 Development |
|
|
90
90
|
| `/read-code` | 阅读代码 | `/read-code commerce-backend app/main.py` | - |
|
|
91
|
+
| `/cn-deploy` | 发布服务到 cn-stage(云效流水线一条龙) | 「把 billing 发到 cn-stage」 | cn-stage |
|
|
91
92
|
|
|
92
93
|
**说明**:
|
|
93
94
|
- 命令支持 CI、Stage、Prod 三个环境
|
|
@@ -109,6 +110,7 @@ Claude:
|
|
|
109
110
|
| `optima-grant-credits` | 赠送积分 | `optima-grant-credits user@example.com --credits 10000 --env prod`(`optima-grant-balance` 为废弃别名)|
|
|
110
111
|
| `optima-entitlement` | 产品权益 grant/revoke/list | `optima-entitlement grant 18898654855 --product-key scout-gift --justification "..." --env cn-prod` |
|
|
111
112
|
| `optima-account` | 账号 status/ban/unban | `optima-account ban user@example.com --reason "abuse" --env prod` |
|
|
113
|
+
| `optima-cn-deploy` | 云效 Flow 发布到 cn-stage(构建→DB迁移→SAE 发布→sha 校验,20 服务) | `optima-cn-deploy billing` / `optima-cn-deploy user-auth --branch feat/xxx` |
|
|
112
114
|
|
|
113
115
|
> **4 环境 + 标识符**:`grant-subscription` / `grant-credits` / `entitlement` / `account` 均支持 `stage` / `prod` / `cn-prod` / `cn-stage`。标识符 `<email\|phone\|userId>`——**cn-prod / cn-stage 用户多为手机号注册**,三种均可;AWS stage/prod 仅 email。`ban`/`unban` 及 `account status` 的禁用态读取需 admin-用户凭证(Infisical `/shared-secrets/credentials`;cn 另需 `INFISICAL_CN_EMAIL/PASSWORD`)。
|
|
114
116
|
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* optima-cn-deploy —— 云效 Flow cn-stage 流水线一条龙触发器(团队版)。
|
|
4
|
+
*
|
|
5
|
+
* 对应 optima-terraform alicloud/stacks/cn-prod-buildbox/yunxiao/ 的 cn-run.py,
|
|
6
|
+
* 去掉 buildbox SSH 依赖(凭证由云效变量组 41970 供给,2026-07-13 起控制台/API 裸跑均可)。
|
|
7
|
+
*
|
|
8
|
+
* 做四件事(每件都是踩过的坑):
|
|
9
|
+
* 1. 触发服务仓 Codeup mirror 同步并等目标分支追平 GitHub —— mirror 不新鲜=构建旧代码。
|
|
10
|
+
* GitHub token 用 `gh auth token`(需本机 gh 已登录)。
|
|
11
|
+
* 2. StartPipelineRun;--branch 时带 runningBranchs 覆盖服务仓 source 分支。
|
|
12
|
+
* 3. 轮询到终态,给出云效 run 链接。
|
|
13
|
+
* 4. SUCCESS 后校验 SAE ImageUrl tag == 目标分支 HEAD short-sha(防"流水线绿但没部上");
|
|
14
|
+
* aliyun API 偶发空返回,带重试守卫。
|
|
15
|
+
*
|
|
16
|
+
* 用法:
|
|
17
|
+
* optima-cn-deploy billing # main 全链(构建→迁移→SAE 发布)
|
|
18
|
+
* optima-cn-deploy user-auth --branch feat/xxx # 非 main 分支构建+部署到 cn-stage
|
|
19
|
+
* optima-cn-deploy gateway-core --no-wait # 只触发不等待
|
|
20
|
+
* optima-cn-deploy --list # 列出全部可发服务
|
|
21
|
+
* optima-cn-deploy billing --vtag cn-v1.2.3 # stage 按版本 tag 构建(发版前验证)
|
|
22
|
+
* optima-cn-deploy billing --env prod --vtag cn-v1.2.3
|
|
23
|
+
* # cn-prod vtag 发版:构建→停在人工卡点(云效控制台审批)→迁移→digest 钉死部署
|
|
24
|
+
*
|
|
25
|
+
* 前置: aliyun CLI(profile 默认 aliyun-optima,可用 OPTIMA_ALIYUN_PROFILE 覆盖)+ gh 已登录。
|
|
26
|
+
* 流水线定义的单一信源在 optima-terraform yunxiao/(gen-pipelines.py);本表为其快照,
|
|
27
|
+
* 新增服务后同步(pipelineId 稳定,不常变)。
|
|
28
|
+
*/
|
|
29
|
+
import { execFileSync } from 'node:child_process';
|
|
30
|
+
|
|
31
|
+
const ORG = '6a17b6282bf8b1184fc0e0c6';
|
|
32
|
+
const ENDPOINT = 'devops.cn-hangzhou.aliyuncs.com';
|
|
33
|
+
const PROFILE = process.env.OPTIMA_ALIYUN_PROFILE || 'aliyun-optima';
|
|
34
|
+
const CODEUP_BASE = `https://codeup.aliyun.com/${ORG}`;
|
|
35
|
+
|
|
36
|
+
// 服务注册表(快照自 optima-terraform services.stage.env + gen-pipelines.py,2026-07-13)
|
|
37
|
+
interface Svc { pipelineId: number; repo: string; saeAppId?: string; prodSaeAppId?: string; }
|
|
38
|
+
const SERVICES: Record<string, Svc> = {
|
|
39
|
+
'agent-portal': { pipelineId: 5118519, repo: 'optima-portals', saeAppId: 'fe757f78-d18d-4480-9402-fe59d4721055', prodSaeAppId: '9211db38-a255-4393-ae92-7a7bb41b583d' },
|
|
40
|
+
'agentic-chat': { pipelineId: 5118520, repo: 'agentic-chat', saeAppId: '6aea1ce1-f813-4e1c-8e97-d1ecb5398e37', prodSaeAppId: '6e290c73-a646-43ef-9da5-ad0b2e7eff73' },
|
|
41
|
+
'billing': { pipelineId: 5118521, repo: 'optima-billing', saeAppId: '09d8e292-dc64-4af8-bce5-0a56cb666921', prodSaeAppId: '6c31cf82-8802-4d45-b6a2-e9d7c83ccce9' },
|
|
42
|
+
'browser-backend': { pipelineId: 5118522, repo: 'optima-browser-use', saeAppId: '1fced3f6-a80a-41a4-8f23-e4d5a467f8eb', prodSaeAppId: 'eb782ed9-c468-4e81-a0fe-87e5b4264192' },
|
|
43
|
+
'commerce-backend': { pipelineId: 5107005, repo: 'commerce-backend', saeAppId: '49d09808-508c-471a-9560-553c49a67f72', prodSaeAppId: 'd40597b6-a98c-4063-9297-8b9fdfa8add4' },
|
|
44
|
+
'commerce-rq-scheduler': { pipelineId: 5118523, repo: 'commerce-backend', saeAppId: '1d2810ef-889e-4675-b6e6-a299e4722e68', prodSaeAppId: '762dd7a5-7fe8-41ed-9332-cfd31b0e087e' },
|
|
45
|
+
'commerce-rq-worker': { pipelineId: 5118524, repo: 'commerce-backend', saeAppId: 'd661da87-6d24-40b8-93ed-e1c967d8abe2', prodSaeAppId: '2c9ee19a-5062-4205-9f5b-6ae3f8062a23' },
|
|
46
|
+
'gateway-core': { pipelineId: 5118525, repo: 'optima-gateway', saeAppId: '9326b7ff-da52-48d6-86db-9c4a884be108', prodSaeAppId: 'a08ce23f-3d3e-4d89-a2cf-53c8adba614e' },
|
|
47
|
+
'gw-admin': { pipelineId: 5118526, repo: 'optima-gateway', saeAppId: '90e0daf7-7910-46b8-b5bf-a0b8bcc60859', prodSaeAppId: 'c6bc5a78-b27f-46e2-825a-eaa338c23645' },
|
|
48
|
+
'kb-backend': { pipelineId: 5118527, repo: 'kb-skills', saeAppId: 'c7f65160-9d9e-416e-9e36-5439010d2b2d', prodSaeAppId: '732dfc8c-ed78-43f7-a722-c614359ff1a1' },
|
|
49
|
+
'ops-portal': { pipelineId: 5118529, repo: 'optima-portals', saeAppId: '41d0ee66-8402-4e72-bfda-8eba75d1270c', prodSaeAppId: '69e513fd-29bf-4355-a275-5eba82b21136' },
|
|
50
|
+
'optima-generation': { pipelineId: 5118530, repo: 'optima-gen', saeAppId: '327856d5-8b18-4e15-bfd8-b8bd3b807ffa', prodSaeAppId: '868d1a69-12c9-4bb7-868b-3e89c26784b5' },
|
|
51
|
+
'optima-generation-worker': { pipelineId: 5118531, repo: 'optima-gen', saeAppId: 'ad75b3b0-9ff4-443d-b0ef-81886bc7aa60', prodSaeAppId: '2adc941e-374b-406a-9863-e53d40bc9500' },
|
|
52
|
+
'optima-scout': { pipelineId: 5117336, repo: 'optima-scout', saeAppId: 'bac2c3a4-90ce-49a6-81d5-caa38cb5c807', prodSaeAppId: 'f5bb7e82-e57c-4bd4-83ae-2c25f8d38647' },
|
|
53
|
+
'optima-sentinel': { pipelineId: 5118532, repo: 'optima-sentinel', saeAppId: '3759db7b-4640-4a04-8436-4f241f0ec9d9', prodSaeAppId: 'e120e70c-b2c1-49ef-a66c-9016fd2d807d' },
|
|
54
|
+
'optima-sentinel-worker': { pipelineId: 5118533, repo: 'optima-sentinel', saeAppId: '7c975caf-ebfb-4bb3-91c4-2a8c83d1d2e5', prodSaeAppId: 'e0f9b92c-00af-4ade-9b07-15bba48c14cc' },
|
|
55
|
+
'optima-skills': { pipelineId: 5118534, repo: 'optima-skills', saeAppId: '90457be3-5eb3-4efc-a362-1788dcc51921', prodSaeAppId: '55a63ee7-fb75-46e4-99f3-20854a699237' },
|
|
56
|
+
'user-auth': { pipelineId: 5118510, repo: 'user-auth', saeAppId: '36efc3a6-5b42-49fa-8db2-0865aa0c25d2', prodSaeAppId: 'd6fbf9de-fed6-4165-978f-7b3a0a456acc' },
|
|
57
|
+
'user-auth-admin': { pipelineId: 5118535, repo: 'user-auth', saeAppId: '7feb9cc3-4731-4ea8-964c-4870a9e63afb', prodSaeAppId: '54093f99-37bf-4354-abd7-f6e76abdbcb6' },
|
|
58
|
+
'yzsgo-api': { pipelineId: 5118536, repo: 'yzsgo', saeAppId: 'd0af7f66-ef66-4587-9e93-250d01ee3cf6', prodSaeAppId: '5a820992-e11b-404a-9e58-8f607d8e0c5f' },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function sh(cmd: string, args: string[]): string {
|
|
62
|
+
try {
|
|
63
|
+
return execFileSync(cmd, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
64
|
+
} catch (e: any) {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function devops(action: string, kv: Record<string, string>, retries = 3): any {
|
|
70
|
+
const args = ['devops', action, '--organizationId', ORG, '--endpoint', ENDPOINT, '--profile', PROFILE];
|
|
71
|
+
for (const [k, v] of Object.entries(kv)) args.push(`--${k}`, v);
|
|
72
|
+
for (let i = 0; i < retries; i++) {
|
|
73
|
+
const out = sh('aliyun', args);
|
|
74
|
+
try { return JSON.parse(out); } catch { /* aliyun 偶发空返回/超时,重试 */ }
|
|
75
|
+
}
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
|
|
80
|
+
|
|
81
|
+
async function main() {
|
|
82
|
+
const argv = process.argv.slice(2);
|
|
83
|
+
if (argv.includes('--list') || argv.length === 0) {
|
|
84
|
+
console.log('可发服务(cn-stage):\n ' + Object.keys(SERVICES).sort().join('\n '));
|
|
85
|
+
console.log('\n用法: optima-cn-deploy <service> [--branch feat/xxx] [--no-wait]');
|
|
86
|
+
process.exit(argv.includes('--list') ? 0 : 1);
|
|
87
|
+
}
|
|
88
|
+
const svcName = argv[0];
|
|
89
|
+
const svc = SERVICES[svcName];
|
|
90
|
+
if (!svc) { console.error(`✗ 未知服务 ${svcName}(--list 看全量)`); process.exit(1); }
|
|
91
|
+
const bIdx = argv.indexOf('--branch');
|
|
92
|
+
const branch = bIdx >= 0 ? argv[bIdx + 1] : 'main';
|
|
93
|
+
const noWait = argv.includes('--no-wait');
|
|
94
|
+
const eIdx = argv.indexOf('--env');
|
|
95
|
+
const envName = eIdx >= 0 ? argv[eIdx + 1] : 'stage';
|
|
96
|
+
const vIdx = argv.indexOf('--vtag');
|
|
97
|
+
const vtag = vIdx >= 0 ? argv[vIdx + 1] : '';
|
|
98
|
+
if (!['stage', 'prod'].includes(envName)) { console.error('✗ --env 只支持 stage|prod'); process.exit(1); }
|
|
99
|
+
if (vtag && !/^cn-v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(vtag)) {
|
|
100
|
+
console.error(`✗ vtag 格式必须 cn-vX.Y.Z(得到 ${vtag});裸 v* 会误触 AWS prod,绝不放行`); process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
if (envName === 'prod') {
|
|
103
|
+
// prod = vtag 制:先 stage 同 tag 验证,再发 prod;流水线内还有人工卡点
|
|
104
|
+
if (!vtag) { console.error('✗ --env prod 必须带 --vtag cn-vX.Y.Z(先 stage 同 tag 验证)'); process.exit(1); }
|
|
105
|
+
if (branch !== 'main') { console.error('✗ prod 发布按 vtag,不接受 --branch'); process.exit(1); }
|
|
106
|
+
}
|
|
107
|
+
const ref = vtag || branch;
|
|
108
|
+
|
|
109
|
+
// 1. GitHub HEAD + mirror 同步
|
|
110
|
+
const ghSha = sh('gh', ['api', `repos/Optima-Chat/${svc.repo}/commits/${ref}`, '--jq', '.sha']);
|
|
111
|
+
if (!ghSha) { console.error(`✗ GitHub 上无 ${svc.repo}@${ref}(或 gh 未登录,tag 需已 push)`); process.exit(1); }
|
|
112
|
+
const repos = devops('ListRepositories', { perPage: '100' });
|
|
113
|
+
const repoId = (repos.result || []).find((r: any) => r.name === svc.repo)?.Id;
|
|
114
|
+
if (!repoId) { console.error(`✗ Codeup 无 mirror 仓 ${svc.repo}`); process.exit(1); }
|
|
115
|
+
const ghTok = sh('gh', ['auth', 'token']);
|
|
116
|
+
devops('TriggerRepositoryMirrorSync', { repositoryId: String(repoId), account: 'xbfool', token: ghTok });
|
|
117
|
+
let synced = false;
|
|
118
|
+
for (let i = 0; i < 30; i++) {
|
|
119
|
+
const b = devops('GetBranchInfo', { repositoryId: String(repoId), branchName: ref });
|
|
120
|
+
if (b?.result?.commit?.id === ghSha) { synced = true; break; }
|
|
121
|
+
await sleep(10000);
|
|
122
|
+
}
|
|
123
|
+
if (!synced) { console.error('✗ 300s 内 Codeup mirror 未追平 GitHub,中止(检查 mirror 凭证)'); process.exit(1); }
|
|
124
|
+
console.log(`✓ mirror 已追平 ${svc.repo}@${ref} = ${ghSha.slice(0, 10)}`);
|
|
125
|
+
|
|
126
|
+
// 2. 触发(凭证由云效变量组供给,无需注入)。prod 流水线 id 按名实时解析(不硬编码)。
|
|
127
|
+
let pipelineId = svc.pipelineId;
|
|
128
|
+
if (envName === 'prod') {
|
|
129
|
+
const lp = devops('ListPipelines', { maxResults: '100' });
|
|
130
|
+
const hit = (lp.pipelines || []).find((p: any) => p.pipelineName === `${svcName}-cn-prod`);
|
|
131
|
+
if (!hit) { console.error(`✗ 云效无 ${svcName}-cn-prod 流水线`); process.exit(1); }
|
|
132
|
+
pipelineId = hit.pipelineId;
|
|
133
|
+
}
|
|
134
|
+
const kv: Record<string, string> = { pipelineId: String(pipelineId) };
|
|
135
|
+
if (vtag) {
|
|
136
|
+
kv.params = JSON.stringify({
|
|
137
|
+
runningTags: { [`${CODEUP_BASE}/${svc.repo}.git`]: vtag },
|
|
138
|
+
envs: { VTAG: vtag },
|
|
139
|
+
});
|
|
140
|
+
} else if (branch !== 'main') {
|
|
141
|
+
kv.params = JSON.stringify({ runningBranchs: { [`${CODEUP_BASE}/${svc.repo}.git`]: branch } });
|
|
142
|
+
}
|
|
143
|
+
const start = devops('StartPipelineRun', kv);
|
|
144
|
+
const runId = start.pipelineRunId;
|
|
145
|
+
if (!runId) { console.error(`✗ 启动失败: ${JSON.stringify(start).slice(0, 150)}`); process.exit(1); }
|
|
146
|
+
console.log(`▶ ${svcName} run#${runId} 已启动 (ref=${ref}, env=${envName})`);
|
|
147
|
+
console.log(` https://flow.aliyun.com/pipelines/${pipelineId}/current`);
|
|
148
|
+
if (envName === 'prod') {
|
|
149
|
+
console.log('⏸ 构建完成后会停在『发布审批』人工卡点 —— 需 xbfool/svenyang 在云效控制台通过,之后迁移+digest 部署自动走完');
|
|
150
|
+
}
|
|
151
|
+
if (noWait) return;
|
|
152
|
+
|
|
153
|
+
// 3. 轮询
|
|
154
|
+
let status = '';
|
|
155
|
+
for (;;) {
|
|
156
|
+
const d = devops('GetPipelineRun', { pipelineId: String(pipelineId), pipelineRunId: String(runId) });
|
|
157
|
+
status = d?.pipelineRun?.status || status;
|
|
158
|
+
if (['SUCCESS', 'FAIL', 'CANCELED', 'ERROR'].includes(status)) break;
|
|
159
|
+
await sleep(45000);
|
|
160
|
+
}
|
|
161
|
+
console.log(`${status === 'SUCCESS' ? '✅' : '❌'} ${svcName} run#${runId}: ${status}`);
|
|
162
|
+
if (status !== 'SUCCESS') process.exit(1);
|
|
163
|
+
|
|
164
|
+
// 4. SAE ImageUrl 校验(空返回重试守卫)。prod 是 digest 寻址,校验含 @sha256 即部署成功。
|
|
165
|
+
const appId = envName === 'prod' ? svc.prodSaeAppId : svc.saeAppId;
|
|
166
|
+
if (appId) {
|
|
167
|
+
let img = '';
|
|
168
|
+
for (let i = 0; i < 3; i++) {
|
|
169
|
+
const raw = sh('aliyun', ['sae', '--profile', PROFILE, '--region', 'cn-beijing',
|
|
170
|
+
'DescribeApplicationConfig', '--AppId', appId]);
|
|
171
|
+
try { img = JSON.parse(raw)?.Data?.ImageUrl || ''; break; } catch { await sleep(3000); }
|
|
172
|
+
}
|
|
173
|
+
if (!img) { console.log('⚠️ SAE 配置查询连续空返回,跳过 ImageUrl 校验(流水线本身已 SUCCESS)'); return; }
|
|
174
|
+
if (envName === 'prod') {
|
|
175
|
+
const ok = img.includes('@sha256:');
|
|
176
|
+
console.log(`${ok ? '✓' : '✗'} SAE ImageUrl(prod 应为 digest 寻址): ${img.split('/').pop()}`);
|
|
177
|
+
if (!ok) process.exit(1);
|
|
178
|
+
} else {
|
|
179
|
+
const tag = img.split(':').pop() || '';
|
|
180
|
+
const ok = ghSha.startsWith(tag);
|
|
181
|
+
console.log(`${ok ? '✓' : '✗'} SAE ImageUrl tag=${tag} vs 目标 ${ghSha.slice(0, 10)}`);
|
|
182
|
+
if (!ok) process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
main().catch(e => { console.error('✗', e?.message || e); process.exit(1); });
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
/**
|
|
5
|
+
* optima-cn-deploy —— 云效 Flow cn-stage 流水线一条龙触发器(团队版)。
|
|
6
|
+
*
|
|
7
|
+
* 对应 optima-terraform alicloud/stacks/cn-prod-buildbox/yunxiao/ 的 cn-run.py,
|
|
8
|
+
* 去掉 buildbox SSH 依赖(凭证由云效变量组 41970 供给,2026-07-13 起控制台/API 裸跑均可)。
|
|
9
|
+
*
|
|
10
|
+
* 做四件事(每件都是踩过的坑):
|
|
11
|
+
* 1. 触发服务仓 Codeup mirror 同步并等目标分支追平 GitHub —— mirror 不新鲜=构建旧代码。
|
|
12
|
+
* GitHub token 用 `gh auth token`(需本机 gh 已登录)。
|
|
13
|
+
* 2. StartPipelineRun;--branch 时带 runningBranchs 覆盖服务仓 source 分支。
|
|
14
|
+
* 3. 轮询到终态,给出云效 run 链接。
|
|
15
|
+
* 4. SUCCESS 后校验 SAE ImageUrl tag == 目标分支 HEAD short-sha(防"流水线绿但没部上");
|
|
16
|
+
* aliyun API 偶发空返回,带重试守卫。
|
|
17
|
+
*
|
|
18
|
+
* 用法:
|
|
19
|
+
* optima-cn-deploy billing # main 全链(构建→迁移→SAE 发布)
|
|
20
|
+
* optima-cn-deploy user-auth --branch feat/xxx # 非 main 分支构建+部署到 cn-stage
|
|
21
|
+
* optima-cn-deploy gateway-core --no-wait # 只触发不等待
|
|
22
|
+
* optima-cn-deploy --list # 列出全部可发服务
|
|
23
|
+
* optima-cn-deploy billing --vtag cn-v1.2.3 # stage 按版本 tag 构建(发版前验证)
|
|
24
|
+
* optima-cn-deploy billing --env prod --vtag cn-v1.2.3
|
|
25
|
+
* # cn-prod vtag 发版:构建→停在人工卡点(云效控制台审批)→迁移→digest 钉死部署
|
|
26
|
+
*
|
|
27
|
+
* 前置: aliyun CLI(profile 默认 aliyun-optima,可用 OPTIMA_ALIYUN_PROFILE 覆盖)+ gh 已登录。
|
|
28
|
+
* 流水线定义的单一信源在 optima-terraform yunxiao/(gen-pipelines.py);本表为其快照,
|
|
29
|
+
* 新增服务后同步(pipelineId 稳定,不常变)。
|
|
30
|
+
*/
|
|
31
|
+
const node_child_process_1 = require("node:child_process");
|
|
32
|
+
const ORG = '6a17b6282bf8b1184fc0e0c6';
|
|
33
|
+
const ENDPOINT = 'devops.cn-hangzhou.aliyuncs.com';
|
|
34
|
+
const PROFILE = process.env.OPTIMA_ALIYUN_PROFILE || 'aliyun-optima';
|
|
35
|
+
const CODEUP_BASE = `https://codeup.aliyun.com/${ORG}`;
|
|
36
|
+
const SERVICES = {
|
|
37
|
+
'agent-portal': { pipelineId: 5118519, repo: 'optima-portals', saeAppId: 'fe757f78-d18d-4480-9402-fe59d4721055', prodSaeAppId: '9211db38-a255-4393-ae92-7a7bb41b583d' },
|
|
38
|
+
'agentic-chat': { pipelineId: 5118520, repo: 'agentic-chat', saeAppId: '6aea1ce1-f813-4e1c-8e97-d1ecb5398e37', prodSaeAppId: '6e290c73-a646-43ef-9da5-ad0b2e7eff73' },
|
|
39
|
+
'billing': { pipelineId: 5118521, repo: 'optima-billing', saeAppId: '09d8e292-dc64-4af8-bce5-0a56cb666921', prodSaeAppId: '6c31cf82-8802-4d45-b6a2-e9d7c83ccce9' },
|
|
40
|
+
'browser-backend': { pipelineId: 5118522, repo: 'optima-browser-use', saeAppId: '1fced3f6-a80a-41a4-8f23-e4d5a467f8eb', prodSaeAppId: 'eb782ed9-c468-4e81-a0fe-87e5b4264192' },
|
|
41
|
+
'commerce-backend': { pipelineId: 5107005, repo: 'commerce-backend', saeAppId: '49d09808-508c-471a-9560-553c49a67f72', prodSaeAppId: 'd40597b6-a98c-4063-9297-8b9fdfa8add4' },
|
|
42
|
+
'commerce-rq-scheduler': { pipelineId: 5118523, repo: 'commerce-backend', saeAppId: '1d2810ef-889e-4675-b6e6-a299e4722e68', prodSaeAppId: '762dd7a5-7fe8-41ed-9332-cfd31b0e087e' },
|
|
43
|
+
'commerce-rq-worker': { pipelineId: 5118524, repo: 'commerce-backend', saeAppId: 'd661da87-6d24-40b8-93ed-e1c967d8abe2', prodSaeAppId: '2c9ee19a-5062-4205-9f5b-6ae3f8062a23' },
|
|
44
|
+
'gateway-core': { pipelineId: 5118525, repo: 'optima-gateway', saeAppId: '9326b7ff-da52-48d6-86db-9c4a884be108', prodSaeAppId: 'a08ce23f-3d3e-4d89-a2cf-53c8adba614e' },
|
|
45
|
+
'gw-admin': { pipelineId: 5118526, repo: 'optima-gateway', saeAppId: '90e0daf7-7910-46b8-b5bf-a0b8bcc60859', prodSaeAppId: 'c6bc5a78-b27f-46e2-825a-eaa338c23645' },
|
|
46
|
+
'kb-backend': { pipelineId: 5118527, repo: 'kb-skills', saeAppId: 'c7f65160-9d9e-416e-9e36-5439010d2b2d', prodSaeAppId: '732dfc8c-ed78-43f7-a722-c614359ff1a1' },
|
|
47
|
+
'ops-portal': { pipelineId: 5118529, repo: 'optima-portals', saeAppId: '41d0ee66-8402-4e72-bfda-8eba75d1270c', prodSaeAppId: '69e513fd-29bf-4355-a275-5eba82b21136' },
|
|
48
|
+
'optima-generation': { pipelineId: 5118530, repo: 'optima-gen', saeAppId: '327856d5-8b18-4e15-bfd8-b8bd3b807ffa', prodSaeAppId: '868d1a69-12c9-4bb7-868b-3e89c26784b5' },
|
|
49
|
+
'optima-generation-worker': { pipelineId: 5118531, repo: 'optima-gen', saeAppId: 'ad75b3b0-9ff4-443d-b0ef-81886bc7aa60', prodSaeAppId: '2adc941e-374b-406a-9863-e53d40bc9500' },
|
|
50
|
+
'optima-scout': { pipelineId: 5117336, repo: 'optima-scout', saeAppId: 'bac2c3a4-90ce-49a6-81d5-caa38cb5c807', prodSaeAppId: 'f5bb7e82-e57c-4bd4-83ae-2c25f8d38647' },
|
|
51
|
+
'optima-sentinel': { pipelineId: 5118532, repo: 'optima-sentinel', saeAppId: '3759db7b-4640-4a04-8436-4f241f0ec9d9', prodSaeAppId: 'e120e70c-b2c1-49ef-a66c-9016fd2d807d' },
|
|
52
|
+
'optima-sentinel-worker': { pipelineId: 5118533, repo: 'optima-sentinel', saeAppId: '7c975caf-ebfb-4bb3-91c4-2a8c83d1d2e5', prodSaeAppId: 'e0f9b92c-00af-4ade-9b07-15bba48c14cc' },
|
|
53
|
+
'optima-skills': { pipelineId: 5118534, repo: 'optima-skills', saeAppId: '90457be3-5eb3-4efc-a362-1788dcc51921', prodSaeAppId: '55a63ee7-fb75-46e4-99f3-20854a699237' },
|
|
54
|
+
'user-auth': { pipelineId: 5118510, repo: 'user-auth', saeAppId: '36efc3a6-5b42-49fa-8db2-0865aa0c25d2', prodSaeAppId: 'd6fbf9de-fed6-4165-978f-7b3a0a456acc' },
|
|
55
|
+
'user-auth-admin': { pipelineId: 5118535, repo: 'user-auth', saeAppId: '7feb9cc3-4731-4ea8-964c-4870a9e63afb', prodSaeAppId: '54093f99-37bf-4354-abd7-f6e76abdbcb6' },
|
|
56
|
+
'yzsgo-api': { pipelineId: 5118536, repo: 'yzsgo', saeAppId: 'd0af7f66-ef66-4587-9e93-250d01ee3cf6', prodSaeAppId: '5a820992-e11b-404a-9e58-8f607d8e0c5f' },
|
|
57
|
+
};
|
|
58
|
+
function sh(cmd, args) {
|
|
59
|
+
try {
|
|
60
|
+
return (0, node_child_process_1.execFileSync)(cmd, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function devops(action, kv, retries = 3) {
|
|
67
|
+
const args = ['devops', action, '--organizationId', ORG, '--endpoint', ENDPOINT, '--profile', PROFILE];
|
|
68
|
+
for (const [k, v] of Object.entries(kv))
|
|
69
|
+
args.push(`--${k}`, v);
|
|
70
|
+
for (let i = 0; i < retries; i++) {
|
|
71
|
+
const out = sh('aliyun', args);
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(out);
|
|
74
|
+
}
|
|
75
|
+
catch { /* aliyun 偶发空返回/超时,重试 */ }
|
|
76
|
+
}
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
80
|
+
async function main() {
|
|
81
|
+
const argv = process.argv.slice(2);
|
|
82
|
+
if (argv.includes('--list') || argv.length === 0) {
|
|
83
|
+
console.log('可发服务(cn-stage):\n ' + Object.keys(SERVICES).sort().join('\n '));
|
|
84
|
+
console.log('\n用法: optima-cn-deploy <service> [--branch feat/xxx] [--no-wait]');
|
|
85
|
+
process.exit(argv.includes('--list') ? 0 : 1);
|
|
86
|
+
}
|
|
87
|
+
const svcName = argv[0];
|
|
88
|
+
const svc = SERVICES[svcName];
|
|
89
|
+
if (!svc) {
|
|
90
|
+
console.error(`✗ 未知服务 ${svcName}(--list 看全量)`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
const bIdx = argv.indexOf('--branch');
|
|
94
|
+
const branch = bIdx >= 0 ? argv[bIdx + 1] : 'main';
|
|
95
|
+
const noWait = argv.includes('--no-wait');
|
|
96
|
+
const eIdx = argv.indexOf('--env');
|
|
97
|
+
const envName = eIdx >= 0 ? argv[eIdx + 1] : 'stage';
|
|
98
|
+
const vIdx = argv.indexOf('--vtag');
|
|
99
|
+
const vtag = vIdx >= 0 ? argv[vIdx + 1] : '';
|
|
100
|
+
if (!['stage', 'prod'].includes(envName)) {
|
|
101
|
+
console.error('✗ --env 只支持 stage|prod');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
if (vtag && !/^cn-v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(vtag)) {
|
|
105
|
+
console.error(`✗ vtag 格式必须 cn-vX.Y.Z(得到 ${vtag});裸 v* 会误触 AWS prod,绝不放行`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
if (envName === 'prod') {
|
|
109
|
+
// prod = vtag 制:先 stage 同 tag 验证,再发 prod;流水线内还有人工卡点
|
|
110
|
+
if (!vtag) {
|
|
111
|
+
console.error('✗ --env prod 必须带 --vtag cn-vX.Y.Z(先 stage 同 tag 验证)');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
if (branch !== 'main') {
|
|
115
|
+
console.error('✗ prod 发布按 vtag,不接受 --branch');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const ref = vtag || branch;
|
|
120
|
+
// 1. GitHub HEAD + mirror 同步
|
|
121
|
+
const ghSha = sh('gh', ['api', `repos/Optima-Chat/${svc.repo}/commits/${ref}`, '--jq', '.sha']);
|
|
122
|
+
if (!ghSha) {
|
|
123
|
+
console.error(`✗ GitHub 上无 ${svc.repo}@${ref}(或 gh 未登录,tag 需已 push)`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
const repos = devops('ListRepositories', { perPage: '100' });
|
|
127
|
+
const repoId = (repos.result || []).find((r) => r.name === svc.repo)?.Id;
|
|
128
|
+
if (!repoId) {
|
|
129
|
+
console.error(`✗ Codeup 无 mirror 仓 ${svc.repo}`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
const ghTok = sh('gh', ['auth', 'token']);
|
|
133
|
+
devops('TriggerRepositoryMirrorSync', { repositoryId: String(repoId), account: 'xbfool', token: ghTok });
|
|
134
|
+
let synced = false;
|
|
135
|
+
for (let i = 0; i < 30; i++) {
|
|
136
|
+
const b = devops('GetBranchInfo', { repositoryId: String(repoId), branchName: ref });
|
|
137
|
+
if (b?.result?.commit?.id === ghSha) {
|
|
138
|
+
synced = true;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
await sleep(10000);
|
|
142
|
+
}
|
|
143
|
+
if (!synced) {
|
|
144
|
+
console.error('✗ 300s 内 Codeup mirror 未追平 GitHub,中止(检查 mirror 凭证)');
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
console.log(`✓ mirror 已追平 ${svc.repo}@${ref} = ${ghSha.slice(0, 10)}`);
|
|
148
|
+
// 2. 触发(凭证由云效变量组供给,无需注入)。prod 流水线 id 按名实时解析(不硬编码)。
|
|
149
|
+
let pipelineId = svc.pipelineId;
|
|
150
|
+
if (envName === 'prod') {
|
|
151
|
+
const lp = devops('ListPipelines', { maxResults: '100' });
|
|
152
|
+
const hit = (lp.pipelines || []).find((p) => p.pipelineName === `${svcName}-cn-prod`);
|
|
153
|
+
if (!hit) {
|
|
154
|
+
console.error(`✗ 云效无 ${svcName}-cn-prod 流水线`);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
pipelineId = hit.pipelineId;
|
|
158
|
+
}
|
|
159
|
+
const kv = { pipelineId: String(pipelineId) };
|
|
160
|
+
if (vtag) {
|
|
161
|
+
kv.params = JSON.stringify({
|
|
162
|
+
runningTags: { [`${CODEUP_BASE}/${svc.repo}.git`]: vtag },
|
|
163
|
+
envs: { VTAG: vtag },
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else if (branch !== 'main') {
|
|
167
|
+
kv.params = JSON.stringify({ runningBranchs: { [`${CODEUP_BASE}/${svc.repo}.git`]: branch } });
|
|
168
|
+
}
|
|
169
|
+
const start = devops('StartPipelineRun', kv);
|
|
170
|
+
const runId = start.pipelineRunId;
|
|
171
|
+
if (!runId) {
|
|
172
|
+
console.error(`✗ 启动失败: ${JSON.stringify(start).slice(0, 150)}`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
console.log(`▶ ${svcName} run#${runId} 已启动 (ref=${ref}, env=${envName})`);
|
|
176
|
+
console.log(` https://flow.aliyun.com/pipelines/${pipelineId}/current`);
|
|
177
|
+
if (envName === 'prod') {
|
|
178
|
+
console.log('⏸ 构建完成后会停在『发布审批』人工卡点 —— 需 xbfool/svenyang 在云效控制台通过,之后迁移+digest 部署自动走完');
|
|
179
|
+
}
|
|
180
|
+
if (noWait)
|
|
181
|
+
return;
|
|
182
|
+
// 3. 轮询
|
|
183
|
+
let status = '';
|
|
184
|
+
for (;;) {
|
|
185
|
+
const d = devops('GetPipelineRun', { pipelineId: String(pipelineId), pipelineRunId: String(runId) });
|
|
186
|
+
status = d?.pipelineRun?.status || status;
|
|
187
|
+
if (['SUCCESS', 'FAIL', 'CANCELED', 'ERROR'].includes(status))
|
|
188
|
+
break;
|
|
189
|
+
await sleep(45000);
|
|
190
|
+
}
|
|
191
|
+
console.log(`${status === 'SUCCESS' ? '✅' : '❌'} ${svcName} run#${runId}: ${status}`);
|
|
192
|
+
if (status !== 'SUCCESS')
|
|
193
|
+
process.exit(1);
|
|
194
|
+
// 4. SAE ImageUrl 校验(空返回重试守卫)。prod 是 digest 寻址,校验含 @sha256 即部署成功。
|
|
195
|
+
const appId = envName === 'prod' ? svc.prodSaeAppId : svc.saeAppId;
|
|
196
|
+
if (appId) {
|
|
197
|
+
let img = '';
|
|
198
|
+
for (let i = 0; i < 3; i++) {
|
|
199
|
+
const raw = sh('aliyun', ['sae', '--profile', PROFILE, '--region', 'cn-beijing',
|
|
200
|
+
'DescribeApplicationConfig', '--AppId', appId]);
|
|
201
|
+
try {
|
|
202
|
+
img = JSON.parse(raw)?.Data?.ImageUrl || '';
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
await sleep(3000);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (!img) {
|
|
210
|
+
console.log('⚠️ SAE 配置查询连续空返回,跳过 ImageUrl 校验(流水线本身已 SUCCESS)');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (envName === 'prod') {
|
|
214
|
+
const ok = img.includes('@sha256:');
|
|
215
|
+
console.log(`${ok ? '✓' : '✗'} SAE ImageUrl(prod 应为 digest 寻址): ${img.split('/').pop()}`);
|
|
216
|
+
if (!ok)
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
const tag = img.split(':').pop() || '';
|
|
221
|
+
const ok = ghSha.startsWith(tag);
|
|
222
|
+
console.log(`${ok ? '✓' : '✗'} SAE ImageUrl tag=${tag} vs 目标 ${ghSha.slice(0, 10)}`);
|
|
223
|
+
if (!ok)
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
main().catch(e => { console.error('✗', e?.message || e); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optima-chat/dev-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Claude Code Skills for Optima development team - cross-environment collaboration tools",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"optima-product": "dist/bin/helpers/product.js",
|
|
18
18
|
"optima-query-db": "dist/bin/helpers/query-db.js",
|
|
19
19
|
"optima-show-env": "dist/bin/helpers/show-env.js",
|
|
20
|
-
"optima-verify-health": "dist/bin/helpers/verify-health.js"
|
|
20
|
+
"optima-verify-health": "dist/bin/helpers/verify-health.js",
|
|
21
|
+
"optima-cn-deploy": "dist/bin/helpers/cn-deploy.js"
|
|
21
22
|
},
|
|
22
23
|
"scripts": {
|
|
23
24
|
"postinstall": "node scripts/install.js",
|
|
@@ -62,4 +63,4 @@
|
|
|
62
63
|
"ts-node": "^10.9.2",
|
|
63
64
|
"typescript": "^5.9.3"
|
|
64
65
|
}
|
|
65
|
-
}
|
|
66
|
+
}
|