@ranger1/dx 0.1.57 → 0.1.59

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.
@@ -11,23 +11,29 @@ const TARGET_CONFIGS = {
11
11
  front: {
12
12
  configFile: 'vercel.front.json',
13
13
  projectIdEnvVar: 'VERCEL_PROJECT_ID_FRONT',
14
- deployCwd: 'apps/front',
14
+ deployCwd: '.',
15
+ deployMode: 'prebuilt',
16
+ prebuiltCwd: '.'
15
17
  },
16
18
  admin: {
17
19
  configFile: 'vercel.admin.json',
18
20
  projectIdEnvVar: 'VERCEL_PROJECT_ID_ADMIN',
19
21
  deployCwd: 'apps/admin-front',
22
+ deployMode: 'prebuilt'
20
23
  },
21
24
  'telegram-bot': {
22
25
  configFile: 'vercel.telegram-bot.json',
23
26
  projectIdEnvVar: 'VERCEL_PROJECT_ID_TELEGRAM_BOT',
24
- },
27
+ deployMode: 'prebuilt'
28
+ }
25
29
  }
26
30
 
31
+ const ALLOWED_DEPLOY_MODES = ['prebuilt']
32
+
27
33
  const APP_ENV_MAP = {
28
34
  development: 'dev',
29
35
  staging: 'staging',
30
- production: 'prod',
36
+ production: 'prod'
31
37
  }
32
38
 
33
39
  const VERCEL_PROJECT_LINK_PATH = '.vercel/project.json'
@@ -53,6 +59,14 @@ function isMissingFilesError(err) {
53
59
  )
54
60
  }
55
61
 
62
+ function isNextPrebuiltMissingPathError(err) {
63
+ const text = collectErrorText(err)
64
+ return (
65
+ text.includes('ENOENT') &&
66
+ (text.includes('next-server.js') || text.includes('/node_modules/.pnpm/'))
67
+ )
68
+ }
69
+
56
70
  function listMissingVarKeys(targetConfigs, token, orgId) {
57
71
  const missing = []
58
72
 
@@ -102,6 +116,20 @@ export function resolveTargetRunCwd(projectRoot, targetConfig) {
102
116
  return join(projectRoot, targetConfig.deployCwd)
103
117
  }
104
118
 
119
+ export function resolveTargetDeployMode(targetConfig) {
120
+ if (!targetConfig?.deployMode) return 'prebuilt'
121
+ return targetConfig.deployMode
122
+ }
123
+
124
+ export function isSupportedDeployMode(mode) {
125
+ return ALLOWED_DEPLOY_MODES.includes(mode)
126
+ }
127
+
128
+ export function resolveTargetPrebuiltCwd(projectRoot, targetConfig, runCwd) {
129
+ if (!targetConfig?.prebuiltCwd) return runCwd
130
+ return join(projectRoot, targetConfig.prebuiltCwd)
131
+ }
132
+
105
133
  function maskIdentifier(value) {
106
134
  const raw = String(value || '').trim()
107
135
  if (raw.length <= 10) return raw || '-'
@@ -122,7 +150,7 @@ function readLinkedProjectContext(contextRoot) {
122
150
  path,
123
151
  orgId: parsed?.orgId || null,
124
152
  projectId: parsed?.projectId || null,
125
- parseError: null,
153
+ parseError: null
126
154
  }
127
155
  } catch (error) {
128
156
  return {
@@ -130,7 +158,7 @@ function readLinkedProjectContext(contextRoot) {
130
158
  path,
131
159
  orgId: null,
132
160
  projectId: null,
133
- parseError: error,
161
+ parseError: error
134
162
  }
135
163
  }
136
164
  }
@@ -148,7 +176,7 @@ async function runVercel(args, options = {}) {
148
176
  const child = spawn('vercel', args, {
149
177
  cwd: cwd || process.cwd(),
150
178
  env: env || process.env,
151
- stdio: ['inherit', 'pipe', 'pipe'],
179
+ stdio: ['inherit', 'pipe', 'pipe']
152
180
  })
153
181
 
154
182
  let stdout = ''
@@ -201,7 +229,7 @@ export async function deployPrebuiltWithFallback(options) {
201
229
  },
202
230
  onMissingFiles = () => {
203
231
  logger.warn('检测到 missing_files,自动使用 --archive=tgz 重试一次')
204
- },
232
+ }
205
233
  } = options || {}
206
234
 
207
235
  try {
@@ -223,7 +251,7 @@ export async function deployToVercel(target, options = {}) {
223
251
  environment = 'staging',
224
252
  telegramWebhook = null,
225
253
  strictContext = true,
226
- run = runVercel,
254
+ run = runVercel
227
255
  } = options
228
256
 
229
257
  // 校验环境参数
@@ -270,7 +298,9 @@ export async function deployToVercel(target, options = {}) {
270
298
  logger.info(' VERCEL_PROJECT_ID_TELEGRAM_BOT=prj_xxx')
271
299
  logger.info('')
272
300
  logger.info('获取方式:')
273
- logger.info(' 1. VERCEL_TOKEN: vercel login 后查看 ~/Library/Application Support/com.vercel.cli/auth.json')
301
+ logger.info(
302
+ ' 1. VERCEL_TOKEN: vercel login 后查看 ~/Library/Application Support/com.vercel.cli/auth.json'
303
+ )
274
304
  logger.info(' 2. PROJECT_ID: vercel project ls --scope <org> 或通过 Vercel Dashboard 获取')
275
305
  logger.info('')
276
306
  logger.info('提示:部署命令会显式校验 --scope 与环境变量上下文,避免环境漂移。')
@@ -302,14 +332,33 @@ export async function deployToVercel(target, options = {}) {
302
332
  const configFile = targetConfig.configFile
303
333
  const configPath = join(projectRoot, configFile)
304
334
  const runCwd = resolveTargetRunCwd(projectRoot, targetConfig)
335
+ const deployMode = resolveTargetDeployMode(targetConfig)
336
+ const prebuiltCwd = resolveTargetPrebuiltCwd(projectRoot, targetConfig, runCwd)
305
337
 
306
338
  if (!existsSync(runCwd)) {
307
- logger.error(`部署目录不存在: target=${t} deployCwd=${targetConfig.deployCwd || '.'} resolved=${runCwd}`)
339
+ logger.error(
340
+ `部署目录不存在: target=${t} deployCwd=${targetConfig.deployCwd || '.'} resolved=${runCwd}`
341
+ )
342
+ process.exitCode = 1
343
+ return
344
+ }
345
+
346
+ if (!isSupportedDeployMode(deployMode)) {
347
+ logger.error(`不支持的部署模式: target=${t} mode=${deployMode}`)
348
+ logger.info(`可用部署模式: ${ALLOWED_DEPLOY_MODES.join(', ')}`)
349
+ process.exitCode = 1
350
+ return
351
+ }
352
+
353
+ if (!existsSync(prebuiltCwd)) {
354
+ logger.error(
355
+ `预构建部署目录不存在: target=${t} prebuiltCwd=${targetConfig.prebuiltCwd || targetConfig.deployCwd || '.'} resolved=${prebuiltCwd}`
356
+ )
308
357
  process.exitCode = 1
309
358
  return
310
359
  }
311
360
 
312
- const linkedContext = readLinkedProjectContext(runCwd)
361
+ const linkedContext = readLinkedProjectContext(prebuiltCwd)
313
362
 
314
363
  const linkedMismatch =
315
364
  linkedContext.exists &&
@@ -319,7 +368,7 @@ export async function deployToVercel(target, options = {}) {
319
368
 
320
369
  if (linkedContext.exists && linkedContext.parseError) {
321
370
  logger.warn(
322
- `检测到 ${VERCEL_PROJECT_LINK_PATH} 但解析失败: ${linkedContext.parseError.message}`,
371
+ `检测到 ${VERCEL_PROJECT_LINK_PATH} 但解析失败: ${linkedContext.parseError.message}`
323
372
  )
324
373
  if (strictContext) {
325
374
  logger.error('strictContext 已开启,已阻止继续部署以避免回退污染')
@@ -332,7 +381,7 @@ export async function deployToVercel(target, options = {}) {
332
381
  logger.error('检测到 .vercel 链接冲突')
333
382
  logger.error(` 当前目标: org=${maskIdentifier(orgId)} project=${maskIdentifier(projectId)}`)
334
383
  logger.error(
335
- ` 本地链接: org=${maskIdentifier(linkedContext.orgId)} project=${maskIdentifier(linkedContext.projectId)}`,
384
+ ` 本地链接: org=${maskIdentifier(linkedContext.orgId)} project=${maskIdentifier(linkedContext.projectId)}`
336
385
  )
337
386
  if (strictContext) {
338
387
  logger.error('strictContext 已开启,已阻止部署(请清理 .vercel 或修正环境变量)')
@@ -343,7 +392,7 @@ export async function deployToVercel(target, options = {}) {
343
392
  }
344
393
 
345
394
  logger.info(
346
- `[deploy-context] env=${environment} target=${t} strict=${strictContext ? 1 : 0} org=${maskIdentifier(orgId)} project=${maskIdentifier(projectId)} linked=${linkedContext.exists ? 'yes' : 'no'} token=env`,
395
+ `[deploy-context] env=${environment} target=${t} mode=${deployMode} runCwd=${runCwd} prebuiltCwd=${prebuiltCwd} strict=${strictContext ? 1 : 0} org=${maskIdentifier(orgId)} project=${maskIdentifier(projectId)} linked=${linkedContext.exists ? 'yes' : 'no'} token=env`
347
396
  )
348
397
 
349
398
  const envVars = {
@@ -351,7 +400,7 @@ export async function deployToVercel(target, options = {}) {
351
400
  VERCEL_PROJECT_ID: projectId,
352
401
  APP_ENV: buildEnv,
353
402
  NODE_ENV: envManager.mapAppEnvToNodeEnv(environment),
354
- VERCEL_ORG_ID: orgId,
403
+ VERCEL_ORG_ID: orgId
355
404
  }
356
405
 
357
406
  // 不通过 CLI args 传递 token,避免出现在错误信息/日志中
@@ -364,7 +413,9 @@ export async function deployToVercel(target, options = {}) {
364
413
  try {
365
414
  originalAuthor = execSync('git log -1 --format="%an <%ae>"', { encoding: 'utf8' }).trim()
366
415
  const authorName = authorEmail.split('@')[0]
367
- execSync(`git commit --amend --author="${authorName} <${authorEmail}>" --no-edit`, { stdio: 'ignore' })
416
+ execSync(`git commit --amend --author="${authorName} <${authorEmail}>" --no-edit`, {
417
+ stdio: 'ignore'
418
+ })
368
419
  logger.info(`临时修改 commit author: ${originalAuthor} -> ${authorName} <${authorEmail}>`)
369
420
  } catch (e) {
370
421
  logger.warn(`修改 commit author 失败: ${e.message}`)
@@ -374,17 +425,14 @@ export async function deployToVercel(target, options = {}) {
374
425
 
375
426
  try {
376
427
  if (strictContext && process.env.DX_VERCEL_KEEP_LINK !== '1') {
377
- clearLinkedProjectContext(runCwd)
428
+ clearLinkedProjectContext(prebuiltCwd)
378
429
  }
379
430
 
380
431
  // 第一步:本地构建
381
432
  logger.step(`本地构建 ${t} (${environment})`)
382
- const buildArgs = appendTargetArgs(
383
- ['build', '--local-config', configPath, '--yes'],
384
- {
385
- orgId,
386
- },
387
- )
433
+ const buildArgs = appendTargetArgs(['build', '--local-config', configPath, '--yes'], {
434
+ orgId
435
+ })
388
436
 
389
437
  // staging 和 production 环境需要 --prod 标志,确保构建产物与部署环境匹配
390
438
  if (environment === 'staging' || environment === 'production') {
@@ -394,13 +442,13 @@ export async function deployToVercel(target, options = {}) {
394
442
  await run(buildArgs, { env: envVars, cwd: runCwd })
395
443
  logger.success(`${t} 本地构建成功`)
396
444
 
397
- // 第二步:上传预构建产物
398
445
  logger.step(`部署 ${t} 到 Vercel (${environment})`)
446
+
399
447
  const baseDeployArgs = appendTargetArgs(
400
448
  ['deploy', '--prebuilt', '--local-config', configPath, '--yes'],
401
449
  {
402
- orgId,
403
- },
450
+ orgId
451
+ }
404
452
  )
405
453
 
406
454
  // staging 和 production 环境都添加 --prod 标志以绑定固定域名
@@ -411,8 +459,8 @@ export async function deployToVercel(target, options = {}) {
411
459
  const deployResult = await deployPrebuiltWithFallback({
412
460
  baseArgs: baseDeployArgs,
413
461
  env: envVars,
414
- cwd: runCwd,
415
- run,
462
+ cwd: prebuiltCwd,
463
+ run
416
464
  })
417
465
 
418
466
  const deployOutput = [deployResult?.result?.stdout, deployResult?.result?.stderr]
@@ -425,13 +473,18 @@ export async function deployToVercel(target, options = {}) {
425
473
  await handleTelegramBotDeploy(environment, projectId, orgId, token, {
426
474
  deployOutput,
427
475
  projectNameHint: 'telegram-bot',
428
- ...(telegramWebhook || {}),
476
+ ...(telegramWebhook || {})
429
477
  })
430
478
  logger.success(`${t} 部署成功(Webhook 已校验)`)
431
479
  } else {
432
480
  logger.success(`${t} 部署成功`)
433
481
  }
434
482
  } catch (error) {
483
+ if (deployMode === 'prebuilt' && isNextPrebuiltMissingPathError(error)) {
484
+ logger.error(
485
+ '高优先级提示:检测到 Next.js 预构建产物缺失(next-server.js/node_modules/.pnpm)。请检查 front prebuiltCwd 与构建产物路径是否一致。'
486
+ )
487
+ }
435
488
  const message = error?.message || String(error)
436
489
  logger.error(`${t} 构建或部署失败: ${message}`)
437
490
  process.exitCode = 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {