@optima-chat/dev-skills 0.12.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.
@@ -28,6 +28,19 @@ optima-cn-deploy --list # 列出全部可发服务
28
28
  5. 失败排查:命令输出的 flow.aliyun.com 链接里看具体 step 日志;常见原因是该分支/仓的 Codeup mirror 凭证问题(命令会在 mirror 步就报错并中止,不会构建旧代码)。
29
29
  6. 成功标准:最后一行 `✓ SAE ImageUrl tag=<sha>` —— 流水线绿且 SAE 真的钉到了目标 commit。只看流水线绿不算完成。
30
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
+
31
44
  ## 前置依赖
32
45
 
33
46
  - `aliyun` CLI 已配置(profile 默认 `aliyun-optima`,可用环境变量 `OPTIMA_ALIYUN_PROFILE` 覆盖为自己的 RAM 用户 profile)
@@ -35,5 +48,5 @@ optima-cn-deploy --list # 列出全部可发服务
35
48
 
36
49
  ## 边界
37
50
 
38
- - 只发 **cn-stage**,不发 cn-prod(cn-prod 发布另走人工卡点流程)。
51
+ - cn-prod 仅走 vtag 制(见上),且有人工卡点闸门;日常无 vtag 的构建只发 cn-stage。
39
52
  - 服务注册表是 optima-terraform `alicloud/stacks/cn-prod-buildbox/yunxiao/` 的快照;新增服务先在那边 gen-pipelines 建好流水线,再同步本工具的 SERVICES 表。
@@ -28,6 +28,19 @@ optima-cn-deploy --list # 列出全部可发服务
28
28
  5. 失败排查:命令输出的 flow.aliyun.com 链接里看具体 step 日志;常见原因是该分支/仓的 Codeup mirror 凭证问题(命令会在 mirror 步就报错并中止,不会构建旧代码)。
29
29
  6. 成功标准:最后一行 `✓ SAE ImageUrl tag=<sha>` —— 流水线绿且 SAE 真的钉到了目标 commit。只看流水线绿不算完成。
30
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
+
31
44
  ## 前置依赖
32
45
 
33
46
  - `aliyun` CLI 已配置(profile 默认 `aliyun-optima`,可用环境变量 `OPTIMA_ALIYUN_PROFILE` 覆盖为自己的 RAM 用户 profile)
@@ -35,5 +48,5 @@ optima-cn-deploy --list # 列出全部可发服务
35
48
 
36
49
  ## 边界
37
50
 
38
- - 只发 **cn-stage**,不发 cn-prod(cn-prod 发布另走人工卡点流程)。
51
+ - cn-prod 仅走 vtag 制(见上),且有人工卡点闸门;日常无 vtag 的构建只发 cn-stage。
39
52
  - 服务注册表是 optima-terraform `alicloud/stacks/cn-prod-buildbox/yunxiao/` 的快照;新增服务先在那边 gen-pipelines 建好流水线,再同步本工具的 SERVICES 表。
@@ -18,6 +18,9 @@
18
18
  * optima-cn-deploy user-auth --branch feat/xxx # 非 main 分支构建+部署到 cn-stage
19
19
  * optima-cn-deploy gateway-core --no-wait # 只触发不等待
20
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 钉死部署
21
24
  *
22
25
  * 前置: aliyun CLI(profile 默认 aliyun-optima,可用 OPTIMA_ALIYUN_PROFILE 覆盖)+ gh 已登录。
23
26
  * 流水线定义的单一信源在 optima-terraform yunxiao/(gen-pipelines.py);本表为其快照,
@@ -31,28 +34,28 @@ const PROFILE = process.env.OPTIMA_ALIYUN_PROFILE || 'aliyun-optima';
31
34
  const CODEUP_BASE = `https://codeup.aliyun.com/${ORG}`;
32
35
 
33
36
  // 服务注册表(快照自 optima-terraform services.stage.env + gen-pipelines.py,2026-07-13)
34
- interface Svc { pipelineId: number; repo: string; saeAppId?: string; }
37
+ interface Svc { pipelineId: number; repo: string; saeAppId?: string; prodSaeAppId?: string; }
35
38
  const SERVICES: Record<string, Svc> = {
36
- 'agent-portal': { pipelineId: 5118519, repo: 'optima-portals', saeAppId: 'fe757f78-d18d-4480-9402-fe59d4721055' },
37
- 'agentic-chat': { pipelineId: 5118520, repo: 'agentic-chat', saeAppId: '6aea1ce1-f813-4e1c-8e97-d1ecb5398e37' },
38
- 'billing': { pipelineId: 5118521, repo: 'optima-billing', saeAppId: '09d8e292-dc64-4af8-bce5-0a56cb666921' },
39
- 'browser-backend': { pipelineId: 5118522, repo: 'optima-browser-use', saeAppId: '1fced3f6-a80a-41a4-8f23-e4d5a467f8eb' },
40
- 'commerce-backend': { pipelineId: 5107005, repo: 'commerce-backend', saeAppId: '49d09808-508c-471a-9560-553c49a67f72' },
41
- 'commerce-rq-scheduler': { pipelineId: 5118523, repo: 'commerce-backend', saeAppId: '1d2810ef-889e-4675-b6e6-a299e4722e68' },
42
- 'commerce-rq-worker': { pipelineId: 5118524, repo: 'commerce-backend', saeAppId: 'd661da87-6d24-40b8-93ed-e1c967d8abe2' },
43
- 'gateway-core': { pipelineId: 5118525, repo: 'optima-gateway', saeAppId: '9326b7ff-da52-48d6-86db-9c4a884be108' },
44
- 'gw-admin': { pipelineId: 5118526, repo: 'optima-gateway', saeAppId: '90e0daf7-7910-46b8-b5bf-a0b8bcc60859' },
45
- 'kb-backend': { pipelineId: 5118527, repo: 'kb-skills', saeAppId: 'c7f65160-9d9e-416e-9e36-5439010d2b2d' },
46
- 'ops-portal': { pipelineId: 5118529, repo: 'optima-portals', saeAppId: '41d0ee66-8402-4e72-bfda-8eba75d1270c' },
47
- 'optima-generation': { pipelineId: 5118530, repo: 'optima-gen', saeAppId: '327856d5-8b18-4e15-bfd8-b8bd3b807ffa' },
48
- 'optima-generation-worker': { pipelineId: 5118531, repo: 'optima-gen', saeAppId: 'ad75b3b0-9ff4-443d-b0ef-81886bc7aa60' },
49
- 'optima-scout': { pipelineId: 5117336, repo: 'optima-scout', saeAppId: 'bac2c3a4-90ce-49a6-81d5-caa38cb5c807' },
50
- 'optima-sentinel': { pipelineId: 5118532, repo: 'optima-sentinel', saeAppId: '3759db7b-4640-4a04-8436-4f241f0ec9d9' },
51
- 'optima-sentinel-worker': { pipelineId: 5118533, repo: 'optima-sentinel', saeAppId: '7c975caf-ebfb-4bb3-91c4-2a8c83d1d2e5' },
52
- 'optima-skills': { pipelineId: 5118534, repo: 'optima-skills', saeAppId: '90457be3-5eb3-4efc-a362-1788dcc51921' },
53
- 'user-auth': { pipelineId: 5118510, repo: 'user-auth', saeAppId: '36efc3a6-5b42-49fa-8db2-0865aa0c25d2' },
54
- 'user-auth-admin': { pipelineId: 5118535, repo: 'user-auth', saeAppId: '7feb9cc3-4731-4ea8-964c-4870a9e63afb' },
55
- 'yzsgo-api': { pipelineId: 5118536, repo: 'yzsgo', saeAppId: 'd0af7f66-ef66-4587-9e93-250d01ee3cf6' },
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' },
56
59
  };
57
60
 
58
61
  function sh(cmd: string, args: string[]): string {
@@ -88,10 +91,24 @@ async function main() {
88
91
  const bIdx = argv.indexOf('--branch');
89
92
  const branch = bIdx >= 0 ? argv[bIdx + 1] : 'main';
90
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;
91
108
 
92
109
  // 1. GitHub HEAD + mirror 同步
93
- const ghSha = sh('gh', ['api', `repos/Optima-Chat/${svc.repo}/commits/${branch}`, '--jq', '.sha']);
94
- if (!ghSha) { console.error(`✗ GitHub 上无 ${svc.repo}@${branch}(或 gh 未登录)`); process.exit(1); }
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); }
95
112
  const repos = devops('ListRepositories', { perPage: '100' });
96
113
  const repoId = (repos.result || []).find((r: any) => r.name === svc.repo)?.Id;
97
114
  if (!repoId) { console.error(`✗ Codeup 无 mirror 仓 ${svc.repo}`); process.exit(1); }
@@ -99,29 +116,44 @@ async function main() {
99
116
  devops('TriggerRepositoryMirrorSync', { repositoryId: String(repoId), account: 'xbfool', token: ghTok });
100
117
  let synced = false;
101
118
  for (let i = 0; i < 30; i++) {
102
- const b = devops('GetBranchInfo', { repositoryId: String(repoId), branchName: branch });
119
+ const b = devops('GetBranchInfo', { repositoryId: String(repoId), branchName: ref });
103
120
  if (b?.result?.commit?.id === ghSha) { synced = true; break; }
104
121
  await sleep(10000);
105
122
  }
106
123
  if (!synced) { console.error('✗ 300s 内 Codeup mirror 未追平 GitHub,中止(检查 mirror 凭证)'); process.exit(1); }
107
- console.log(`✓ mirror 已追平 ${svc.repo}@${branch} = ${ghSha.slice(0, 10)}`);
124
+ console.log(`✓ mirror 已追平 ${svc.repo}@${ref} = ${ghSha.slice(0, 10)}`);
108
125
 
109
- // 2. 触发(凭证由云效变量组 41970 供给,无需注入)
110
- const kv: Record<string, string> = { pipelineId: String(svc.pipelineId) };
111
- if (branch !== 'main') {
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') {
112
141
  kv.params = JSON.stringify({ runningBranchs: { [`${CODEUP_BASE}/${svc.repo}.git`]: branch } });
113
142
  }
114
143
  const start = devops('StartPipelineRun', kv);
115
144
  const runId = start.pipelineRunId;
116
145
  if (!runId) { console.error(`✗ 启动失败: ${JSON.stringify(start).slice(0, 150)}`); process.exit(1); }
117
- console.log(`▶ ${svcName} run#${runId} 已启动 (branch=${branch})`);
118
- console.log(` https://flow.aliyun.com/pipelines/${svc.pipelineId}/current`);
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
+ }
119
151
  if (noWait) return;
120
152
 
121
153
  // 3. 轮询
122
154
  let status = '';
123
155
  for (;;) {
124
- const d = devops('GetPipelineRun', { pipelineId: String(svc.pipelineId), pipelineRunId: String(runId) });
156
+ const d = devops('GetPipelineRun', { pipelineId: String(pipelineId), pipelineRunId: String(runId) });
125
157
  status = d?.pipelineRun?.status || status;
126
158
  if (['SUCCESS', 'FAIL', 'CANCELED', 'ERROR'].includes(status)) break;
127
159
  await sleep(45000);
@@ -129,19 +161,26 @@ async function main() {
129
161
  console.log(`${status === 'SUCCESS' ? '✅' : '❌'} ${svcName} run#${runId}: ${status}`);
130
162
  if (status !== 'SUCCESS') process.exit(1);
131
163
 
132
- // 4. SAE ImageUrl 校验(空返回重试守卫)
133
- if (svc.saeAppId) {
164
+ // 4. SAE ImageUrl 校验(空返回重试守卫)。prod 是 digest 寻址,校验含 @sha256 即部署成功。
165
+ const appId = envName === 'prod' ? svc.prodSaeAppId : svc.saeAppId;
166
+ if (appId) {
134
167
  let img = '';
135
168
  for (let i = 0; i < 3; i++) {
136
169
  const raw = sh('aliyun', ['sae', '--profile', PROFILE, '--region', 'cn-beijing',
137
- 'DescribeApplicationConfig', '--AppId', svc.saeAppId]);
170
+ 'DescribeApplicationConfig', '--AppId', appId]);
138
171
  try { img = JSON.parse(raw)?.Data?.ImageUrl || ''; break; } catch { await sleep(3000); }
139
172
  }
140
173
  if (!img) { console.log('⚠️ SAE 配置查询连续空返回,跳过 ImageUrl 校验(流水线本身已 SUCCESS)'); return; }
141
- const tag = img.split(':').pop() || '';
142
- const ok = ghSha.startsWith(tag);
143
- console.log(`${ok ? '✓' : '✗'} SAE ImageUrl tag=${tag} vs 目标 ${ghSha.slice(0, 10)}`);
144
- if (!ok) process.exit(1);
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
+ }
145
184
  }
146
185
  }
147
186
 
@@ -20,6 +20,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
20
20
  * optima-cn-deploy user-auth --branch feat/xxx # 非 main 分支构建+部署到 cn-stage
21
21
  * optima-cn-deploy gateway-core --no-wait # 只触发不等待
22
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 钉死部署
23
26
  *
24
27
  * 前置: aliyun CLI(profile 默认 aliyun-optima,可用 OPTIMA_ALIYUN_PROFILE 覆盖)+ gh 已登录。
25
28
  * 流水线定义的单一信源在 optima-terraform yunxiao/(gen-pipelines.py);本表为其快照,
@@ -31,26 +34,26 @@ const ENDPOINT = 'devops.cn-hangzhou.aliyuncs.com';
31
34
  const PROFILE = process.env.OPTIMA_ALIYUN_PROFILE || 'aliyun-optima';
32
35
  const CODEUP_BASE = `https://codeup.aliyun.com/${ORG}`;
33
36
  const SERVICES = {
34
- 'agent-portal': { pipelineId: 5118519, repo: 'optima-portals', saeAppId: 'fe757f78-d18d-4480-9402-fe59d4721055' },
35
- 'agentic-chat': { pipelineId: 5118520, repo: 'agentic-chat', saeAppId: '6aea1ce1-f813-4e1c-8e97-d1ecb5398e37' },
36
- 'billing': { pipelineId: 5118521, repo: 'optima-billing', saeAppId: '09d8e292-dc64-4af8-bce5-0a56cb666921' },
37
- 'browser-backend': { pipelineId: 5118522, repo: 'optima-browser-use', saeAppId: '1fced3f6-a80a-41a4-8f23-e4d5a467f8eb' },
38
- 'commerce-backend': { pipelineId: 5107005, repo: 'commerce-backend', saeAppId: '49d09808-508c-471a-9560-553c49a67f72' },
39
- 'commerce-rq-scheduler': { pipelineId: 5118523, repo: 'commerce-backend', saeAppId: '1d2810ef-889e-4675-b6e6-a299e4722e68' },
40
- 'commerce-rq-worker': { pipelineId: 5118524, repo: 'commerce-backend', saeAppId: 'd661da87-6d24-40b8-93ed-e1c967d8abe2' },
41
- 'gateway-core': { pipelineId: 5118525, repo: 'optima-gateway', saeAppId: '9326b7ff-da52-48d6-86db-9c4a884be108' },
42
- 'gw-admin': { pipelineId: 5118526, repo: 'optima-gateway', saeAppId: '90e0daf7-7910-46b8-b5bf-a0b8bcc60859' },
43
- 'kb-backend': { pipelineId: 5118527, repo: 'kb-skills', saeAppId: 'c7f65160-9d9e-416e-9e36-5439010d2b2d' },
44
- 'ops-portal': { pipelineId: 5118529, repo: 'optima-portals', saeAppId: '41d0ee66-8402-4e72-bfda-8eba75d1270c' },
45
- 'optima-generation': { pipelineId: 5118530, repo: 'optima-gen', saeAppId: '327856d5-8b18-4e15-bfd8-b8bd3b807ffa' },
46
- 'optima-generation-worker': { pipelineId: 5118531, repo: 'optima-gen', saeAppId: 'ad75b3b0-9ff4-443d-b0ef-81886bc7aa60' },
47
- 'optima-scout': { pipelineId: 5117336, repo: 'optima-scout', saeAppId: 'bac2c3a4-90ce-49a6-81d5-caa38cb5c807' },
48
- 'optima-sentinel': { pipelineId: 5118532, repo: 'optima-sentinel', saeAppId: '3759db7b-4640-4a04-8436-4f241f0ec9d9' },
49
- 'optima-sentinel-worker': { pipelineId: 5118533, repo: 'optima-sentinel', saeAppId: '7c975caf-ebfb-4bb3-91c4-2a8c83d1d2e5' },
50
- 'optima-skills': { pipelineId: 5118534, repo: 'optima-skills', saeAppId: '90457be3-5eb3-4efc-a362-1788dcc51921' },
51
- 'user-auth': { pipelineId: 5118510, repo: 'user-auth', saeAppId: '36efc3a6-5b42-49fa-8db2-0865aa0c25d2' },
52
- 'user-auth-admin': { pipelineId: 5118535, repo: 'user-auth', saeAppId: '7feb9cc3-4731-4ea8-964c-4870a9e63afb' },
53
- 'yzsgo-api': { pipelineId: 5118536, repo: 'yzsgo', saeAppId: 'd0af7f66-ef66-4587-9e93-250d01ee3cf6' },
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' },
54
57
  };
55
58
  function sh(cmd, args) {
56
59
  try {
@@ -90,10 +93,34 @@ async function main() {
90
93
  const bIdx = argv.indexOf('--branch');
91
94
  const branch = bIdx >= 0 ? argv[bIdx + 1] : 'main';
92
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;
93
120
  // 1. GitHub HEAD + mirror 同步
94
- const ghSha = sh('gh', ['api', `repos/Optima-Chat/${svc.repo}/commits/${branch}`, '--jq', '.sha']);
121
+ const ghSha = sh('gh', ['api', `repos/Optima-Chat/${svc.repo}/commits/${ref}`, '--jq', '.sha']);
95
122
  if (!ghSha) {
96
- console.error(`✗ GitHub 上无 ${svc.repo}@${branch}(或 gh 未登录)`);
123
+ console.error(`✗ GitHub 上无 ${svc.repo}@${ref}(或 gh 未登录,tag 需已 push)`);
97
124
  process.exit(1);
98
125
  }
99
126
  const repos = devops('ListRepositories', { perPage: '100' });
@@ -106,7 +133,7 @@ async function main() {
106
133
  devops('TriggerRepositoryMirrorSync', { repositoryId: String(repoId), account: 'xbfool', token: ghTok });
107
134
  let synced = false;
108
135
  for (let i = 0; i < 30; i++) {
109
- const b = devops('GetBranchInfo', { repositoryId: String(repoId), branchName: branch });
136
+ const b = devops('GetBranchInfo', { repositoryId: String(repoId), branchName: ref });
110
137
  if (b?.result?.commit?.id === ghSha) {
111
138
  synced = true;
112
139
  break;
@@ -117,10 +144,26 @@ async function main() {
117
144
  console.error('✗ 300s 内 Codeup mirror 未追平 GitHub,中止(检查 mirror 凭证)');
118
145
  process.exit(1);
119
146
  }
120
- console.log(`✓ mirror 已追平 ${svc.repo}@${branch} = ${ghSha.slice(0, 10)}`);
121
- // 2. 触发(凭证由云效变量组 41970 供给,无需注入)
122
- const kv = { pipelineId: String(svc.pipelineId) };
123
- if (branch !== 'main') {
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') {
124
167
  kv.params = JSON.stringify({ runningBranchs: { [`${CODEUP_BASE}/${svc.repo}.git`]: branch } });
125
168
  }
126
169
  const start = devops('StartPipelineRun', kv);
@@ -129,14 +172,17 @@ async function main() {
129
172
  console.error(`✗ 启动失败: ${JSON.stringify(start).slice(0, 150)}`);
130
173
  process.exit(1);
131
174
  }
132
- console.log(`▶ ${svcName} run#${runId} 已启动 (branch=${branch})`);
133
- console.log(` https://flow.aliyun.com/pipelines/${svc.pipelineId}/current`);
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
+ }
134
180
  if (noWait)
135
181
  return;
136
182
  // 3. 轮询
137
183
  let status = '';
138
184
  for (;;) {
139
- const d = devops('GetPipelineRun', { pipelineId: String(svc.pipelineId), pipelineRunId: String(runId) });
185
+ const d = devops('GetPipelineRun', { pipelineId: String(pipelineId), pipelineRunId: String(runId) });
140
186
  status = d?.pipelineRun?.status || status;
141
187
  if (['SUCCESS', 'FAIL', 'CANCELED', 'ERROR'].includes(status))
142
188
  break;
@@ -145,12 +191,13 @@ async function main() {
145
191
  console.log(`${status === 'SUCCESS' ? '✅' : '❌'} ${svcName} run#${runId}: ${status}`);
146
192
  if (status !== 'SUCCESS')
147
193
  process.exit(1);
148
- // 4. SAE ImageUrl 校验(空返回重试守卫)
149
- if (svc.saeAppId) {
194
+ // 4. SAE ImageUrl 校验(空返回重试守卫)。prod 是 digest 寻址,校验含 @sha256 即部署成功。
195
+ const appId = envName === 'prod' ? svc.prodSaeAppId : svc.saeAppId;
196
+ if (appId) {
150
197
  let img = '';
151
198
  for (let i = 0; i < 3; i++) {
152
199
  const raw = sh('aliyun', ['sae', '--profile', PROFILE, '--region', 'cn-beijing',
153
- 'DescribeApplicationConfig', '--AppId', svc.saeAppId]);
200
+ 'DescribeApplicationConfig', '--AppId', appId]);
154
201
  try {
155
202
  img = JSON.parse(raw)?.Data?.ImageUrl || '';
156
203
  break;
@@ -163,11 +210,19 @@ async function main() {
163
210
  console.log('⚠️ SAE 配置查询连续空返回,跳过 ImageUrl 校验(流水线本身已 SUCCESS)');
164
211
  return;
165
212
  }
166
- const tag = img.split(':').pop() || '';
167
- const ok = ghSha.startsWith(tag);
168
- console.log(`${ok ? '✓' : '✗'} SAE ImageUrl tag=${tag} vs 目标 ${ghSha.slice(0, 10)}`);
169
- if (!ok)
170
- process.exit(1);
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
+ }
171
226
  }
172
227
  }
173
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.12.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": {