@optima-chat/dev-skills 0.11.0 → 0.12.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.
@@ -0,0 +1,39 @@
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
+
33
+ - `aliyun` CLI 已配置(profile 默认 `aliyun-optima`,可用环境变量 `OPTIMA_ALIYUN_PROFILE` 覆盖为自己的 RAM 用户 profile)
34
+ - `gh` 已登录(mirror 同步需要 GitHub token)
35
+
36
+ ## 边界
37
+
38
+ - 只发 **cn-stage**,不发 cn-prod(cn-prod 发布另走人工卡点流程)。
39
+ - 服务注册表是 optima-terraform `alicloud/stacks/cn-prod-buildbox/yunxiao/` 的快照;新增服务先在那边 gen-pipelines 建好流水线,再同步本工具的 SERVICES 表。
@@ -0,0 +1,39 @@
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
+
33
+ - `aliyun` CLI 已配置(profile 默认 `aliyun-optima`,可用环境变量 `OPTIMA_ALIYUN_PROFILE` 覆盖为自己的 RAM 用户 profile)
34
+ - `gh` 已登录(mirror 同步需要 GitHub token)
35
+
36
+ ## 边界
37
+
38
+ - 只发 **cn-stage**,不发 cn-prod(cn-prod 发布另走人工卡点流程)。
39
+ - 服务注册表是 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,148 @@
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
+ *
22
+ * 前置: aliyun CLI(profile 默认 aliyun-optima,可用 OPTIMA_ALIYUN_PROFILE 覆盖)+ gh 已登录。
23
+ * 流水线定义的单一信源在 optima-terraform yunxiao/(gen-pipelines.py);本表为其快照,
24
+ * 新增服务后同步(pipelineId 稳定,不常变)。
25
+ */
26
+ import { execFileSync } from 'node:child_process';
27
+
28
+ const ORG = '6a17b6282bf8b1184fc0e0c6';
29
+ const ENDPOINT = 'devops.cn-hangzhou.aliyuncs.com';
30
+ const PROFILE = process.env.OPTIMA_ALIYUN_PROFILE || 'aliyun-optima';
31
+ const CODEUP_BASE = `https://codeup.aliyun.com/${ORG}`;
32
+
33
+ // 服务注册表(快照自 optima-terraform services.stage.env + gen-pipelines.py,2026-07-13)
34
+ interface Svc { pipelineId: number; repo: string; saeAppId?: string; }
35
+ 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' },
56
+ };
57
+
58
+ function sh(cmd: string, args: string[]): string {
59
+ try {
60
+ return execFileSync(cmd, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
61
+ } catch (e: any) {
62
+ return '';
63
+ }
64
+ }
65
+
66
+ function devops(action: string, kv: Record<string, string>, retries = 3): any {
67
+ const args = ['devops', action, '--organizationId', ORG, '--endpoint', ENDPOINT, '--profile', PROFILE];
68
+ for (const [k, v] of Object.entries(kv)) args.push(`--${k}`, v);
69
+ for (let i = 0; i < retries; i++) {
70
+ const out = sh('aliyun', args);
71
+ try { return JSON.parse(out); } catch { /* aliyun 偶发空返回/超时,重试 */ }
72
+ }
73
+ return {};
74
+ }
75
+
76
+ const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
77
+
78
+ async function main() {
79
+ const argv = process.argv.slice(2);
80
+ if (argv.includes('--list') || argv.length === 0) {
81
+ console.log('可发服务(cn-stage):\n ' + Object.keys(SERVICES).sort().join('\n '));
82
+ console.log('\n用法: optima-cn-deploy <service> [--branch feat/xxx] [--no-wait]');
83
+ process.exit(argv.includes('--list') ? 0 : 1);
84
+ }
85
+ const svcName = argv[0];
86
+ const svc = SERVICES[svcName];
87
+ if (!svc) { console.error(`✗ 未知服务 ${svcName}(--list 看全量)`); process.exit(1); }
88
+ const bIdx = argv.indexOf('--branch');
89
+ const branch = bIdx >= 0 ? argv[bIdx + 1] : 'main';
90
+ const noWait = argv.includes('--no-wait');
91
+
92
+ // 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); }
95
+ const repos = devops('ListRepositories', { perPage: '100' });
96
+ const repoId = (repos.result || []).find((r: any) => r.name === svc.repo)?.Id;
97
+ if (!repoId) { console.error(`✗ Codeup 无 mirror 仓 ${svc.repo}`); process.exit(1); }
98
+ const ghTok = sh('gh', ['auth', 'token']);
99
+ devops('TriggerRepositoryMirrorSync', { repositoryId: String(repoId), account: 'xbfool', token: ghTok });
100
+ let synced = false;
101
+ for (let i = 0; i < 30; i++) {
102
+ const b = devops('GetBranchInfo', { repositoryId: String(repoId), branchName: branch });
103
+ if (b?.result?.commit?.id === ghSha) { synced = true; break; }
104
+ await sleep(10000);
105
+ }
106
+ if (!synced) { console.error('✗ 300s 内 Codeup mirror 未追平 GitHub,中止(检查 mirror 凭证)'); process.exit(1); }
107
+ console.log(`✓ mirror 已追平 ${svc.repo}@${branch} = ${ghSha.slice(0, 10)}`);
108
+
109
+ // 2. 触发(凭证由云效变量组 41970 供给,无需注入)
110
+ const kv: Record<string, string> = { pipelineId: String(svc.pipelineId) };
111
+ if (branch !== 'main') {
112
+ kv.params = JSON.stringify({ runningBranchs: { [`${CODEUP_BASE}/${svc.repo}.git`]: branch } });
113
+ }
114
+ const start = devops('StartPipelineRun', kv);
115
+ const runId = start.pipelineRunId;
116
+ 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`);
119
+ if (noWait) return;
120
+
121
+ // 3. 轮询
122
+ let status = '';
123
+ for (;;) {
124
+ const d = devops('GetPipelineRun', { pipelineId: String(svc.pipelineId), pipelineRunId: String(runId) });
125
+ status = d?.pipelineRun?.status || status;
126
+ if (['SUCCESS', 'FAIL', 'CANCELED', 'ERROR'].includes(status)) break;
127
+ await sleep(45000);
128
+ }
129
+ console.log(`${status === 'SUCCESS' ? '✅' : '❌'} ${svcName} run#${runId}: ${status}`);
130
+ if (status !== 'SUCCESS') process.exit(1);
131
+
132
+ // 4. SAE ImageUrl 校验(空返回重试守卫)
133
+ if (svc.saeAppId) {
134
+ let img = '';
135
+ for (let i = 0; i < 3; i++) {
136
+ const raw = sh('aliyun', ['sae', '--profile', PROFILE, '--region', 'cn-beijing',
137
+ 'DescribeApplicationConfig', '--AppId', svc.saeAppId]);
138
+ try { img = JSON.parse(raw)?.Data?.ImageUrl || ''; break; } catch { await sleep(3000); }
139
+ }
140
+ 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);
145
+ }
146
+ }
147
+
148
+ main().catch(e => { console.error('✗', e?.message || e); process.exit(1); });
@@ -0,0 +1,173 @@
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
+ *
24
+ * 前置: aliyun CLI(profile 默认 aliyun-optima,可用 OPTIMA_ALIYUN_PROFILE 覆盖)+ gh 已登录。
25
+ * 流水线定义的单一信源在 optima-terraform yunxiao/(gen-pipelines.py);本表为其快照,
26
+ * 新增服务后同步(pipelineId 稳定,不常变)。
27
+ */
28
+ const node_child_process_1 = require("node:child_process");
29
+ const ORG = '6a17b6282bf8b1184fc0e0c6';
30
+ const ENDPOINT = 'devops.cn-hangzhou.aliyuncs.com';
31
+ const PROFILE = process.env.OPTIMA_ALIYUN_PROFILE || 'aliyun-optima';
32
+ const CODEUP_BASE = `https://codeup.aliyun.com/${ORG}`;
33
+ 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' },
54
+ };
55
+ function sh(cmd, args) {
56
+ try {
57
+ return (0, node_child_process_1.execFileSync)(cmd, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
58
+ }
59
+ catch (e) {
60
+ return '';
61
+ }
62
+ }
63
+ function devops(action, kv, retries = 3) {
64
+ const args = ['devops', action, '--organizationId', ORG, '--endpoint', ENDPOINT, '--profile', PROFILE];
65
+ for (const [k, v] of Object.entries(kv))
66
+ args.push(`--${k}`, v);
67
+ for (let i = 0; i < retries; i++) {
68
+ const out = sh('aliyun', args);
69
+ try {
70
+ return JSON.parse(out);
71
+ }
72
+ catch { /* aliyun 偶发空返回/超时,重试 */ }
73
+ }
74
+ return {};
75
+ }
76
+ const sleep = (ms) => new Promise(r => setTimeout(r, ms));
77
+ async function main() {
78
+ const argv = process.argv.slice(2);
79
+ if (argv.includes('--list') || argv.length === 0) {
80
+ console.log('可发服务(cn-stage):\n ' + Object.keys(SERVICES).sort().join('\n '));
81
+ console.log('\n用法: optima-cn-deploy <service> [--branch feat/xxx] [--no-wait]');
82
+ process.exit(argv.includes('--list') ? 0 : 1);
83
+ }
84
+ const svcName = argv[0];
85
+ const svc = SERVICES[svcName];
86
+ if (!svc) {
87
+ console.error(`✗ 未知服务 ${svcName}(--list 看全量)`);
88
+ process.exit(1);
89
+ }
90
+ const bIdx = argv.indexOf('--branch');
91
+ const branch = bIdx >= 0 ? argv[bIdx + 1] : 'main';
92
+ const noWait = argv.includes('--no-wait');
93
+ // 1. GitHub HEAD + mirror 同步
94
+ const ghSha = sh('gh', ['api', `repos/Optima-Chat/${svc.repo}/commits/${branch}`, '--jq', '.sha']);
95
+ if (!ghSha) {
96
+ console.error(`✗ GitHub 上无 ${svc.repo}@${branch}(或 gh 未登录)`);
97
+ process.exit(1);
98
+ }
99
+ const repos = devops('ListRepositories', { perPage: '100' });
100
+ const repoId = (repos.result || []).find((r) => r.name === svc.repo)?.Id;
101
+ if (!repoId) {
102
+ console.error(`✗ Codeup 无 mirror 仓 ${svc.repo}`);
103
+ process.exit(1);
104
+ }
105
+ const ghTok = sh('gh', ['auth', 'token']);
106
+ devops('TriggerRepositoryMirrorSync', { repositoryId: String(repoId), account: 'xbfool', token: ghTok });
107
+ let synced = false;
108
+ for (let i = 0; i < 30; i++) {
109
+ const b = devops('GetBranchInfo', { repositoryId: String(repoId), branchName: branch });
110
+ if (b?.result?.commit?.id === ghSha) {
111
+ synced = true;
112
+ break;
113
+ }
114
+ await sleep(10000);
115
+ }
116
+ if (!synced) {
117
+ console.error('✗ 300s 内 Codeup mirror 未追平 GitHub,中止(检查 mirror 凭证)');
118
+ process.exit(1);
119
+ }
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') {
124
+ kv.params = JSON.stringify({ runningBranchs: { [`${CODEUP_BASE}/${svc.repo}.git`]: branch } });
125
+ }
126
+ const start = devops('StartPipelineRun', kv);
127
+ const runId = start.pipelineRunId;
128
+ if (!runId) {
129
+ console.error(`✗ 启动失败: ${JSON.stringify(start).slice(0, 150)}`);
130
+ process.exit(1);
131
+ }
132
+ console.log(`▶ ${svcName} run#${runId} 已启动 (branch=${branch})`);
133
+ console.log(` https://flow.aliyun.com/pipelines/${svc.pipelineId}/current`);
134
+ if (noWait)
135
+ return;
136
+ // 3. 轮询
137
+ let status = '';
138
+ for (;;) {
139
+ const d = devops('GetPipelineRun', { pipelineId: String(svc.pipelineId), pipelineRunId: String(runId) });
140
+ status = d?.pipelineRun?.status || status;
141
+ if (['SUCCESS', 'FAIL', 'CANCELED', 'ERROR'].includes(status))
142
+ break;
143
+ await sleep(45000);
144
+ }
145
+ console.log(`${status === 'SUCCESS' ? '✅' : '❌'} ${svcName} run#${runId}: ${status}`);
146
+ if (status !== 'SUCCESS')
147
+ process.exit(1);
148
+ // 4. SAE ImageUrl 校验(空返回重试守卫)
149
+ if (svc.saeAppId) {
150
+ let img = '';
151
+ for (let i = 0; i < 3; i++) {
152
+ const raw = sh('aliyun', ['sae', '--profile', PROFILE, '--region', 'cn-beijing',
153
+ 'DescribeApplicationConfig', '--AppId', svc.saeAppId]);
154
+ try {
155
+ img = JSON.parse(raw)?.Data?.ImageUrl || '';
156
+ break;
157
+ }
158
+ catch {
159
+ await sleep(3000);
160
+ }
161
+ }
162
+ if (!img) {
163
+ console.log('⚠️ SAE 配置查询连续空返回,跳过 ImageUrl 校验(流水线本身已 SUCCESS)');
164
+ return;
165
+ }
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);
171
+ }
172
+ }
173
+ 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.11.0",
3
+ "version": "0.12.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
+ }