@optima-chat/dev-skills 0.16.7 → 0.16.8

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.
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  };
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.parseArgs = parseArgs;
37
38
  /**
38
39
  * optima-verify-health —— 服务上线健康探针(L1 DNS / L2 TLS / L3-L5 /health)。
39
40
  *
@@ -52,8 +53,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
52
53
  *
53
54
  * 用法:
54
55
  * optima-verify-health user-auth # 默认 cn-prod
55
- * optima-verify-health user-auth --env prod # 环境 stage|prod|cn|all
56
- * optima-verify-health --all --env all # stage/prod/cn 三环境矩阵
56
+ * optima-verify-health user-auth --env prod # 环境 stage|prod|cn-prod|cn-stage|all
57
+ * optima-verify-health --all --env all # stage/prod/cn-prod/cn-stage 四环境矩阵
57
58
  * optima-verify-health gateway-core --expect-commit a1b2c3d
58
59
  * optima-verify-health --url https://auth.yzsgo.com/health
59
60
  * optima-verify-health --all --json # 机器可读,接 CI
@@ -67,16 +68,28 @@ const https = __importStar(require("node:https"));
67
68
  // cn-prod 真实 subdomain 抄自 optima-terraform alicloud/stacks/cn-prod-ingress-sae/main.tf。
68
69
  // #201 (2026-06-12): yzsgo.com 全量迁移完成,旧 *-cn.optima.chat 路由已下线。
69
70
  // cn-stage(阿里云预发)域名 *.stage.optima.chat,抄自 cn-stage-ingress-sae services map。
71
+ // #83 (2026-08-13): AWS stage 是 *.stage.optima.onl(点号),旧的连字符形式 *-stage.optima.onl
72
+ // 已不再路由到服务——三项实测 301 → www.optima.onl,探测恒红并误报成「疑似未部署」。
73
+ // 🔴 别只看 stage 列:agentic-chat 的 prod 也踩同一个坑(见下面那行的注释)。
70
74
  const SERVICES = {
71
- 'user-auth': { path: '/health', stage: 'auth-stage.optima.onl', prod: 'auth.optima.onl', cn: 'auth.yzsgo.com', 'cn-stage': 'auth.stage.optima.chat' },
72
- 'agentic-chat': { path: '/api/health', stage: 'ai-stage.optima.onl', prod: 'ai.optima.onl', cn: 'app.yzsgo.com', 'cn-stage': 'app.stage.optima.chat' },
73
- 'commerce-backend': { path: '/health', stage: 'api-stage.optima.onl', prod: 'api.optima.onl', cn: 'commerce.yzsgo.com', cn_path: '/health/live', 'cn-stage': 'commerce.stage.optima.chat', 'cn-stage_path': '/health/live' },
74
- 'mcp-host': { path: '/health', stage: 'mcp-stage.optima.onl', prod: 'mcp.optima.onl' },
75
- 'gateway-core': { path: '/health', cn: 'gw.yzsgo.com', 'cn-stage': 'gw.stage.optima.chat' },
76
- 'optima-scout': { path: '/health', cn: 'scout.yzsgo.com', 'cn-stage': 'scout.stage.optima.chat' },
77
- 'optima-skills': { path: '/health', cn: 'skills.yzsgo.com', 'cn-stage': 'skills.stage.optima.chat' },
75
+ 'user-auth': { path: '/health', stage: 'auth.stage.optima.onl', prod: 'auth.optima.onl', 'cn-prod': 'auth.yzsgo.com', 'cn-stage': 'auth.stage.optima.chat' },
76
+ // agentic-chat prod 入口是 www 不是 ai:prod-ecs/variables.tf 里它的 subdomain = "www",
77
+ // main.tf 有一条 ai_to_www_redirect(priority 309) 专门把 ai.optima.onl 301 www。
78
+ // 实测 ai.optima.onl/api/health 301;www.optima.onl/api/health → 200 service=agentic-chat。
79
+ 'agentic-chat': { path: '/api/health', stage: 'ai.stage.optima.onl', prod: 'www.optima.onl', 'cn-prod': 'app.yzsgo.com', 'cn-stage': 'app.stage.optima.chat' },
80
+ 'commerce-backend': { path: '/health', stage: 'api.stage.optima.onl', prod: 'api.optima.onl', 'cn-prod': 'commerce.yzsgo.com', 'cn-prod_path': '/health/live', 'cn-stage': 'commerce.stage.optima.chat', 'cn-stage_path': '/health/live' },
81
+ // #83: mcp-host 已于 2025-12-18 下线,故不在表内 —— optima-terraform origin/main(f97adbb)
82
+ // stage-ecs/variables.tf:146 与 prod-ecs/variables.tf:508 都写着「MCP 工具服务已移除」,
83
+ // 两个 ecs stack 的 services map 里均无该条目;实测四个候选主机名全是壳(mcp.stage.optima.onl
84
+ // 307 → /en-US/health 前端 locale 路由,mcp-stage.optima.onl 与 prod 的 mcp.optima.onl 均
85
+ // 301 → www)。🔴 别再把它加回来:worstOk 是全局单标志(见 main()),留着它会让 `--all --env stage`
86
+ // 与 `--all --env prod` 恒 exit 1,而那个退出码正是接 CI 卡口时唯一被读的东西。
87
+ // 同一约束在 tests/service-matrix-alignment.test.js 里对 show-env 的清单也钉着。
88
+ 'gateway-core': { path: '/health', 'cn-prod': 'gw.yzsgo.com', 'cn-stage': 'gw.stage.optima.chat' },
89
+ 'optima-scout': { path: '/health', 'cn-prod': 'scout.yzsgo.com', 'cn-stage': 'scout.stage.optima.chat' },
90
+ 'optima-skills': { path: '/health', 'cn-prod': 'skills.yzsgo.com', 'cn-stage': 'skills.stage.optima.chat' },
78
91
  };
79
- const ENVS = ['stage', 'prod', 'cn', 'cn-stage'];
92
+ const ENVS = ['stage', 'prod', 'cn-prod', 'cn-stage'];
80
93
  const G = '\x1b[32m', R = '\x1b[31m', Y = '\x1b[33m', B = '\x1b[34m', N = '\x1b[0m';
81
94
  const MARK = { ok: `${G}✅${N}`, fail: `${R}❌${N}`, warn: `${Y}⚠️ ${N}`, na: `${B}··${N}` };
82
95
  const verdict = (ls) => ls.some((l) => l.result === 'fail') ? 'fail' : ls.some((l) => l.result === 'warn') ? 'warn' : 'pass';
@@ -194,21 +207,66 @@ function resolve(svc, e) {
194
207
  const path = cfg[`${e}_path`] || cfg.path;
195
208
  return [`${svc} [${e}]`, host, path];
196
209
  }
210
+ // 🔴 不认识的输入一律报错、绝不静默丢弃(同 query-db.ts 的 parseQueryDbArgs、logs.ts 的 parseArgs):
211
+ // 此前 `--env=stage` 不认、`--env` 漏写取值、多敲一个位置参数都会被静默无视而回落默认 cn-prod ——
212
+ // 想探 stage,实际探的是阿里云生产,还 exit 0。
213
+ function parseArgs(argv) {
214
+ const flags = new Set(), vals = {}, positional = [];
215
+ for (let i = 0; i < argv.length; i++) {
216
+ const arg = argv[i];
217
+ const eq = arg.startsWith('--') ? arg.indexOf('=') : -1;
218
+ const name = eq > 0 ? arg.slice(0, eq) : arg;
219
+ if (['--env', '--expect-commit', '--url'].includes(name)) {
220
+ const v = eq > 0 ? arg.slice(eq + 1) : argv[++i];
221
+ if (!v || v.startsWith('-'))
222
+ throw new Error(`${name} 缺少取值`);
223
+ vals[name] = v;
224
+ }
225
+ else if (['--json', '--strict', '--all', '--help', '-h'].includes(arg))
226
+ flags.add(arg);
227
+ else if (arg.startsWith('-'))
228
+ throw new Error(`未知参数:${arg}`);
229
+ else
230
+ positional.push(arg);
231
+ }
232
+ // 🔴 必须先校验再转型:不校验的话任何不认识的 env 都会让 resolve() 把每个 SvcCfg 键查成
233
+ // undefined,于是落到下面的「无目标」分支,把「环境名打错」报成「该服务无部署」——
234
+ // 一个活着的服务被说成没上线,正是 #83 要根治的那类静默误报。
235
+ let envArg = vals['--env'] || 'cn-prod';
236
+ // 'cn' 是本文件的历史叫法,继续放行但不在 usage 里宣传(同 query-db.ts 的 ENV_ALIASES)。
237
+ if (envArg === 'cn')
238
+ envArg = 'cn-prod';
239
+ if (envArg !== 'all' && !ENVS.includes(envArg))
240
+ throw new Error(`未知环境:${envArg}(可选:${ENVS.join(' | ')} | all)`);
241
+ if (positional.length > 1)
242
+ throw new Error(`多余参数:${positional.slice(1).join(' ')}(一次探一个服务;环境用 --env 指定)`);
243
+ const service = positional[0];
244
+ if (service && !Object.keys(SERVICES).includes(service))
245
+ throw new Error(`未知服务:${service}(可选:${Object.keys(SERVICES).join(' | ')})`);
246
+ const url = vals['--url'], all = flags.has('--all');
247
+ return { help: flags.has('--help') || flags.has('-h') || !(url || all || service), json: flags.has('--json'), strict: flags.has('--strict'), all, env: envArg, expect: vals['--expect-commit'], url, service };
248
+ }
197
249
  async function main() {
198
- const argv = process.argv.slice(2);
199
- const has = (f) => argv.includes(f);
200
- const val = (f) => { const i = argv.indexOf(f); return i >= 0 ? argv[i + 1] : undefined; };
201
- const asJson = has('--json'), strict = has('--strict');
202
- const expect = val('--expect-commit');
203
- const env = (val('--env') || 'cn');
250
+ let args;
251
+ try {
252
+ args = parseArgs(process.argv.slice(2));
253
+ }
254
+ catch (e) {
255
+ console.error(`❌ ${e.message}`);
256
+ process.exit(2);
257
+ }
258
+ const { json: asJson, strict, expect, env } = args;
204
259
  const envs = env === 'all' ? ENVS : [env];
205
- const positional = argv.filter((x, i) => !x.startsWith('--') && !(i > 0 && argv[i - 1].startsWith('--') && ['--env', '--expect-commit', '--url'].includes(argv[i - 1])));
206
260
  let targets = [];
207
- if (has('--url')) {
208
- const u = new URL(val('--url'));
261
+ if (args.help) {
262
+ console.log((require('fs').readFileSync(__filename, 'utf-8').match(/\/\*\*[\s\S]*?\*\//)?.[0] || '').replace(/^\s*\*?/gm, ''));
263
+ process.exit(2);
264
+ }
265
+ else if (args.url) {
266
+ const u = new URL(args.url);
209
267
  targets = [[u.hostname, u.hostname, u.pathname || '/health']];
210
268
  }
211
- else if (has('--all')) {
269
+ else if (args.all) {
212
270
  for (const e of envs)
213
271
  for (const s of Object.keys(SERVICES)) {
214
272
  const t = resolve(s, e);
@@ -216,17 +274,13 @@ async function main() {
216
274
  targets.push(t);
217
275
  }
218
276
  }
219
- else if (positional[0] && SERVICES[positional[0]]) {
277
+ else if (args.service) {
220
278
  for (const e of envs) {
221
- const t = resolve(positional[0], e);
279
+ const t = resolve(args.service, e);
222
280
  if (t)
223
281
  targets.push(t);
224
282
  }
225
283
  }
226
- else {
227
- console.log((require('fs').readFileSync(__filename, 'utf-8').match(/\/\*\*[\s\S]*?\*\//)?.[0] || '').replace(/^\s*\*?/gm, ''));
228
- process.exit(2);
229
- }
230
284
  if (targets.length === 0) {
231
285
  console.log(`(无目标:${env} 环境下该服务无部署)`);
232
286
  process.exit(2);
@@ -252,4 +306,6 @@ async function main() {
252
306
  // 用 exitCode 而非 process.exit():后者会在管道(--json | jq)未 flush 完就退出而截断输出
253
307
  process.exitCode = worstOk ? 0 : 1;
254
308
  }
255
- main();
309
+ if (require.main === module) {
310
+ main();
311
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optima-chat/dev-skills",
3
- "version": "0.16.7",
3
+ "version": "0.16.8",
4
4
  "description": "Claude Code Skills for Optima development team - cross-environment collaboration tools",
5
5
  "main": "index.js",
6
6
  "bin": {