@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.
- package/lib/vercel-deploy.js +82 -29
- package/package.json +1 -1
package/lib/vercel-deploy.js
CHANGED
|
@@ -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: '
|
|
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(
|
|
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(
|
|
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(
|
|
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`, {
|
|
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(
|
|
428
|
+
clearLinkedProjectContext(prebuiltCwd)
|
|
378
429
|
}
|
|
379
430
|
|
|
380
431
|
// 第一步:本地构建
|
|
381
432
|
logger.step(`本地构建 ${t} (${environment})`)
|
|
382
|
-
const buildArgs = appendTargetArgs(
|
|
383
|
-
|
|
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:
|
|
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
|