@ranger1/dx 0.1.107 → 0.1.109
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/README.md
CHANGED
|
@@ -463,6 +463,7 @@ SSH 认证说明:
|
|
|
463
463
|
- `dx deploy backend` 当前直接调用系统 `ssh` / `scp`,不会单独解析 `sshKey`、`identityFile` 之类的 dx 配置项。
|
|
464
464
|
- 因此,发布使用哪把私钥,取决于本机 OpenSSH 的默认认证行为,例如 `ssh-agent`、`~/.ssh/config`、默认私钥文件等。
|
|
465
465
|
- 如果你已经在 `~/.ssh/config` 中配置了主机别名(例如 `Host ai-staging`),推荐直接把 `backendDeploy.remote.host` 写成这个别名,让 OpenSSH 自动匹配对应的 `HostName`、`User`、`Port`、`IdentityFile`。
|
|
466
|
+
- `backendDeploy.remote` 可以保持旧的单远端对象;也可以按环境拆成 `remote.staging` / `remote.production`,此时 `dx deploy backend --staging` 与 `dx deploy backend --prod` 会选择对应环境的远端。
|
|
466
467
|
|
|
467
468
|
例如本机 `~/.ssh/config`:
|
|
468
469
|
|
|
@@ -494,6 +495,34 @@ Host ai-staging
|
|
|
494
495
|
}
|
|
495
496
|
```
|
|
496
497
|
|
|
498
|
+
按环境区分远端时:
|
|
499
|
+
|
|
500
|
+
```json
|
|
501
|
+
{
|
|
502
|
+
"deploy": {
|
|
503
|
+
"backend": {
|
|
504
|
+
"internal": "backend-artifact-deploy",
|
|
505
|
+
"backendDeploy": {
|
|
506
|
+
"remote": {
|
|
507
|
+
"staging": {
|
|
508
|
+
"host": "ai-staging",
|
|
509
|
+
"port": 22,
|
|
510
|
+
"user": "deploy",
|
|
511
|
+
"baseDir": "/srv/example-app"
|
|
512
|
+
},
|
|
513
|
+
"production": {
|
|
514
|
+
"host": "ai-ubuntu-prod",
|
|
515
|
+
"port": 22,
|
|
516
|
+
"user": "deploy",
|
|
517
|
+
"baseDir": "/srv/example-app"
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
497
526
|
注意:
|
|
498
527
|
|
|
499
528
|
- `remote.host` 写成别名后,dx 仍会显式传入 `remote.user` 和 `remote.port`;如果这两个值与 `~/.ssh/config` 中的 `User` / `Port` 不一致,命令行参数会覆盖 SSH config。
|
|
@@ -16,6 +16,36 @@ function requirePositiveInteger(value, fieldPath) {
|
|
|
16
16
|
return parsed
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function requireEnvName(value, fieldPath) {
|
|
20
|
+
const name = requireString(value, fieldPath)
|
|
21
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
22
|
+
throw new Error(`缺少必填配置: ${fieldPath}`)
|
|
23
|
+
}
|
|
24
|
+
return name
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveHealthCheckEndpoint(healthCheckConfig) {
|
|
28
|
+
if (healthCheckConfig.url != null) {
|
|
29
|
+
const url = requireString(healthCheckConfig.url, 'verify.healthCheck.url')
|
|
30
|
+
try {
|
|
31
|
+
new URL(url)
|
|
32
|
+
} catch {
|
|
33
|
+
throw new Error(`缺少必填配置: verify.healthCheck.url`)
|
|
34
|
+
}
|
|
35
|
+
return { url }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (healthCheckConfig.envPort != null) {
|
|
39
|
+
const rawPath = healthCheckConfig.path == null ? '/health' : requireString(healthCheckConfig.path, 'verify.healthCheck.path')
|
|
40
|
+
return {
|
|
41
|
+
envPort: requireEnvName(healthCheckConfig.envPort, 'verify.healthCheck.envPort'),
|
|
42
|
+
path: rawPath.startsWith('/') ? rawPath : `/${rawPath}`,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
throw new Error(`缺少必填配置: verify.healthCheck.url`)
|
|
47
|
+
}
|
|
48
|
+
|
|
19
49
|
function resolveVerifyConfig(verifyConfig = {}) {
|
|
20
50
|
const healthCheckConfig = verifyConfig?.healthCheck
|
|
21
51
|
if (healthCheckConfig == null) {
|
|
@@ -24,16 +54,9 @@ function resolveVerifyConfig(verifyConfig = {}) {
|
|
|
24
54
|
}
|
|
25
55
|
}
|
|
26
56
|
|
|
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
57
|
return {
|
|
35
58
|
healthCheck: {
|
|
36
|
-
|
|
59
|
+
...resolveHealthCheckEndpoint(healthCheckConfig),
|
|
37
60
|
timeoutSeconds:
|
|
38
61
|
healthCheckConfig.timeoutSeconds == null
|
|
39
62
|
? 10
|
|
@@ -80,6 +103,22 @@ function requireRemoteBaseDir(value, fieldPath) {
|
|
|
80
103
|
return baseDir.replace(/\/+$/, '') || '/'
|
|
81
104
|
}
|
|
82
105
|
|
|
106
|
+
function resolveRemoteConfig(remoteConfig, environment) {
|
|
107
|
+
if (!remoteConfig || typeof remoteConfig !== 'object') {
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (typeof remoteConfig.host === 'string') {
|
|
112
|
+
return remoteConfig
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const selected = remoteConfig[environment]
|
|
116
|
+
if (!selected || typeof selected !== 'object') {
|
|
117
|
+
throw new Error(`缺少必填配置: remote.${environment}`)
|
|
118
|
+
}
|
|
119
|
+
return selected
|
|
120
|
+
}
|
|
121
|
+
|
|
83
122
|
export function resolveBackendDeployConfig({ cli, targetConfig, environment, flags = {} }) {
|
|
84
123
|
const deployConfig = targetConfig?.backendDeploy
|
|
85
124
|
if (!deployConfig || typeof deployConfig !== 'object') {
|
|
@@ -89,7 +128,7 @@ export function resolveBackendDeployConfig({ cli, targetConfig, environment, fla
|
|
|
89
128
|
const buildConfig = deployConfig.build || {}
|
|
90
129
|
const runtimeConfig = deployConfig.runtime || {}
|
|
91
130
|
const artifactConfig = deployConfig.artifact || {}
|
|
92
|
-
const remoteConfig = deployConfig.remote
|
|
131
|
+
const remoteConfig = resolveRemoteConfig(deployConfig.remote, environment)
|
|
93
132
|
const startupConfig = deployConfig.startup || {}
|
|
94
133
|
const runConfig = deployConfig.deploy || {}
|
|
95
134
|
const verifyConfig = deployConfig.verify || {}
|
|
@@ -32,6 +32,8 @@ export function buildRemoteDeployScript(phaseModel = []) {
|
|
|
32
32
|
const shouldMigrate = deploy.prismaMigrateDeploy !== false && deploy.skipMigration !== true
|
|
33
33
|
const shouldSeed = deploy.prismaSeed === true
|
|
34
34
|
const healthCheckUrl = healthCheck?.url ? String(healthCheck.url) : ''
|
|
35
|
+
const healthCheckEnvPort = healthCheck?.envPort ? String(healthCheck.envPort) : ''
|
|
36
|
+
const healthCheckPath = healthCheck?.path ? String(healthCheck.path) : ''
|
|
35
37
|
const healthCheckTimeoutSeconds = Number(healthCheck?.timeoutSeconds || 10)
|
|
36
38
|
const healthCheckMaxWaitSeconds = Number(healthCheck?.maxWaitSeconds || 24)
|
|
37
39
|
const healthCheckRetryIntervalSeconds = Number(healthCheck?.retryIntervalSeconds || 2)
|
|
@@ -57,6 +59,8 @@ START_MODE=${escapeShell(startupMode)}
|
|
|
57
59
|
SERVICE_NAME=${escapeShell(serviceName)}
|
|
58
60
|
START_ENTRY=${escapeShell(startupEntry)}
|
|
59
61
|
HEALTHCHECK_URL=${escapeShell(healthCheckUrl)}
|
|
62
|
+
HEALTHCHECK_ENV_PORT=${escapeShell(healthCheckEnvPort)}
|
|
63
|
+
HEALTHCHECK_PATH=${escapeShell(healthCheckPath)}
|
|
60
64
|
HEALTHCHECK_TIMEOUT_SECONDS=${healthCheckTimeoutSeconds}
|
|
61
65
|
HEALTHCHECK_MAX_WAIT_SECONDS=${healthCheckMaxWaitSeconds}
|
|
62
66
|
HEALTHCHECK_RETRY_DELAY_SECONDS=${healthCheckRetryIntervalSeconds}
|
|
@@ -226,6 +230,34 @@ read_pm2_status() {
|
|
|
226
230
|
' "$SERVICE_NAME"
|
|
227
231
|
}
|
|
228
232
|
|
|
233
|
+
resolve_healthcheck_url() {
|
|
234
|
+
if [[ -n "$HEALTHCHECK_URL" ]]; then
|
|
235
|
+
printf '%s\n' "$HEALTHCHECK_URL"
|
|
236
|
+
return
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
if [[ -z "$HEALTHCHECK_ENV_PORT" ]]; then
|
|
240
|
+
return
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
local healthcheck_port
|
|
244
|
+
healthcheck_port="$(run_with_env "$CURRENT_LINK" env | awk -F= -v key="$HEALTHCHECK_ENV_PORT" '$1 == key { print substr($0, index($0, "=") + 1); exit }')"
|
|
245
|
+
if ! [[ "$healthcheck_port" =~ ^[0-9]+$ ]]; then
|
|
246
|
+
echo "health check port env $HEALTHCHECK_ENV_PORT is missing or invalid" >&2
|
|
247
|
+
exit 1
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
if [[ -z "$HEALTHCHECK_PATH" ]]; then
|
|
251
|
+
HEALTHCHECK_PATH="/health"
|
|
252
|
+
fi
|
|
253
|
+
if [[ "$HEALTHCHECK_PATH" != /* ]]; then
|
|
254
|
+
HEALTHCHECK_PATH="/$HEALTHCHECK_PATH"
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
HEALTHCHECK_URL="http://127.0.0.1:\${healthcheck_port}\${HEALTHCHECK_PATH}"
|
|
258
|
+
printf '%s\n' "$HEALTHCHECK_URL"
|
|
259
|
+
}
|
|
260
|
+
|
|
229
261
|
build_summary_json() {
|
|
230
262
|
local release_name="$1"
|
|
231
263
|
local current_release_path="$2"
|
|
@@ -436,6 +468,8 @@ if [[ "$START_MODE" == "pm2" ]]; then
|
|
|
436
468
|
fi
|
|
437
469
|
fi
|
|
438
470
|
|
|
471
|
+
HEALTHCHECK_URL="$(resolve_healthcheck_url)"
|
|
472
|
+
|
|
439
473
|
if [[ -n "$HEALTHCHECK_URL" ]]; then
|
|
440
474
|
healthcheck_started_at="$(date +%s)"
|
|
441
475
|
until curl -fsS --max-time "$HEALTHCHECK_TIMEOUT_SECONDS" "$HEALTHCHECK_URL" >/dev/null; do
|