@ranger1/dx 0.1.83 → 0.1.84

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.
@@ -16,6 +16,32 @@ function requirePositiveInteger(value, fieldPath) {
16
16
  return parsed
17
17
  }
18
18
 
19
+ function resolveVerifyConfig(verifyConfig = {}) {
20
+ const healthCheckConfig = verifyConfig?.healthCheck
21
+ if (healthCheckConfig == null) {
22
+ return {
23
+ healthCheck: null,
24
+ }
25
+ }
26
+
27
+ const url = requireString(healthCheckConfig.url, 'verify.healthCheck.url')
28
+ try {
29
+ new URL(url)
30
+ } catch {
31
+ throw new Error(`缺少必填配置: verify.healthCheck.url`)
32
+ }
33
+
34
+ return {
35
+ healthCheck: {
36
+ url,
37
+ timeoutSeconds:
38
+ healthCheckConfig.timeoutSeconds == null
39
+ ? 10
40
+ : requirePositiveInteger(healthCheckConfig.timeoutSeconds, 'verify.healthCheck.timeoutSeconds'),
41
+ },
42
+ }
43
+ }
44
+
19
45
  function resolveBuildCommand(buildConfig, environment) {
20
46
  if (buildConfig?.commands && typeof buildConfig.commands === 'object') {
21
47
  const selected = buildConfig.commands[environment]
@@ -55,6 +81,7 @@ export function resolveBackendDeployConfig({ cli, targetConfig, environment, fla
55
81
  const remoteConfig = deployConfig.remote || null
56
82
  const startupConfig = deployConfig.startup || {}
57
83
  const runConfig = deployConfig.deploy || {}
84
+ const verifyConfig = deployConfig.verify || {}
58
85
  const buildOnly = Boolean(flags.buildOnly)
59
86
  const startupMode = String(startupConfig.mode || 'pm2').trim()
60
87
  const prismaGenerate = runConfig.prismaGenerate !== false
@@ -117,6 +144,7 @@ export function resolveBackendDeployConfig({ cli, targetConfig, environment, fla
117
144
  prismaMigrateDeploy,
118
145
  skipMigration: Boolean(flags.skipMigration),
119
146
  },
147
+ verify: resolveVerifyConfig(verifyConfig),
120
148
  }
121
149
 
122
150
  if (!['pm2', 'direct'].includes(normalized.startup.mode)) {
@@ -8,6 +8,7 @@ export function createRemotePhaseModel(payload) {
8
8
  { phase: 'prisma-migrate', payload },
9
9
  { phase: 'switch-current', payload },
10
10
  { phase: 'startup', payload },
11
+ { phase: 'verify', payload },
11
12
  { phase: 'cleanup', payload },
12
13
  ]
13
14
  }
@@ -8,7 +8,11 @@ export function buildRemoteDeployScript(phaseModel = []) {
8
8
  const runtime = payload.runtime || {}
9
9
  const startup = payload.startup || {}
10
10
  const deploy = payload.deploy || {}
11
+ const verify = payload.verify || {}
12
+ const healthCheck = verify.healthCheck || null
11
13
  const environment = String(payload.environment || 'production')
14
+ const expectedAppEnv = environment
15
+ const expectedNodeEnv = environment === 'development' ? 'development' : 'production'
12
16
  const baseDir = String(remote.baseDir || '.')
13
17
  const releaseDir = `${baseDir}/releases/${payload.versionName || 'unknown'}`
14
18
  const currentLink = `${baseDir}/current`
@@ -26,6 +30,8 @@ export function buildRemoteDeployScript(phaseModel = []) {
26
30
  const keepReleases = Number(deploy.keepReleases || 5)
27
31
  const shouldGenerate = deploy.prismaGenerate !== false
28
32
  const shouldMigrate = deploy.prismaMigrateDeploy !== false && deploy.skipMigration !== true
33
+ const healthCheckUrl = healthCheck?.url ? String(healthCheck.url) : ''
34
+ const healthCheckTimeoutSeconds = Number(healthCheck?.timeoutSeconds || 10)
29
35
 
30
36
  return `#!/usr/bin/env bash
31
37
  set -euo pipefail
@@ -36,6 +42,8 @@ ARCHIVE=${escapeShell(uploadedBundlePath)}
36
42
  RELEASE_DIR=${escapeShell(releaseDir)}
37
43
  CURRENT_LINK=${escapeShell(currentLink)}
38
44
  ENV_NAME=${escapeShell(environment)}
45
+ EXPECTED_APP_ENV=${escapeShell(expectedAppEnv)}
46
+ EXPECTED_NODE_ENV=${escapeShell(expectedNodeEnv)}
39
47
  ENV_FILE_NAME=${escapeShell(envFileName)}
40
48
  ENV_LOCAL_FILE_NAME=${escapeShell(envLocalFileName)}
41
49
  PRISMA_SCHEMA=${escapeShell(prismaSchema)}
@@ -45,6 +53,8 @@ INSTALL_COMMAND=${escapeShell(installCommand)}
45
53
  START_MODE=${escapeShell(startupMode)}
46
54
  SERVICE_NAME=${escapeShell(serviceName)}
47
55
  START_ENTRY=${escapeShell(startupEntry)}
56
+ HEALTHCHECK_URL=${escapeShell(healthCheckUrl)}
57
+ HEALTHCHECK_TIMEOUT_SECONDS=${healthCheckTimeoutSeconds}
48
58
  KEEP_RELEASES=${keepReleases}
49
59
  SHOULD_GENERATE=${shouldGenerate ? '1' : '0'}
50
60
  SHOULD_MIGRATE=${shouldMigrate ? '1' : '0'}
@@ -174,10 +184,22 @@ run_with_env() {
174
184
  shift
175
185
  (
176
186
  cd "$cwd"
177
- APP_ENV="$ENV_NAME" "$DOTENV_BIN" -o -e "$ENV_FILE_NAME" -e "$ENV_LOCAL_FILE_NAME" -- "$@"
187
+ APP_ENV="$EXPECTED_APP_ENV" NODE_ENV="$EXPECTED_NODE_ENV" \\
188
+ "$DOTENV_BIN" -o -e "$ENV_FILE_NAME" -e "$ENV_LOCAL_FILE_NAME" -- "$@"
178
189
  )
179
190
  }
180
191
 
192
+ read_pm2_env_var() {
193
+ local key="$1"
194
+ pm2 jlist | node -e '
195
+ const fs = require("node:fs")
196
+ const key = process.argv[1]
197
+ const list = JSON.parse(fs.readFileSync(0, "utf8"))
198
+ const app = list.find(item => item?.name === process.argv[2])
199
+ process.stdout.write(String(app?.pm2_env?.[key] || ""))
200
+ ' "$key" "$SERVICE_NAME"
201
+ }
202
+
181
203
  attempt_pm2_restore() {
182
204
  if [[ -z "$PREVIOUS_CURRENT_TARGET" || ! -e "$PREVIOUS_CURRENT_TARGET/$ECOSYSTEM_CONFIG" ]]; then
183
205
  ROLLBACK_SUCCEEDED=false
@@ -185,7 +207,8 @@ attempt_pm2_restore() {
185
207
  fi
186
208
  if (
187
209
  cd "$PREVIOUS_CURRENT_TARGET"
188
- APP_ENV="$ENV_NAME" "$DOTENV_BIN" -o -e "$ENV_FILE_NAME" -e "$ENV_LOCAL_FILE_NAME" -- \\
210
+ APP_ENV="$EXPECTED_APP_ENV" NODE_ENV="$EXPECTED_NODE_ENV" \\
211
+ "$DOTENV_BIN" -o -e "$ENV_FILE_NAME" -e "$ENV_LOCAL_FILE_NAME" -- \\
189
212
  pm2 start "$ECOSYSTEM_CONFIG" --only "$SERVICE_NAME" --update-env
190
213
  pm2 save
191
214
  ); then
@@ -291,7 +314,8 @@ if [[ "$START_MODE" == "pm2" ]]; then
291
314
  if ! (
292
315
  cd "$CURRENT_LINK"
293
316
  pm2 delete "$SERVICE_NAME" || true
294
- APP_ENV="$ENV_NAME" "$DOTENV_BIN" -o -e "$ENV_FILE_NAME" -e "$ENV_LOCAL_FILE_NAME" -- \\
317
+ APP_ENV="$EXPECTED_APP_ENV" NODE_ENV="$EXPECTED_NODE_ENV" \\
318
+ "$DOTENV_BIN" -o -e "$ENV_FILE_NAME" -e "$ENV_LOCAL_FILE_NAME" -- \\
295
319
  pm2 start "$ECOSYSTEM_CONFIG" --only "$SERVICE_NAME" --update-env
296
320
  pm2 save
297
321
  ); then
@@ -306,7 +330,8 @@ if [[ "$START_MODE" == "pm2" ]]; then
306
330
  else
307
331
  if ! (
308
332
  cd "$CURRENT_LINK"
309
- APP_ENV="$ENV_NAME" "$DOTENV_BIN" -o -e "$ENV_FILE_NAME" -e "$ENV_LOCAL_FILE_NAME" -- \\
333
+ APP_ENV="$EXPECTED_APP_ENV" NODE_ENV="$EXPECTED_NODE_ENV" \\
334
+ "$DOTENV_BIN" -o -e "$ENV_FILE_NAME" -e "$ENV_LOCAL_FILE_NAME" -- \\
310
335
  node "$START_ENTRY"
311
336
  ); then
312
337
  emit_result false "startup" "direct startup failed" false null
@@ -316,6 +341,50 @@ else
316
341
  exit 0
317
342
  fi
318
343
 
344
+ CURRENT_PHASE="verify"
345
+ echo "DX_REMOTE_PHASE=verify"
346
+ if [[ ! -L "$CURRENT_LINK" ]]; then
347
+ echo "current 软链接不存在: $CURRENT_LINK" >&2
348
+ exit 1
349
+ fi
350
+
351
+ current_release="$(readlink -f "$CURRENT_LINK")"
352
+ expected_release="$(readlink -f "$RELEASE_DIR")"
353
+ if [[ -z "$current_release" || ! -d "$current_release" ]]; then
354
+ echo "current 软链接未指向有效目录: \${current_release:-<empty>}" >&2
355
+ exit 1
356
+ fi
357
+ if [[ "$current_release" != "$expected_release" ]]; then
358
+ echo "current 软链接未指向本次 release: expected=$expected_release actual=$current_release" >&2
359
+ exit 1
360
+ fi
361
+
362
+ if [[ "$START_MODE" == "pm2" ]]; then
363
+ if ! pm2 describe "$SERVICE_NAME" >/dev/null 2>&1; then
364
+ echo "PM2 进程不存在: $SERVICE_NAME" >&2
365
+ pm2 list || true
366
+ exit 1
367
+ fi
368
+
369
+ pm2_app_env="$(read_pm2_env_var APP_ENV)"
370
+ if [[ "$pm2_app_env" != "$EXPECTED_APP_ENV" ]]; then
371
+ echo "APP_ENV 不匹配,期望=$EXPECTED_APP_ENV,实际=\${pm2_app_env:-<empty>}" >&2
372
+ pm2 describe "$SERVICE_NAME" || true
373
+ exit 1
374
+ fi
375
+
376
+ pm2_node_env="$(read_pm2_env_var NODE_ENV)"
377
+ if [[ "$pm2_node_env" != "$EXPECTED_NODE_ENV" ]]; then
378
+ echo "NODE_ENV 不匹配,期望=$EXPECTED_NODE_ENV,实际=\${pm2_node_env:-<empty>}" >&2
379
+ pm2 describe "$SERVICE_NAME" || true
380
+ exit 1
381
+ fi
382
+ fi
383
+
384
+ if [[ -n "$HEALTHCHECK_URL" ]]; then
385
+ curl -fsS --max-time "$HEALTHCHECK_TIMEOUT_SECONDS" "$HEALTHCHECK_URL" >/dev/null
386
+ fi
387
+
319
388
  CURRENT_PHASE="cleanup"
320
389
  echo "DX_REMOTE_PHASE=cleanup"
321
390
  release_count=0
@@ -100,6 +100,7 @@ function createRemotePayload(config, bundle) {
100
100
  },
101
101
  startup: config.startup,
102
102
  deploy: config.deploy,
103
+ verify: config.verify,
103
104
  }
104
105
  }
105
106
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.83",
3
+ "version": "0.1.84",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {