@ranger1/dx 0.1.45 → 0.1.48

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.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: review (Codex)
3
3
  mode: subagent
4
- model: openai/gpt-5.2-codex
4
+ model: openai/gpt-5.3-codex
5
5
  temperature: 0.1
6
6
  tools:
7
7
  write: true
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: build PR context file
3
3
  mode: subagent
4
- model: openai/gpt-5.1-codex-mini
4
+ model: github-copilot/claude-sonnet-4.5
5
5
  temperature: 0.1
6
6
  tools:
7
7
  bash: true
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: PR fix review
3
3
  mode: subagent
4
- model: openai/gpt-5.2-codex
4
+ model: openai/gpt-5.3-codex
5
5
  temperature: 0.1
6
6
  tools:
7
7
  write: true
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: PR precheck (checkout + lint + build)
3
3
  mode: subagent
4
- model: openai/gpt-5.2-codex
4
+ model: openai/gpt-5.3-codex
5
5
  temperature: 0.1
6
6
  tools:
7
7
  bash: true
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  allowed-tools: [Bash, Read, Glob, TodoWrite, Edit, Grep]
3
3
  description: 'Git 工作流:Issue/Commit/PR 自动化'
4
- agent: middle
4
+ agent: documenter
5
5
  ---
6
6
 
7
7
  ## 用法
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  allowed-tools: [Bash, Read, Glob, TodoWrite, Edit, Grep, AskUserQuestion]
3
3
  description: 'Create release versions with intelligent changelog generation from release branch'
4
- agent: quick
4
+ agent: documenter
5
5
  ---
6
6
 
7
7
  ## Usage
@@ -15,10 +15,10 @@
15
15
  "variant": "high"
16
16
  },
17
17
  "librarian": {
18
- "model": "openai/gpt-5.1-codex-mini"
18
+ "model": "github-copilot/claude-sonnet-4.5"
19
19
  },
20
20
  "explore": {
21
- "model": "openai/gpt-5.1-codex-mini"
21
+ "model": "github-copilot/claude-sonnet-4.5"
22
22
  },
23
23
  "multimodal-looker": {
24
24
  "model": "github-copilot/gemini-3-flash-preview"
@@ -14,6 +14,9 @@
14
14
  },
15
15
  "middle": {
16
16
  "model": "github-copilot/claude-sonnet-4.5"
17
+ },
18
+ "documenter": {
19
+ "model": "github-copilot/claude-sonnet-4.5"
17
20
  }
18
21
  },
19
22
  "permission": {
package/lib/cli/args.js CHANGED
@@ -1,9 +1,7 @@
1
1
  export function getCleanArgs(args = []) {
2
2
  const result = []
3
- let afterDoubleDash = false
4
3
  for (const arg of args) {
5
4
  if (arg === '--') {
6
- afterDoubleDash = true
7
5
  break
8
6
  }
9
7
  if (arg.startsWith('-')) continue
@@ -12,6 +10,27 @@ export function getCleanArgs(args = []) {
12
10
  return result
13
11
  }
14
12
 
13
+ // Like getCleanArgs(), but also strips values consumed by flags that expect a value.
14
+ // consumedFlagValueIndexes: Set<number> of indexes in the original argv that should be skipped.
15
+ export function getCleanArgsWithConsumedValues(args = [], consumedFlagValueIndexes = new Set()) {
16
+ const result = []
17
+ const consumed = consumedFlagValueIndexes instanceof Set
18
+ ? consumedFlagValueIndexes
19
+ : new Set(consumedFlagValueIndexes || [])
20
+
21
+ for (let i = 0; i < args.length; i++) {
22
+ const arg = args[i]
23
+ if (arg === '--') {
24
+ break
25
+ }
26
+ if (consumed.has(i)) continue
27
+ if (arg.startsWith('-')) continue
28
+ result.push(arg)
29
+ }
30
+
31
+ return result
32
+ }
33
+
15
34
  export function getPassthroughArgs(args = []) {
16
35
  const doubleDashIndex = args.indexOf('--')
17
36
  if (doubleDashIndex === -1) return []
@@ -2,6 +2,28 @@ import { logger } from '../../logger.js'
2
2
  import { envManager } from '../../env.js'
3
3
  import { validateEnvironment } from '../../validate-env.js'
4
4
 
5
+ export function parseTelegramWebhookFlags(argv = []) {
6
+ const args = Array.isArray(argv) ? argv : []
7
+
8
+ const idx = args.indexOf('--webhook-path')
9
+ const webhookPath = idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined
10
+
11
+ const dryRun = args.includes('--webhook-dry-run') ? true : undefined
12
+
13
+ // 默认值由下游根据 environment 决定,这里只负责覆盖
14
+ const strict = args.includes('--strict-webhook')
15
+ ? true
16
+ : args.includes('--no-strict-webhook')
17
+ ? false
18
+ : undefined
19
+
20
+ return {
21
+ webhookPath,
22
+ dryRun,
23
+ strict,
24
+ }
25
+ }
26
+
5
27
  export async function handleDeploy(cli, args) {
6
28
  const target = args[0]
7
29
  if (!target) {
@@ -59,7 +81,9 @@ export async function handleDeploy(cli, args) {
59
81
  // 加载环境变量层,但不校验后端必需变量
60
82
  const layeredEnv = envManager.collectEnvFromLayers(null, environment)
61
83
  if (envManager.latestEnvWarnings && envManager.latestEnvWarnings.length > 0) {
62
- envManager.latestEnvWarnings.forEach(message => logger.warn(message))
84
+ envManager.latestEnvWarnings.forEach(message => {
85
+ logger.warn(message)
86
+ })
63
87
  }
64
88
 
65
89
  // 仅在目标变量不存在或是占位符时才使用 .env 文件的值
@@ -72,5 +96,10 @@ export async function handleDeploy(cli, args) {
72
96
  envManager.syncEnvironments(environment)
73
97
 
74
98
  const { deployToVercel } = await import('../../vercel-deploy.js')
75
- await deployToVercel(normalizedTarget, { environment })
99
+
100
+ const telegramWebhook = normalizedTarget === 'telegram-bot'
101
+ ? parseTelegramWebhookFlags(cli.args)
102
+ : null
103
+
104
+ await deployToVercel(normalizedTarget, { environment, telegramWebhook })
76
105
  }
package/lib/cli/dx-cli.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  resolveTargetRequiredVars,
12
12
  } from '../env-policy.js'
13
13
  import { FLAG_DEFINITIONS, parseFlags } from './flags.js'
14
- import { getCleanArgs } from './args.js'
14
+ import { getCleanArgs, getCleanArgsWithConsumedValues } from './args.js'
15
15
  import { showHelp, showCommandHelp } from './help.js'
16
16
  import { getPackageVersion } from '../version.js'
17
17
  import {
@@ -175,7 +175,9 @@ class DxCli {
175
175
  const envKey = this.normalizeEnvKey(environment)
176
176
  const execFlags = { ...this.flags }
177
177
  ;['dev', 'development', 'prod', 'production', 'test', 'e2e', 'staging', 'stage'].forEach(
178
- key => delete execFlags[key]
178
+ key => {
179
+ delete execFlags[key]
180
+ },
179
181
  )
180
182
  if (envKey === 'prod') execFlags.prod = true
181
183
  else if (envKey === 'dev') execFlags.dev = true
@@ -329,8 +331,12 @@ class DxCli {
329
331
 
330
332
  // 命令路由
331
333
  async routeCommand() {
332
- const cleanArgs = getCleanArgs(this.args)
333
- const [command, ...subArgs] = cleanArgs
334
+ const command = getCleanArgs(this.args)[0]
335
+
336
+ const allowedFlags = this.getAllowedFlags(command)
337
+ const consumedFlagValueIndexes = this.validateFlags(command, allowedFlags)
338
+ const cleanArgs = getCleanArgsWithConsumedValues(this.args, consumedFlagValueIndexes)
339
+ const [, ...subArgs] = cleanArgs
334
340
 
335
341
  if (!command) {
336
342
  showHelp()
@@ -747,13 +753,17 @@ class DxCli {
747
753
  const portSet = new Set()
748
754
 
749
755
  if (startConfig && Array.isArray(startConfig.ports)) {
750
- startConfig.ports.forEach(port => this.addPortToSet(portSet, port))
756
+ startConfig.ports.forEach(port => {
757
+ this.addPortToSet(portSet, port)
758
+ })
751
759
  }
752
760
 
753
761
  if (envKey === 'dev') {
754
762
  const legacyConfig = this.commands.dev?.[service]
755
763
  if (legacyConfig && Array.isArray(legacyConfig.ports)) {
756
- legacyConfig.ports.forEach(port => this.addPortToSet(portSet, port))
764
+ legacyConfig.ports.forEach(port => {
765
+ this.addPortToSet(portSet, port)
766
+ })
757
767
  }
758
768
  }
759
769
 
package/lib/cli/flags.js CHANGED
@@ -37,6 +37,12 @@ export const FLAG_DEFINITIONS = {
37
37
  lint: [
38
38
  { flag: '--fix' },
39
39
  ],
40
+ deploy: [
41
+ { flag: '--webhook-path', expectsValue: true },
42
+ { flag: '--webhook-dry-run' },
43
+ { flag: '--strict-webhook' },
44
+ { flag: '--no-strict-webhook' },
45
+ ],
40
46
  }
41
47
 
42
48
  export function parseFlags(args = []) {
package/lib/cli/help.js CHANGED
@@ -184,17 +184,26 @@ script 子命令:
184
184
 
185
185
  case 'deploy':
186
186
  console.log(`
187
- deploy 命令用法:
188
- dx deploy <target> [环境标志]
187
+ deploy 命令用法:
188
+ dx deploy <target> [环境标志] [选项]
189
189
 
190
- 参数说明:
190
+ 参数说明:
191
191
  target: front, admin, telegram-bot, all
192
192
  环境标志: --dev、--staging、--prod(默认 --staging)
193
193
 
194
- 常见示例:
194
+ Telegram Webhook(仅 target=telegram-bot 生效):
195
+ --webhook-path <path> 对外 webhook 路径(默认 /api/webhook)
196
+ --webhook-dry-run 只打印将设置的 URL,不调用 Telegram API
197
+ --strict-webhook 强制严格校验(Webhook 不生效则 deploy 失败)
198
+ --no-strict-webhook 关闭严格校验(仅告警)
199
+
200
+ 常见示例:
195
201
  dx deploy front --staging # 部署用户前端(staging)
196
202
  dx deploy admin --prod # 部署管理后台(生产)
197
203
  dx deploy telegram-bot --staging # 部署 Telegram Bot + 自动配置 Webhook
204
+ dx deploy telegram-bot --staging --webhook-path /webhook # 使用短路径(rewrite 到 /api/webhook)
205
+ dx deploy telegram-bot --prod --webhook-dry-run # 仅打印,不实际调用 Telegram
206
+ dx deploy telegram-bot --prod --no-strict-webhook # 生产环境也仅告警(不推荐)
198
207
  dx deploy all --staging # 串行部署 front + admin
199
208
  `)
200
209
  return
@@ -2,12 +2,89 @@ import { execSync } from 'node:child_process'
2
2
  import { logger } from './logger.js'
3
3
  import { envManager } from './env.js'
4
4
 
5
+ function normalizeWebhookPath(raw) {
6
+ const s = String(raw || '').trim()
7
+ if (!s) return '/api/webhook'
8
+ if (s.startsWith('/')) return s
9
+ return `/${s}`
10
+ }
11
+
12
+ function normalizeDeployUrl(raw) {
13
+ const s = String(raw || '')
14
+ const m = s.match(/(https?:\/\/)?([a-z0-9-]+\.vercel\.app)\b/i)
15
+ if (!m) return null
16
+ return `https://${m[2]}`
17
+ }
18
+
19
+ export function parseDeployUrlFromDeployOutput(output) {
20
+ const s = String(output || '')
21
+ const matches = [...s.matchAll(/(https?:\/\/)?([a-z0-9-]+\.vercel\.app)\b/gi)]
22
+ if (matches.length === 0) return null
23
+ const last = matches[matches.length - 1]
24
+ return `https://${last[2]}`
25
+ }
26
+
27
+ export function parseDeployUrlFromVercelListOutput(output, projectNameHint) {
28
+ const lines = String(output || '')
29
+ .split(/\r?\n/)
30
+ .map(l => l.trim())
31
+ .filter(Boolean)
32
+
33
+ const isReady = line => /\b(Ready|READY)\b/.test(line)
34
+ const hasUrl = line => /\b[a-z0-9-]+\.vercel\.app\b/i.test(line)
35
+ const pickUrl = line => {
36
+ const m = line.match(/(https?:\/\/)?([a-z0-9-]+\.vercel\.app)\b/i)
37
+ return m ? `https://${m[2]}` : null
38
+ }
39
+
40
+ const hint = projectNameHint ? String(projectNameHint) : ''
41
+ if (hint) {
42
+ for (const line of lines) {
43
+ if (!line.includes(hint)) continue
44
+ if (!isReady(line)) continue
45
+ if (!hasUrl(line)) continue
46
+ const url = pickUrl(line)
47
+ if (url) return url
48
+ }
49
+ }
50
+
51
+ for (const line of lines) {
52
+ if (!isReady(line)) continue
53
+ if (!hasUrl(line)) continue
54
+ const url = pickUrl(line)
55
+ if (url) return url
56
+ }
57
+
58
+ return null
59
+ }
60
+
5
61
  /**
6
62
  * 处理 Telegram Bot 部署后的 Webhook 配置
7
63
  */
8
- export async function handleTelegramBotDeploy(environment, projectId, orgId, token) {
64
+ export async function handleTelegramBotDeploy(environment, projectId, orgId, token, options = {}) {
9
65
  logger.step('配置 Telegram Webhook...')
10
66
 
67
+ const {
68
+ deployOutput,
69
+ projectNameHint,
70
+ webhookPath: webhookPathOverride,
71
+ dryRun: dryRunOverride,
72
+ strict: strictOverride,
73
+ } = options || {}
74
+
75
+ const strictDefault = environment !== 'development'
76
+
77
+ const strictEnv = process.env.DX_TELEGRAM_WEBHOOK_STRICT != null
78
+ ? !['0', 'false', 'no'].includes(String(process.env.DX_TELEGRAM_WEBHOOK_STRICT).toLowerCase())
79
+ : undefined
80
+
81
+ const strict = strictOverride ?? strictEnv ?? strictDefault
82
+
83
+ const dryRunEnv = ['1', 'true', 'yes'].includes(String(process.env.DX_TELEGRAM_WEBHOOK_DRY_RUN || '').toLowerCase())
84
+ const dryRun = dryRunOverride ?? (dryRunEnv ? true : false)
85
+
86
+ const webhookPath = normalizeWebhookPath(webhookPathOverride ?? process.env.DX_TELEGRAM_WEBHOOK_PATH ?? '/api/webhook')
87
+
11
88
  // 1. 验证必需环境变量
12
89
  const botToken = process.env.TELEGRAM_BOT_TOKEN
13
90
  const webhookSecret = process.env.TELEGRAM_BOT_WEBHOOK_SECRET
@@ -25,21 +102,42 @@ export async function handleTelegramBotDeploy(environment, projectId, orgId, tok
25
102
  missingVars.forEach(v => {
26
103
  logger.error(` - ${v}`)
27
104
  })
105
+
106
+ if (strict) {
107
+ throw new Error('Telegram Webhook 配置失败:缺少必需环境变量')
108
+ }
109
+
28
110
  logger.warn('跳过 Webhook 配置,请手动设置')
29
111
  return
30
112
  }
31
113
 
32
114
  try {
33
115
  // 2. 获取 Vercel 部署 URL
34
- const deploymentUrl = await getLatestDeploymentUrl(projectId, orgId, token, environment)
116
+ const deploymentUrl = await getLatestDeploymentUrl({
117
+ projectId,
118
+ orgId,
119
+ token,
120
+ environment,
121
+ deployOutput,
122
+ projectNameHint,
123
+ })
35
124
  if (!deploymentUrl) {
125
+ if (strict) {
126
+ throw new Error('无法获取 Vercel 部署 URL')
127
+ }
128
+
36
129
  logger.error('无法获取 Vercel 部署 URL,跳过 Webhook 配置')
37
130
  return
38
131
  }
39
132
 
40
- const webhookUrl = `${deploymentUrl}/api/webhook`
133
+ const webhookUrl = `${deploymentUrl}${webhookPath}`
41
134
  logger.info(`Webhook URL: ${webhookUrl}`)
42
135
 
136
+ if (dryRun) {
137
+ logger.warn('DX_TELEGRAM_WEBHOOK_DRY_RUN=1,已跳过 setWebhook/getWebhookInfo 调用')
138
+ return
139
+ }
140
+
43
141
  // 3. 调用 Telegram API 设置 Webhook
44
142
  const telegramApiUrl = `https://api.telegram.org/bot${botToken}/setWebhook`
45
143
  const payload = JSON.stringify({
@@ -65,10 +163,15 @@ export async function handleTelegramBotDeploy(environment, projectId, orgId, tok
65
163
  logger.info(`Webhook URL: ${webhookUrl}`)
66
164
 
67
165
  // 4. 验证 Webhook 状态
68
- await verifyWebhook(botToken)
166
+ await verifyWebhook(botToken, webhookUrl, { strict })
69
167
  }
70
168
  else {
71
- logger.error(`Telegram Webhook 设置失败: ${result.description}`)
169
+ const desc = result.description || '未知错误'
170
+ if (strict) {
171
+ throw new Error(`Telegram Webhook 设置失败: ${desc}`)
172
+ }
173
+
174
+ logger.error(`Telegram Webhook 设置失败: ${desc}`)
72
175
  logger.info('请手动执行以下命令(不要把明文 token/secret 写进日志):')
73
176
  const manualPayload = JSON.stringify({
74
177
  url: webhookUrl,
@@ -81,7 +184,11 @@ export async function handleTelegramBotDeploy(environment, projectId, orgId, tok
81
184
  }
82
185
  }
83
186
  catch (error) {
84
- logger.error(`Webhook 配置失败: ${error.message}`)
187
+ const message = error?.message || String(error)
188
+ logger.error(`Webhook 配置失败: ${message}`)
189
+
190
+ if (strict) throw error
191
+
85
192
  logger.warn('请手动设置 Webhook(参考 apps/telegram-bot/README.md)')
86
193
  }
87
194
  }
@@ -89,9 +196,86 @@ export async function handleTelegramBotDeploy(environment, projectId, orgId, tok
89
196
  /**
90
197
  * 获取最新部署的 URL
91
198
  */
92
- async function getLatestDeploymentUrl(projectId, orgId, token, environment) {
199
+ async function getLatestDeploymentUrl({
200
+ projectId,
201
+ orgId,
202
+ token,
203
+ environment,
204
+ deployOutput,
205
+ projectNameHint,
206
+ }) {
207
+ const fromDeploy = parseDeployUrlFromDeployOutput(deployOutput)
208
+ if (fromDeploy) return fromDeploy
209
+
210
+ const fromApi = await getDeploymentUrlFromVercelApi({ projectId, orgId, token, environment })
211
+ if (fromApi) return fromApi
212
+
213
+ const fromList = await getDeploymentUrlFromVercelList({ orgId, token, projectNameHint })
214
+ if (fromList) return fromList
215
+
216
+ return null
217
+ }
218
+
219
+ export function pickDeploymentUrlFromVercelApiResponse(json) {
220
+ const deployments = json?.deployments
221
+ if (!Array.isArray(deployments)) return null
222
+
223
+ for (const d of deployments) {
224
+ const url = d?.url
225
+ if (!url) continue
226
+ const state = d?.state || d?.readyState
227
+ if (state && String(state).toUpperCase() !== 'READY') continue
228
+ const normalized = normalizeDeployUrl(url)
229
+ if (normalized) return normalized
230
+ }
231
+
232
+ return null
233
+ }
234
+
235
+ async function getDeploymentUrlFromVercelApi({ projectId, orgId, token, environment }) {
236
+ try {
237
+ const qs = new URLSearchParams({
238
+ projectId: String(projectId),
239
+ state: 'READY',
240
+ limit: '10',
241
+ })
242
+
243
+ // dx 的 deploy 实现里:staging/production 都会传 --prod,因此对应 Vercel 的 production target。
244
+ // development 环境若需要兜底查询,则不强制 target(避免与 Vercel CLI/REST 字段差异耦合)。
245
+ if (environment !== 'development') {
246
+ qs.set('target', 'production')
247
+ }
248
+
249
+ if (orgId) {
250
+ const scope = String(orgId)
251
+ if (scope.startsWith('team_')) qs.set('teamId', scope)
252
+ else qs.set('slug', scope)
253
+ }
254
+
255
+ const url = `https://api.vercel.com/v6/deployments?${qs.toString()}`
256
+ const res = await fetch(url, {
257
+ method: 'GET',
258
+ headers: {
259
+ Authorization: `Bearer ${token}`,
260
+ },
261
+ })
262
+
263
+ if (!res.ok) {
264
+ logger.warn(`Vercel API 获取部署列表失败: HTTP ${res.status}`)
265
+ return null
266
+ }
267
+
268
+ const json = await res.json()
269
+ return pickDeploymentUrlFromVercelApiResponse(json)
270
+ } catch (error) {
271
+ logger.warn(`Vercel API 获取部署列表失败: ${error?.message || String(error)}`)
272
+ return null
273
+ }
274
+ }
275
+
276
+ async function getDeploymentUrlFromVercelList({ orgId, token, projectNameHint }) {
93
277
  try {
94
- const cmd = ['vercel', 'ls', orgId ? `--scope=${orgId}` : '', '--json']
278
+ const cmd = ['vercel', 'list', orgId ? `--scope=${orgId}` : '']
95
279
  .filter(Boolean)
96
280
  .join(' ')
97
281
 
@@ -99,24 +283,13 @@ async function getLatestDeploymentUrl(projectId, orgId, token, environment) {
99
283
  encoding: 'utf8',
100
284
  env: {
101
285
  ...process.env,
102
- // 不通过 CLI args 传递 token,避免出现在错误信息/日志中
103
286
  VERCEL_TOKEN: token,
104
287
  },
105
288
  })
106
- const deployments = JSON.parse(output)
107
-
108
- // 根据环境筛选部署
109
- const targetEnv = environment === 'production' ? 'production' : 'preview'
110
- const latest = deployments.find(d =>
111
- d.projectId === projectId
112
- && d.target === targetEnv
113
- && d.state === 'READY',
114
- )
115
289
 
116
- return latest ? `https://${latest.url}` : null
117
- }
118
- catch (error) {
119
- logger.warn(`获取部署 URL 失败: ${error.message}`)
290
+ return parseDeployUrlFromVercelListOutput(output, projectNameHint)
291
+ } catch (error) {
292
+ logger.warn(`vercel list 获取部署 URL 失败: ${error?.message || String(error)}`)
120
293
  return null
121
294
  }
122
295
  }
@@ -124,7 +297,8 @@ async function getLatestDeploymentUrl(projectId, orgId, token, environment) {
124
297
  /**
125
298
  * 验证 Webhook 配置
126
299
  */
127
- async function verifyWebhook(botToken) {
300
+ async function verifyWebhook(botToken, expectedWebhookUrl, options = {}) {
301
+ const { strict = false } = options || {}
128
302
  try {
129
303
  const cmd = `curl -s "https://api.telegram.org/bot${botToken}/getWebhookInfo"`
130
304
  const response = execSync(cmd, { encoding: 'utf8' })
@@ -138,9 +312,22 @@ async function verifyWebhook(botToken) {
138
312
  if (info.last_error_message) {
139
313
  logger.warn(` 最后错误: ${info.last_error_message}`)
140
314
  }
315
+
316
+ if (expectedWebhookUrl && info.url !== expectedWebhookUrl) {
317
+ const message = `Webhook 未生效:期望 ${expectedWebhookUrl},实际 ${info.url || '(empty)'}`
318
+ if (strict) throw new Error(message)
319
+ logger.warn(message)
320
+ }
321
+ }
322
+ else {
323
+ const desc = result?.description || '未知错误'
324
+ const message = `getWebhookInfo 失败: ${desc}`
325
+ if (strict) throw new Error(message)
326
+ logger.warn(message)
141
327
  }
142
328
  }
143
329
  catch (error) {
330
+ if (strict) throw error
144
331
  logger.warn('无法验证 Webhook 状态')
145
332
  }
146
333
  }
@@ -87,21 +87,21 @@ export async function deployPrebuiltWithFallback(options) {
87
87
  } = options || {}
88
88
 
89
89
  try {
90
- await run(baseArgs, { env, cwd })
91
- return { usedArchive: false }
90
+ const result = await run(baseArgs, { env, cwd })
91
+ return { usedArchive: false, result }
92
92
  } catch (e) {
93
93
  if (!isMissingFilesError(e)) throw e
94
94
  onMissingFiles(e)
95
95
  cleanupArchiveParts()
96
96
  const archiveArgs = baseArgs.slice()
97
97
  archiveArgs.splice(2, 0, '--archive=tgz')
98
- await run(archiveArgs, { env, cwd })
99
- return { usedArchive: true }
98
+ const result = await run(archiveArgs, { env, cwd })
99
+ return { usedArchive: true, result }
100
100
  }
101
101
  }
102
102
 
103
103
  export async function deployToVercel(target, options = {}) {
104
- const { environment = 'staging' } = options
104
+ const { environment = 'staging', telegramWebhook = null } = options
105
105
 
106
106
  // 校验环境参数
107
107
  if (!ALLOWED_ENVIRONMENTS.includes(environment)) {
@@ -264,17 +264,27 @@ export async function deployToVercel(target, options = {}) {
264
264
  baseDeployArgs.push('--scope', orgId)
265
265
  }
266
266
 
267
- await deployPrebuiltWithFallback({
267
+ const deployResult = await deployPrebuiltWithFallback({
268
268
  baseArgs: baseDeployArgs,
269
269
  env: envVars,
270
270
  cwd: process.cwd(),
271
271
  })
272
- logger.success(`${t} 部署成功`)
273
272
 
274
- // Telegram Bot 部署成功后自动设置 Webhook
273
+ const deployOutput = [deployResult?.result?.stdout, deployResult?.result?.stderr]
274
+ .filter(Boolean)
275
+ .join('\n')
276
+
277
+ // Telegram Bot 部署成功后自动设置 Webhook(并做严格校验)
275
278
  if (t === 'telegram-bot') {
276
279
  const { handleTelegramBotDeploy } = await import('./telegram-webhook.js')
277
- await handleTelegramBotDeploy(environment, projectId, orgId, token)
280
+ await handleTelegramBotDeploy(environment, projectId, orgId, token, {
281
+ deployOutput,
282
+ projectNameHint: 'telegram-bot',
283
+ ...(telegramWebhook || {}),
284
+ })
285
+ logger.success(`${t} 部署成功(Webhook 已校验)`)
286
+ } else {
287
+ logger.success(`${t} 部署成功`)
278
288
  }
279
289
  } catch (error) {
280
290
  const message = error?.message || String(error)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.45",
3
+ "version": "0.1.48",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {