@optima-chat/dev-skills 0.7.38 → 0.7.41
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/grant-balance/SKILL.md +10 -8
- package/.claude/skills/grant-subscription/SKILL.md +12 -6
- package/.codex/skills/grant-balance/SKILL.md +10 -8
- package/.codex/skills/grant-subscription/SKILL.md +8 -1
- package/bin/helpers/billing-http.ts +85 -3
- package/bin/helpers/db-utils.ts +274 -37
- package/bin/helpers/generate-test-token.ts +17 -8
- package/bin/helpers/grant-balance.ts +39 -46
- package/bin/helpers/grant-subscription.ts +47 -89
- package/bin/helpers/query-db.ts +25 -60
- package/bin/helpers/show-env.ts +3 -28
- package/bin/helpers/verify-health.ts +9 -8
- package/dist/bin/helpers/billing-http.js +84 -3
- package/dist/bin/helpers/db-utils.js +292 -45
- package/dist/bin/helpers/generate-test-token.js +15 -6
- package/dist/bin/helpers/grant-balance.js +35 -43
- package/dist/bin/helpers/grant-subscription.js +36 -86
- package/dist/bin/helpers/query-db.js +33 -49
- package/dist/bin/helpers/show-env.js +5 -17
- package/dist/bin/helpers/verify-health.js +9 -8
- package/package.json +1 -1
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
// P15 D8b:USD 钱包退役——原「SSH 直写 subscriptions/usd_wallets/token_quotas」
|
|
5
|
+
// 作废,改调 billing 服务态端点(与用户态 /api/admin/grant-subscription 同
|
|
6
|
+
// 业务体:supersede 旧授予 + 实得 credits + token quota,重试双发不双倍)。
|
|
4
7
|
const db_utils_1 = require("./db-utils");
|
|
8
|
+
const billing_http_1 = require("./billing-http");
|
|
9
|
+
// cn-prod sells the CNY-priced -cn plans (P12); the bare USD plan ids also
|
|
10
|
+
// exist in the cn DB, so a per-env whitelist (not billing-side validation)
|
|
11
|
+
// is what prevents accidentally granting a USD-priced plan to a CN user.
|
|
12
|
+
const PLANS_BY_ENV = {
|
|
13
|
+
stage: ['trial', 'starter', 'pro', 'enterprise'],
|
|
14
|
+
prod: ['trial', 'starter', 'pro', 'enterprise'],
|
|
15
|
+
'cn-prod': ['trial', 'starter-cn', 'pro-cn', 'enterprise-cn'],
|
|
16
|
+
};
|
|
5
17
|
function parseArgs(args) {
|
|
6
18
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
7
19
|
console.log(`Usage: optima-grant-subscription <email> [options]
|
|
8
20
|
|
|
9
21
|
Options:
|
|
10
22
|
--plan <id> Plan: trial, starter, pro, enterprise (default: pro)
|
|
23
|
+
cn-prod plans: trial, starter-cn, pro-cn, enterprise-cn (default: pro-cn)
|
|
11
24
|
--months <n> Duration in months (default: 1)
|
|
12
|
-
--env <env> Environment: stage, prod (default: stage)
|
|
25
|
+
--env <env> Environment: stage, prod, cn-prod (default: stage)
|
|
13
26
|
-h, --help Show this help`);
|
|
14
27
|
process.exit(0);
|
|
15
28
|
}
|
|
16
29
|
const email = args[0];
|
|
17
|
-
let plan =
|
|
30
|
+
let plan = null;
|
|
18
31
|
let months = 1;
|
|
19
32
|
let env = 'stage';
|
|
20
33
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -28,103 +41,40 @@ Options:
|
|
|
28
41
|
env = args[++i];
|
|
29
42
|
}
|
|
30
43
|
}
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
(0, billing_http_1.validateEnvCnProd)(env);
|
|
45
|
+
plan = plan ?? (env === 'cn-prod' ? 'pro-cn' : 'pro');
|
|
46
|
+
const allowed = PLANS_BY_ENV[env];
|
|
47
|
+
if (!allowed.includes(plan)) {
|
|
48
|
+
console.error(`Unknown plan for ${env}: ${plan}. Available: ${allowed.join(', ')}`);
|
|
33
49
|
process.exit(1);
|
|
34
50
|
}
|
|
35
51
|
if (months < 1) {
|
|
36
52
|
console.error('Months must be >= 1');
|
|
37
53
|
process.exit(1);
|
|
38
54
|
}
|
|
39
|
-
if (!['stage', 'prod'].includes(env)) {
|
|
40
|
-
console.error('Env must be stage or prod (billing DB not available in CI)');
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
55
|
return { email, plan, months, env };
|
|
44
56
|
}
|
|
45
57
|
async function main() {
|
|
46
58
|
const { email, plan, months, env } = parseArgs(process.argv.slice(2));
|
|
47
|
-
const infisicalConfig = (0, db_utils_1.getInfisicalConfig)();
|
|
48
|
-
const token = (0, db_utils_1.getInfisicalToken)(infisicalConfig);
|
|
49
59
|
console.log(`\n🎁 Granting ${plan} subscription to ${email} for ${months} month(s) [${env.toUpperCase()}]\n`);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const planRow = bq(`SELECT name, monthly_credits, session_token_limit, weekly_token_limit FROM plans WHERE id='${(0, db_utils_1.escapeSQL)(plan)}'`);
|
|
56
|
-
if (!planRow) {
|
|
57
|
-
console.error(`❌ Plan not found in DB: ${plan}`);
|
|
58
|
-
process.exit(1);
|
|
60
|
+
// cn-prod has no SSH tunnel into the Aliyun RDS — resolve via user-auth's
|
|
61
|
+
// internal lookup API instead of the direct SQL path.
|
|
62
|
+
let userId;
|
|
63
|
+
if (env === 'cn-prod') {
|
|
64
|
+
userId = await (0, billing_http_1.resolveUserIdByEmail)(env, email);
|
|
59
65
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const weeklyTokenLimit = parseInt(weeklyTokenLimitStr, 10);
|
|
65
|
-
console.log(`✓ Plan: ${planName} (grant: $${(grantMicros / 1000000).toFixed(2)}, session: ${sessionTokenLimit.toLocaleString()}, weekly: ${weeklyTokenLimit.toLocaleString()})`);
|
|
66
|
-
// Execute all mutations in a single transaction
|
|
67
|
-
const now = new Date().toISOString();
|
|
68
|
-
const periodEnd = new Date();
|
|
69
|
-
periodEnd.setMonth(periodEnd.getMonth() + months);
|
|
70
|
-
const periodEndISO = periodEnd.toISOString();
|
|
71
|
-
const sessionEnd = new Date(new Date().getTime() + 5 * 60 * 60 * 1000).toISOString();
|
|
72
|
-
const weekEnd = new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
73
|
-
const safeUserId = (0, db_utils_1.escapeSQL)(userId);
|
|
74
|
-
const safePlan = (0, db_utils_1.escapeSQL)(plan);
|
|
75
|
-
const safePlanName = (0, db_utils_1.escapeSQL)(planName);
|
|
76
|
-
console.log('Executing transaction...');
|
|
77
|
-
const txSQL = `
|
|
78
|
-
BEGIN;
|
|
79
|
-
|
|
80
|
-
-- Cancel active subscriptions
|
|
81
|
-
UPDATE subscriptions SET status='canceled', canceled_at='${now}'
|
|
82
|
-
WHERE user_id='${safeUserId}' AND status IN ('active','trialing');
|
|
83
|
-
|
|
84
|
-
-- Create new subscription
|
|
85
|
-
INSERT INTO subscriptions (id, user_id, plan_id, status, billing_interval, current_period_start, current_period_end, created_at, updated_at)
|
|
86
|
-
VALUES (concat('sub_gift_', substr(md5(random()::text), 1, 16)), '${safeUserId}', '${safePlan}', 'active', 'monthly', '${now}', '${periodEndISO}', '${now}', '${now}');
|
|
87
|
-
|
|
88
|
-
-- Ensure wallet exists (upsert)
|
|
89
|
-
INSERT INTO usd_wallets (id, user_id, balance_micros, reserved_micros, granted_balance_micros, created_at, updated_at)
|
|
90
|
-
VALUES (gen_random_uuid(), '${safeUserId}', 0, 0, 0, '${now}', '${now}')
|
|
91
|
-
ON CONFLICT (user_id) DO NOTHING;
|
|
92
|
-
|
|
93
|
-
-- Reset granted balance and set new grant
|
|
94
|
-
UPDATE usd_wallets SET granted_balance_micros = ${grantMicros}, updated_at = '${now}'
|
|
95
|
-
WHERE user_id = '${safeUserId}';
|
|
96
|
-
|
|
97
|
-
-- Record topup for audit trail
|
|
98
|
-
INSERT INTO usd_wallet_topups (id, wallet_id, amount_micros, service_fee_micros, net_credit_micros, status, source, service_namespace, created_at, completed_at)
|
|
99
|
-
SELECT gen_random_uuid(), w.id, ${grantMicros}, 0, ${grantMicros}, 'completed', 'subscription_grant', 'platform', '${now}', '${now}'
|
|
100
|
-
FROM usd_wallets w WHERE w.user_id = '${safeUserId}';
|
|
101
|
-
|
|
102
|
-
-- Update existing active session quota, or insert new one if none exists
|
|
103
|
-
UPDATE token_quotas SET plan_id='${safePlan}', monthly_limit=${sessionTokenLimit}, updated_at='${now}'
|
|
104
|
-
WHERE user_id='${safeUserId}' AND period_type='session' AND period_end > '${now}';
|
|
105
|
-
|
|
106
|
-
INSERT INTO token_quotas (id, user_id, plan_id, period_type, monthly_limit, monthly_used, period_start, period_end, created_at, updated_at)
|
|
107
|
-
SELECT concat('tq_sess_', substr(md5(random()::text), 1, 16)), '${safeUserId}', '${safePlan}', 'session', ${sessionTokenLimit}, 0, '${now}', '${sessionEnd}', '${now}', '${now}'
|
|
108
|
-
WHERE NOT EXISTS (SELECT 1 FROM token_quotas WHERE user_id='${safeUserId}' AND period_type='session' AND period_end > '${now}');
|
|
109
|
-
|
|
110
|
-
-- Update existing active weekly quota, or insert new one if none exists
|
|
111
|
-
UPDATE token_quotas SET plan_id='${safePlan}', monthly_limit=${weeklyTokenLimit}, updated_at='${now}'
|
|
112
|
-
WHERE user_id='${safeUserId}' AND period_type='weekly' AND period_end > '${now}';
|
|
113
|
-
|
|
114
|
-
INSERT INTO token_quotas (id, user_id, plan_id, period_type, monthly_limit, monthly_used, period_start, period_end, created_at, updated_at)
|
|
115
|
-
SELECT concat('tq_week_', substr(md5(random()::text), 1, 16)), '${safeUserId}', '${safePlan}', 'weekly', ${weeklyTokenLimit}, 0, '${now}', '${weekEnd}', '${now}', '${now}'
|
|
116
|
-
WHERE NOT EXISTS (SELECT 1 FROM token_quotas WHERE user_id='${safeUserId}' AND period_type='weekly' AND period_end > '${now}');
|
|
117
|
-
|
|
118
|
-
COMMIT;
|
|
119
|
-
`.trim();
|
|
120
|
-
bq(txSQL);
|
|
121
|
-
console.log('✓ Old subscriptions canceled');
|
|
122
|
-
console.log(`✓ ${planName} subscription created (expires: ${periodEnd.toLocaleDateString()})`);
|
|
123
|
-
if (grantMicros > 0) {
|
|
124
|
-
console.log(`✓ Wallet granted $${(grantMicros / 1000000).toFixed(2)} (${monthlyCredits} credits equivalent)`);
|
|
66
|
+
else {
|
|
67
|
+
const infisicalConfig = (0, db_utils_1.getInfisicalConfig)();
|
|
68
|
+
const token = (0, db_utils_1.getInfisicalToken)(infisicalConfig);
|
|
69
|
+
userId = await (0, db_utils_1.resolveUserId)(email, env, infisicalConfig, token);
|
|
125
70
|
}
|
|
126
|
-
|
|
127
|
-
console.log(
|
|
71
|
+
const { body } = await (0, billing_http_1.callBilling)(env, 'POST', '/api/billing/admin/grant-subscription', { userId, planId: plan, months });
|
|
72
|
+
console.log(`✓ Subscription ${body.subscriptionId} (${body.planId})`);
|
|
73
|
+
console.log(`✓ Credits: ${body.credits.toLocaleString()} (expires ${body.expiresAt})`);
|
|
74
|
+
console.log(`✓ Token limits: session ${body.sessionTokenLimit.toLocaleString()} / weekly ${body.weeklyTokenLimit.toLocaleString()}`);
|
|
75
|
+
if (body.warning)
|
|
76
|
+
console.log(`⚠️ ${body.warning}`);
|
|
77
|
+
console.log(`\n✅ Done! ${email} now has ${body.planId} until ${body.expiresAt}\n`);
|
|
128
78
|
}
|
|
129
79
|
main().catch(error => {
|
|
130
80
|
console.error('\n❌ Error:', error.message);
|
|
@@ -114,44 +114,8 @@ const RDS_HOSTS = {
|
|
|
114
114
|
stage: 'optima-stage-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com',
|
|
115
115
|
prod: 'optima-prod-postgres.ctg866o0ehac.ap-southeast-1.rds.amazonaws.com'
|
|
116
116
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const match = url.match(/^postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/([^?]+)/);
|
|
120
|
-
if (!match) {
|
|
121
|
-
throw new Error(`Failed to parse DATABASE_URL: ${url}`);
|
|
122
|
-
}
|
|
123
|
-
return {
|
|
124
|
-
user: decodeURIComponent(match[1]),
|
|
125
|
-
password: decodeURIComponent(match[2]),
|
|
126
|
-
host: match[3],
|
|
127
|
-
port: parseInt(match[4]),
|
|
128
|
-
database: match[5]
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
function getGitHubVariable(name) {
|
|
132
|
-
return (0, child_process_1.execSync)(`gh variable get ${name} -R Optima-Chat/optima-dev-skills`, { encoding: 'utf-8' }).trim();
|
|
133
|
-
}
|
|
134
|
-
function getInfisicalConfig() {
|
|
135
|
-
return {
|
|
136
|
-
url: getGitHubVariable('INFISICAL_URL'),
|
|
137
|
-
clientId: getGitHubVariable('INFISICAL_CLIENT_ID'),
|
|
138
|
-
clientSecret: getGitHubVariable('INFISICAL_CLIENT_SECRET'),
|
|
139
|
-
projectId: getGitHubVariable('INFISICAL_PROJECT_ID')
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
function getInfisicalToken(config) {
|
|
143
|
-
const response = (0, child_process_1.execSync)(`curl -s -X POST "${config.url}/api/v1/auth/universal-auth/login" -H "Content-Type: application/json" -d '{"clientId": "${config.clientId}", "clientSecret": "${config.clientSecret}"}'`, { encoding: 'utf-8' });
|
|
144
|
-
return JSON.parse(response).accessToken;
|
|
145
|
-
}
|
|
146
|
-
function getInfisicalSecrets(config, token, environment, secretPath) {
|
|
147
|
-
const response = (0, child_process_1.execSync)(`curl -s "${config.url}/api/v3/secrets/raw?workspaceId=${config.projectId}&environment=${environment}&secretPath=${secretPath}" -H "Authorization: Bearer ${token}"`, { encoding: 'utf-8' });
|
|
148
|
-
const data = JSON.parse(response);
|
|
149
|
-
const secrets = {};
|
|
150
|
-
for (const secret of data.secrets || []) {
|
|
151
|
-
secrets[secret.secretKey] = secret.secretValue;
|
|
152
|
-
}
|
|
153
|
-
return secrets;
|
|
154
|
-
}
|
|
117
|
+
// parseDatabaseUrl 统一用 db-utils 的实现:右切 userinfo 容忍密码特殊字符,
|
|
118
|
+
// 且报错不回显 URL(这里曾把含密码的完整 URL 打进错误信息)。
|
|
155
119
|
function findPsqlPath() {
|
|
156
120
|
// 1. 优先从 PATH 中查找
|
|
157
121
|
const whichCmd = process.platform === 'win32' ? 'where psql' : 'which psql';
|
|
@@ -206,7 +170,7 @@ async function main() {
|
|
|
206
170
|
console.error('Usage: query-db.ts <service> <sql> [environment]');
|
|
207
171
|
console.error('');
|
|
208
172
|
console.error('Services: commerce-backend, user-auth, agentic-chat, bi-backend, session-gateway, gateway-core, optima-logistics, billing, ads-backend, amazon-backend, browser-backend, shopify-backend, optima-generation, optima-sentinel');
|
|
209
|
-
console.error('Environments: ci (default), stage, prod');
|
|
173
|
+
console.error('Environments: ci (default), stage, prod, cn (阿里云 cn-prod)');
|
|
210
174
|
console.error('');
|
|
211
175
|
console.error('Example: query-db.ts user-auth "SELECT COUNT(*) FROM users" prod');
|
|
212
176
|
process.exit(1);
|
|
@@ -217,6 +181,27 @@ async function main() {
|
|
|
217
181
|
console.error('Available services:', Object.keys(SERVICE_DB_MAP).join(', '));
|
|
218
182
|
process.exit(1);
|
|
219
183
|
}
|
|
184
|
+
// cn-prod(阿里云):独立 Infisical + 经 buildbox 跳板连内网 RDS(动态端口隧道)。
|
|
185
|
+
// 两类 cred:① shared-secrets/database-users(按 prefix)② 服务自己的 DATABASE_URL(展开引用)。
|
|
186
|
+
if ((0, db_utils_1.isCnEnv)(environment)) {
|
|
187
|
+
const prodCfg = SERVICE_DB_MAP[service].prod;
|
|
188
|
+
let db;
|
|
189
|
+
if (prodCfg?.userKey) {
|
|
190
|
+
const prefix = prodCfg.userKey.replace(/_DB_USER$/, '');
|
|
191
|
+
console.log(`\n🔍 Querying ${service} (CN-PROD, prefix ${prefix})...`);
|
|
192
|
+
db = (0, db_utils_1.connectCnDB)(prefix);
|
|
193
|
+
}
|
|
194
|
+
else if (prodCfg?.databaseUrlPath) {
|
|
195
|
+
console.log(`\n🔍 Querying ${service} (CN-PROD, DATABASE_URL @ ${prodCfg.databaseUrlPath})...`);
|
|
196
|
+
db = (0, db_utils_1.connectCnDBFromUrl)(prodCfg.databaseUrlPath);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.error(`cn-prod query 暂不支持 ${service}(既无 userKey 也无 databaseUrlPath)。见 optima-dev-skills#21。`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
console.log('\n' + db.query(sql));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
220
205
|
const serviceConfig = SERVICE_DB_MAP[service][environment];
|
|
221
206
|
if (!serviceConfig) {
|
|
222
207
|
console.error(`Service ${service} is not available in ${environment.toUpperCase()} environment.`);
|
|
@@ -228,18 +213,18 @@ async function main() {
|
|
|
228
213
|
console.log(`\n🔍 Querying ${service} (${environment.toUpperCase()})...`);
|
|
229
214
|
if (environment === 'ci') {
|
|
230
215
|
// CI 环境:通过 SSH + Docker Exec
|
|
231
|
-
const ciUser = getGitHubVariable('CI_SSH_USER');
|
|
232
|
-
const ciHost = getGitHubVariable('CI_SSH_HOST');
|
|
233
|
-
const ciPassword = getGitHubVariable('CI_SSH_PASSWORD');
|
|
216
|
+
const ciUser = (0, db_utils_1.getGitHubVariable)('CI_SSH_USER');
|
|
217
|
+
const ciHost = (0, db_utils_1.getGitHubVariable)('CI_SSH_HOST');
|
|
218
|
+
const ciPassword = (0, db_utils_1.getGitHubVariable)('CI_SSH_PASSWORD');
|
|
234
219
|
const { container, user, database } = serviceConfig;
|
|
235
220
|
const result = (0, child_process_1.execSync)(`sshpass -p "${ciPassword}" ssh -o StrictHostKeyChecking=no ${ciUser}@${ciHost} "docker exec ${container} psql -U ${user} -d ${database} -c \\"${sql}\\""`, { encoding: 'utf-8' });
|
|
236
221
|
console.log('\n' + result);
|
|
237
222
|
}
|
|
238
223
|
else {
|
|
239
224
|
// Stage/Prod 环境:通过 SSH 隧道访问 RDS
|
|
240
|
-
const infisicalConfig = getInfisicalConfig();
|
|
225
|
+
const infisicalConfig = (0, db_utils_1.getInfisicalConfig)();
|
|
241
226
|
console.log('✓ Loaded Infisical config from GitHub Variables');
|
|
242
|
-
const token = getInfisicalToken(infisicalConfig);
|
|
227
|
+
const token = (0, db_utils_1.getInfisicalToken)(infisicalConfig);
|
|
243
228
|
console.log('✓ Obtained Infisical access token');
|
|
244
229
|
const infisicalEnv = environment === 'stage' ? 'staging' : 'prod';
|
|
245
230
|
let dbUser;
|
|
@@ -249,13 +234,13 @@ async function main() {
|
|
|
249
234
|
if ('databaseUrlPath' in serviceConfig) {
|
|
250
235
|
// 从服务路径获取 DATABASE_URL 并解析
|
|
251
236
|
const { databaseUrlPath, databaseUrlKey } = serviceConfig;
|
|
252
|
-
const secrets = getInfisicalSecrets(infisicalConfig, token, infisicalEnv, databaseUrlPath);
|
|
237
|
+
const secrets = (0, db_utils_1.getInfisicalSecrets)(infisicalConfig, token, infisicalEnv, databaseUrlPath);
|
|
253
238
|
console.log(`✓ Retrieved DATABASE_URL from Infisical (path: ${databaseUrlPath})`);
|
|
254
239
|
const databaseUrl = secrets[databaseUrlKey];
|
|
255
240
|
if (!databaseUrl) {
|
|
256
241
|
throw new Error(`DATABASE_URL not found in Infisical at ${databaseUrlPath}`);
|
|
257
242
|
}
|
|
258
|
-
const parsed = parseDatabaseUrl(databaseUrl);
|
|
243
|
+
const parsed = (0, db_utils_1.parseDatabaseUrl)(databaseUrl);
|
|
259
244
|
dbUser = parsed.user;
|
|
260
245
|
dbPassword = parsed.password;
|
|
261
246
|
dbHost = parsed.host;
|
|
@@ -263,7 +248,7 @@ async function main() {
|
|
|
263
248
|
}
|
|
264
249
|
else {
|
|
265
250
|
// 从 shared-secrets/database-users 获取凭证
|
|
266
|
-
const secrets = getInfisicalSecrets(infisicalConfig, token, infisicalEnv, '/shared-secrets/database-users');
|
|
251
|
+
const secrets = (0, db_utils_1.getInfisicalSecrets)(infisicalConfig, token, infisicalEnv, '/shared-secrets/database-users');
|
|
267
252
|
console.log('✓ Retrieved database credentials from Infisical');
|
|
268
253
|
const { userKey, passwordKey } = serviceConfig;
|
|
269
254
|
database = serviceConfig.database;
|
|
@@ -274,8 +259,7 @@ async function main() {
|
|
|
274
259
|
throw new Error(`Database credentials not found in Infisical for ${service}. Keys: ${userKey}, ${passwordKey}`);
|
|
275
260
|
}
|
|
276
261
|
}
|
|
277
|
-
const localPort =
|
|
278
|
-
(0, db_utils_1.setupTunnel)(dbHost, localPort);
|
|
262
|
+
const localPort = (0, db_utils_1.ensureTunnel)(dbHost);
|
|
279
263
|
const result = queryDatabase('localhost', localPort, dbUser, dbPassword, database, sql);
|
|
280
264
|
console.log('\n' + result);
|
|
281
265
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
|
+
const db_utils_1 = require("./db-utils");
|
|
5
6
|
// 支持的服务列表(Infisical 路径为 /services/<service-name>)
|
|
6
7
|
const SUPPORTED_SERVICES = [
|
|
7
8
|
'ads-backend',
|
|
@@ -35,21 +36,8 @@ const ENV_MAP = {
|
|
|
35
36
|
stage: 'staging',
|
|
36
37
|
prod: 'prod'
|
|
37
38
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
function getInfisicalConfig() {
|
|
42
|
-
return {
|
|
43
|
-
url: getGitHubVariable('INFISICAL_URL'),
|
|
44
|
-
clientId: getGitHubVariable('INFISICAL_CLIENT_ID'),
|
|
45
|
-
clientSecret: getGitHubVariable('INFISICAL_CLIENT_SECRET'),
|
|
46
|
-
projectId: getGitHubVariable('INFISICAL_PROJECT_ID')
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function getInfisicalToken(config) {
|
|
50
|
-
const response = (0, child_process_1.execSync)(`curl -s -X POST "${config.url}/api/v1/auth/universal-auth/login" -H "Content-Type: application/json" -d '{"clientId": "${config.clientId}", "clientSecret": "${config.clientSecret}"}'`, { encoding: 'utf-8' });
|
|
51
|
-
return JSON.parse(response).accessToken;
|
|
52
|
-
}
|
|
39
|
+
// NOTE: getInfisicalSecrets is kept local because it encodes secretPath (encodeURIComponent),
|
|
40
|
+
// unlike db-utils' raw-path variant; getGitHubVariable/getInfisicalConfig/getInfisicalToken are shared from db-utils.
|
|
53
41
|
function getInfisicalSecrets(config, token, environment, secretPath) {
|
|
54
42
|
const response = (0, child_process_1.execSync)(`curl -s "${config.url}/api/v3/secrets/raw?workspaceId=${config.projectId}&environment=${environment}&secretPath=${encodeURIComponent(secretPath)}" -H "Authorization: Bearer ${token}"`, { encoding: 'utf-8' });
|
|
55
43
|
const data = JSON.parse(response);
|
|
@@ -122,9 +110,9 @@ async function main() {
|
|
|
122
110
|
const secretPath = `/services/${service}`;
|
|
123
111
|
console.log(`\n🔍 Fetching environment variables for ${service} (${environment.toUpperCase()})...\n`);
|
|
124
112
|
try {
|
|
125
|
-
const infisicalConfig = getInfisicalConfig();
|
|
113
|
+
const infisicalConfig = (0, db_utils_1.getInfisicalConfig)();
|
|
126
114
|
console.log('✓ Loaded Infisical config from GitHub Variables');
|
|
127
|
-
const token = getInfisicalToken(infisicalConfig);
|
|
115
|
+
const token = (0, db_utils_1.getInfisicalToken)(infisicalConfig);
|
|
128
116
|
console.log('✓ Obtained Infisical access token');
|
|
129
117
|
const infisicalEnv = ENV_MAP[environment];
|
|
130
118
|
const secrets = getInfisicalSecrets(infisicalConfig, token, infisicalEnv, secretPath);
|
|
@@ -55,7 +55,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
55
55
|
* optima-verify-health user-auth --env prod # 环境 stage|prod|cn|all
|
|
56
56
|
* optima-verify-health --all --env all # stage/prod/cn 三环境矩阵
|
|
57
57
|
* optima-verify-health gateway-core --expect-commit a1b2c3d
|
|
58
|
-
* optima-verify-health --url https://auth
|
|
58
|
+
* optima-verify-health --url https://auth.yzsgo.com/health
|
|
59
59
|
* optima-verify-health --all --json # 机器可读,接 CI
|
|
60
60
|
* optima-verify-health --all --strict # warn 也算不通过
|
|
61
61
|
* 退出码:fail → 非 0;warn 默认放行(0),--strict 让 warn 也非 0。
|
|
@@ -64,15 +64,16 @@ const node_dns_1 = require("node:dns");
|
|
|
64
64
|
const tls = __importStar(require("node:tls"));
|
|
65
65
|
const https = __importStar(require("node:https"));
|
|
66
66
|
// 服务 × 环境 FQDN 表。某服务某环境没部署 → 该 env 键缺省,探时跳过。
|
|
67
|
-
// cn-prod 真实 subdomain 抄自 optima-terraform
|
|
67
|
+
// cn-prod 真实 subdomain 抄自 optima-terraform alicloud/stacks/cn-prod-ingress-sae/main.tf。
|
|
68
|
+
// #201 (2026-06-12): yzsgo.com 全量迁移完成,旧 *-cn.optima.chat 路由已下线。
|
|
68
69
|
const SERVICES = {
|
|
69
|
-
'user-auth': { path: '/health', stage: 'auth-stage.optima.onl', prod: 'auth.optima.onl', cn: 'auth
|
|
70
|
-
'agentic-chat': { path: '/api/health', stage: 'ai-stage.optima.onl', prod: 'ai.optima.onl', cn: '
|
|
71
|
-
'commerce-backend': { path: '/health', stage: 'api-stage.optima.onl', prod: 'api.optima.onl', cn: '
|
|
70
|
+
'user-auth': { path: '/health', stage: 'auth-stage.optima.onl', prod: 'auth.optima.onl', cn: 'auth.yzsgo.com' },
|
|
71
|
+
'agentic-chat': { path: '/api/health', stage: 'ai-stage.optima.onl', prod: 'ai.optima.onl', cn: 'app.yzsgo.com' },
|
|
72
|
+
'commerce-backend': { path: '/health', stage: 'api-stage.optima.onl', prod: 'api.optima.onl', cn: 'commerce.yzsgo.com', cn_path: '/health/live' },
|
|
72
73
|
'mcp-host': { path: '/health', stage: 'mcp-stage.optima.onl', prod: 'mcp.optima.onl' },
|
|
73
|
-
'gateway-core': { path: '/health', cn: 'gw
|
|
74
|
-
'optima-scout': { path: '/health', cn: 'scout
|
|
75
|
-
'optima-skills': { path: '/health', cn: 'skills
|
|
74
|
+
'gateway-core': { path: '/health', cn: 'gw.yzsgo.com' },
|
|
75
|
+
'optima-scout': { path: '/health', cn: 'scout.yzsgo.com' },
|
|
76
|
+
'optima-skills': { path: '/health', cn: 'skills.yzsgo.com' },
|
|
76
77
|
};
|
|
77
78
|
const ENVS = ['stage', 'prod', 'cn'];
|
|
78
79
|
const G = '\x1b[32m', R = '\x1b[31m', Y = '\x1b[33m', B = '\x1b[34m', N = '\x1b[0m';
|