@ranger1/dx 0.1.106 → 0.1.108

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。
@@ -80,6 +80,22 @@ function requireRemoteBaseDir(value, fieldPath) {
80
80
  return baseDir.replace(/\/+$/, '') || '/'
81
81
  }
82
82
 
83
+ function resolveRemoteConfig(remoteConfig, environment) {
84
+ if (!remoteConfig || typeof remoteConfig !== 'object') {
85
+ return null
86
+ }
87
+
88
+ if (typeof remoteConfig.host === 'string') {
89
+ return remoteConfig
90
+ }
91
+
92
+ const selected = remoteConfig[environment]
93
+ if (!selected || typeof selected !== 'object') {
94
+ throw new Error(`缺少必填配置: remote.${environment}`)
95
+ }
96
+ return selected
97
+ }
98
+
83
99
  export function resolveBackendDeployConfig({ cli, targetConfig, environment, flags = {} }) {
84
100
  const deployConfig = targetConfig?.backendDeploy
85
101
  if (!deployConfig || typeof deployConfig !== 'object') {
@@ -89,7 +105,7 @@ export function resolveBackendDeployConfig({ cli, targetConfig, environment, fla
89
105
  const buildConfig = deployConfig.build || {}
90
106
  const runtimeConfig = deployConfig.runtime || {}
91
107
  const artifactConfig = deployConfig.artifact || {}
92
- const remoteConfig = deployConfig.remote || null
108
+ const remoteConfig = resolveRemoteConfig(deployConfig.remote, environment)
93
109
  const startupConfig = deployConfig.startup || {}
94
110
  const runConfig = deployConfig.deploy || {}
95
111
  const verifyConfig = deployConfig.verify || {}
@@ -76,6 +76,7 @@ export async function handleTest(cli, args) {
76
76
  // 解析 -t 参数用于指定特定测试用例(使用原始参数列表)
77
77
  const allArgs = cli.args // 使用原始参数列表包含所有标志
78
78
  const testNamePattern = resolveTestNamePattern(allArgs)
79
+ const passthroughArgs = resolvePassthroughArgs(allArgs)
79
80
 
80
81
  // 根据测试类型自动设置环境标志
81
82
  if (type === 'e2e' && !cli.flags.e2e) {
@@ -108,16 +109,24 @@ export async function handleTest(cli, args) {
108
109
  process.exit(1)
109
110
  }
110
111
 
112
+ const directTarget = resolveNxTargetDirectCommand(cli, fileCommand, ['test:e2e'])
111
113
  const normalizedTestPath = normalizeE2eTestPathForCommand(cli, fileCommand, testPath)
112
- let command = fileCommand.replace('{TEST_PATH}', shellEscape(normalizedTestPath))
114
+ let command = directTarget
115
+ ? `${directTarget.command} ${shellEscape(normalizedTestPath)}`
116
+ : fileCommand.replace('{TEST_PATH}', shellEscape(normalizedTestPath))
113
117
 
114
118
  if (testNamePattern) {
115
119
  command += ` -t ${shellEscape(testNamePattern)}`
116
120
  }
117
121
 
122
+ if (passthroughArgs.length > 0) {
123
+ command += ` ${passthroughArgs.map(shellEscape).join(' ')}`
124
+ }
125
+
118
126
  testConfig = {
119
127
  ...testConfig,
120
128
  command: command,
129
+ ...(directTarget?.cwd ? { cwd: directTarget.cwd } : {}),
121
130
  description: testNamePattern
122
131
  ? `运行单个E2E测试文件的特定用例: ${testPath} -> ${testNamePattern}`
123
132
  : `运行单个E2E测试文件: ${testPath}`
@@ -142,6 +151,10 @@ export async function handleTest(cli, args) {
142
151
  forwardedArgs.push(`-t ${shellEscape(testNamePattern)}`)
143
152
  }
144
153
 
154
+ if (passthroughArgs.length > 0) {
155
+ forwardedArgs.push(...passthroughArgs.map(shellEscape))
156
+ }
157
+
145
158
  command += ` ${forwardedArgs.join(' ')}`
146
159
 
147
160
  testConfig = {
@@ -177,6 +190,12 @@ function resolveTestNamePattern(args = []) {
177
190
  return null
178
191
  }
179
192
 
193
+ function resolvePassthroughArgs(args = []) {
194
+ const index = args.indexOf('--')
195
+ if (index === -1) return []
196
+ return args.slice(index + 1)
197
+ }
198
+
180
199
  function shouldUseDirectPathArg(command) {
181
200
  const text = String(command || '')
182
201
  return (
@@ -228,6 +247,32 @@ function normalizeE2eTestPathForCommand(cli, command, testPath) {
228
247
  return relativePath
229
248
  }
230
249
 
250
+ function resolveNxTargetDirectCommand(cli, command, targetNames = []) {
251
+ const projectRoot = cli?.projectRoot || process.cwd()
252
+ const nxResolution = extractNxTarget(command, targetNames)
253
+ if (!nxResolution) return null
254
+
255
+ const projectDir = join(projectRoot, 'apps', nxResolution.project)
256
+ const projectConfigPath = join(projectDir, 'project.json')
257
+ if (!existsSync(projectConfigPath)) return null
258
+
259
+ try {
260
+ const projectConfig = JSON.parse(readFileSync(projectConfigPath, 'utf8'))
261
+ const resolvedTarget = projectConfig?.targets?.[nxResolution.target]
262
+ const directCommand = resolvedTarget?.options?.command
263
+ if (typeof directCommand !== 'string' || directCommand.trim().length === 0) {
264
+ return null
265
+ }
266
+ const cwd = resolvedTarget?.options?.cwd
267
+ return {
268
+ command: directCommand.trim(),
269
+ cwd: typeof cwd === 'string' && cwd.trim().length > 0 ? cwd.trim() : null,
270
+ }
271
+ } catch {
272
+ return null
273
+ }
274
+ }
275
+
231
276
  function resolveNxTargetProjectCwd(cli, command, targetNames = []) {
232
277
  const projectRoot = cli?.projectRoot || process.cwd()
233
278
  const nxResolution = extractNxTarget(command, targetNames)
package/lib/cli/dx-cli.js CHANGED
@@ -910,6 +910,7 @@ class DxCli {
910
910
  const options = {
911
911
  app: config.app,
912
912
  flags: effectiveFlags,
913
+ cwd: config.cwd,
913
914
  ports: config.ports || [],
914
915
  // 允许上游在 config.env 中注入环境变量(例如 NX_CACHE=false)
915
916
  env: config.env || {},
package/lib/env.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readFileSync } from 'node:fs'
2
- import { join } from 'node:path'
2
+ import { isAbsolute, join } from 'node:path'
3
3
 
4
4
  function resolveProjectRoot() {
5
5
  return process.env.DX_PROJECT_ROOT || process.cwd()
@@ -101,9 +101,13 @@ export class EnvManager {
101
101
  }
102
102
 
103
103
  // 构建dotenv命令参数
104
- buildEnvFlags(app, environment) {
104
+ buildEnvFlags(app, environment, options = {}) {
105
+ const absolute = Boolean(options.absolute)
105
106
  return this.getResolvedEnvLayers(app, environment)
106
- .map(layer => `-e ${layer}`)
107
+ .map(layer => {
108
+ const envFile = absolute && !isAbsolute(layer) ? join(this.projectRoot, layer) : layer
109
+ return `-e ${envFile}`
110
+ })
107
111
  .join(' ')
108
112
  }
109
113
 
package/lib/exec.js CHANGED
@@ -163,7 +163,10 @@ export class ExecManager {
163
163
  logger.info(`dotenv层 ${envLabel}: ${layerSummary}`, '🌱')
164
164
  }
165
165
 
166
- const envFlags = envManager.buildEnvFlags(app, environment)
166
+ const commandCwd = cwd || process.cwd()
167
+ const envFlags = envManager.buildEnvFlags(app, environment, {
168
+ absolute: commandCwd !== process.cwd(),
169
+ })
167
170
  if (envFlags) {
168
171
  // 对 build 命令禁用 dotenv 的 --override,避免覆盖我们显式传入的 NODE_ENV
169
172
  const overrideFlag = isBuildCmdForWrapping ? '' : '--override'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.106",
3
+ "version": "0.1.108",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {