@ranger1/dx 0.1.76 → 0.1.78

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.
Files changed (36) hide show
  1. package/README.md +92 -31
  2. package/bin/dx.js +3 -3
  3. package/lib/cli/commands/deploy.js +2 -1
  4. package/lib/cli/commands/stack.js +198 -237
  5. package/lib/cli/commands/start.js +0 -6
  6. package/lib/cli/dx-cli.js +10 -1
  7. package/lib/cli/help.js +8 -7
  8. package/lib/{opencode-initial.js → codex-initial.js} +3 -82
  9. package/lib/vercel-deploy.js +14 -27
  10. package/package.json +1 -2
  11. package/@opencode/agents/__pycache__/gh_review_harvest.cpython-314.pyc +0 -0
  12. package/@opencode/agents/__pycache__/pr_context.cpython-314.pyc +0 -0
  13. package/@opencode/agents/__pycache__/pr_precheck.cpython-314.pyc +0 -0
  14. package/@opencode/agents/__pycache__/pr_review_aggregate.cpython-314.pyc +0 -0
  15. package/@opencode/agents/__pycache__/test_pr_review_aggregate.cpython-314-pytest-9.0.2.pyc +0 -0
  16. package/@opencode/agents/__pycache__/test_pr_review_aggregate.cpython-314.pyc +0 -0
  17. package/@opencode/agents/claude-reviewer.md +0 -82
  18. package/@opencode/agents/codex-reviewer.md +0 -83
  19. package/@opencode/agents/gemini-reviewer.md +0 -82
  20. package/@opencode/agents/gh-thread-reviewer.md +0 -122
  21. package/@opencode/agents/gh_review_harvest.py +0 -292
  22. package/@opencode/agents/pr-context.md +0 -82
  23. package/@opencode/agents/pr-fix.md +0 -243
  24. package/@opencode/agents/pr-precheck.md +0 -89
  25. package/@opencode/agents/pr-review-aggregate.md +0 -151
  26. package/@opencode/agents/pr_context.py +0 -351
  27. package/@opencode/agents/pr_precheck.py +0 -505
  28. package/@opencode/agents/pr_review_aggregate.py +0 -868
  29. package/@opencode/agents/test_pr_review_aggregate.py +0 -701
  30. package/@opencode/commands/doctor.md +0 -271
  31. package/@opencode/commands/git-commit-and-pr.md +0 -282
  32. package/@opencode/commands/git-release.md +0 -642
  33. package/@opencode/commands/oh_attach.json +0 -92
  34. package/@opencode/commands/opencode_attach.json +0 -29
  35. package/@opencode/commands/opencode_attach.py +0 -142
  36. package/@opencode/commands/pr-review-loop.md +0 -211
@@ -1,245 +1,240 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * PM2 开发服务栈交互式管理器
5
- * 启动 backend、front、admin 三个服务,并提供交互式命令界面
6
- *
7
- * 使用方法:
8
- * dx dev stack
9
- *
10
- * 可用命令:
11
- * r <service> - 重启服务 (r backend / r front / r admin)
12
- * l <service> - 查看日志 (l backend / l front / l admin)
13
- * s <service> - 停止服务 (s backend / s front / s admin)
14
- * list - 显示服务状态
15
- * monit - 打开实时监控
16
- * q / quit - 停止所有服务并退出
17
- * help - 显示帮助信息
18
- */
19
-
20
1
  import { exec, spawn } from 'node:child_process'
21
2
  import { promisify } from 'node:util'
22
3
  import readline from 'node:readline'
23
- import { join } from 'node:path'
24
- import { existsSync, rmSync, readdirSync, unlinkSync, mkdirSync } from 'node:fs'
4
+ import { isAbsolute, join } from 'node:path'
5
+ import { existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from 'node:fs'
25
6
  import { homedir } from 'node:os'
26
7
  import { logger } from '../../logger.js'
8
+ import { execManager } from '../../exec.js'
27
9
 
28
10
  const execPromise = promisify(exec)
29
11
 
12
+ const DEFAULT_SERVICES = ['backend', 'front', 'admin']
13
+ const DEFAULT_PRE_FLIGHT = {
14
+ pm2Reset: true,
15
+ killPorts: [3000, 3001, 3500],
16
+ forcePortCleanup: true,
17
+ cleanPaths: [
18
+ 'apps/front/.next',
19
+ 'dist/front',
20
+ 'apps/front/.eslintcache',
21
+ 'apps/admin-front/node_modules/.vite',
22
+ 'dist/admin-front',
23
+ 'apps/admin-front/.eslintcache',
24
+ ],
25
+ cleanTsBuildInfo: true,
26
+ cleanTsBuildInfoDirs: ['apps/front', 'apps/admin-front'],
27
+ }
28
+
30
29
  class PM2StackManager {
31
- constructor() {
32
- this.configPath = join(process.cwd(), 'ecosystem.config.cjs')
33
- this.services = ['backend', 'front', 'admin']
30
+ constructor(options = {}) {
31
+ this.projectRoot = process.cwd()
32
+ this.pm2Bin = String(options.pm2Bin || 'pnpm pm2').trim()
33
+
34
+ const ecosystemConfig = options.ecosystemConfig || 'ecosystem.config.cjs'
35
+ this.configPath = this.resolvePath(ecosystemConfig)
36
+
37
+ this.services = Array.isArray(options.services) && options.services.length > 0
38
+ ? options.services.map(item => String(item).trim()).filter(Boolean)
39
+ : [...DEFAULT_SERVICES]
40
+
41
+ const incomingPreflight = options.preflight && typeof options.preflight === 'object'
42
+ ? options.preflight
43
+ : {}
44
+
45
+ this.preflight = {
46
+ pm2Reset: incomingPreflight.pm2Reset ?? DEFAULT_PRE_FLIGHT.pm2Reset,
47
+ killPorts: this.normalizePorts(incomingPreflight.killPorts, DEFAULT_PRE_FLIGHT.killPorts),
48
+ forcePortCleanup:
49
+ incomingPreflight.forcePortCleanup ?? DEFAULT_PRE_FLIGHT.forcePortCleanup,
50
+ cleanPaths: this.normalizeStringArray(
51
+ incomingPreflight.cleanPaths,
52
+ DEFAULT_PRE_FLIGHT.cleanPaths,
53
+ ),
54
+ cleanTsBuildInfo:
55
+ incomingPreflight.cleanTsBuildInfo ?? DEFAULT_PRE_FLIGHT.cleanTsBuildInfo,
56
+ cleanTsBuildInfoDirs: this.normalizeStringArray(
57
+ incomingPreflight.cleanTsBuildInfoDirs,
58
+ DEFAULT_PRE_FLIGHT.cleanTsBuildInfoDirs,
59
+ ),
60
+ }
61
+
34
62
  this.isRunning = false
35
63
  }
36
64
 
65
+ resolvePath(targetPath) {
66
+ if (isAbsolute(targetPath)) return targetPath
67
+ return join(this.projectRoot, targetPath)
68
+ }
69
+
70
+ normalizePorts(input, fallback) {
71
+ const source = Array.isArray(input) ? input : fallback
72
+ return source
73
+ .map(port => Number(port))
74
+ .filter(port => Number.isFinite(port) && port > 0)
75
+ }
76
+
77
+ normalizeStringArray(input, fallback) {
78
+ const source = Array.isArray(input) ? input : fallback
79
+ return source.map(item => String(item).trim()).filter(Boolean)
80
+ }
81
+
37
82
  async start() {
38
- // 检查配置文件
39
83
  if (!existsSync(this.configPath)) {
40
- logger.error('未找到 ecosystem.config.cjs 配置文件')
41
- logger.info('请确保配置文件存在于项目根目录')
42
- process.exit(1)
84
+ logger.error(`未找到 PM2 配置文件: ${this.configPath}`)
85
+ logger.info('请在 commands.json 的 start.stack.stack.ecosystemConfig 中配置正确路径')
86
+ throw new Error('PM2 配置文件不存在')
43
87
  }
44
88
 
45
- logger.step('启动 PM2 开发服务栈')
89
+ logger.step('启动 PM2 交互式服务栈')
46
90
 
47
- try {
48
- // 启动前清理 PM2 状态(确保干净启动)
49
- await this.preparePM2State()
91
+ await this.prepareBeforeStart()
92
+ await this.pm2Start()
93
+ this.isRunning = true
94
+ await this.showStatus()
95
+ this.startInteractive()
96
+ }
50
97
 
51
- // 清理前端缓存
52
- this.cleanFrontendCache()
98
+ async prepareBeforeStart() {
99
+ logger.info('正在执行 stack 启动前检查...')
53
100
 
54
- // 启动 PM2 服务
55
- await this.pm2Start()
56
- this.isRunning = true
101
+ if (this.preflight.pm2Reset) {
102
+ await this.resetPm2State()
103
+ }
57
104
 
58
- // 显示初始状态
59
- await this.showStatus()
105
+ if (this.preflight.killPorts.length > 0) {
106
+ logger.info(`正在清理端口占用: ${this.preflight.killPorts.join(', ')}`)
107
+ await execManager.handlePortConflicts(
108
+ this.preflight.killPorts,
109
+ Boolean(this.preflight.forcePortCleanup),
110
+ )
111
+ }
60
112
 
61
- // 启动交互式命令行
62
- this.startInteractive()
63
- } catch (error) {
64
- logger.error('启动失败')
65
- logger.error(error.message)
66
- process.exit(1)
113
+ this.cleanConfiguredPaths()
114
+
115
+ if (this.preflight.cleanTsBuildInfo) {
116
+ this.cleanTsBuildInfoFiles()
67
117
  }
68
118
  }
69
119
 
70
- /**
71
- * 准备 PM2 状态
72
- * 启动前清理 PM2 状态,确保干净启动
73
- */
74
- async preparePM2State() {
75
- logger.info('正在准备 PM2 环境...')
76
- await this.fixPM2State()
77
- }
120
+ async resetPm2State() {
121
+ logger.info('正在重置 PM2 状态...')
78
122
 
79
- /**
80
- * 清理 PM2 状态
81
- * 停止守护进程并清理状态文件,确保干净启动
82
- */
83
- async fixPM2State() {
84
123
  try {
85
- // 1. 停止 PM2 守护进程
86
- logger.info('正在停止 PM2 守护进程...')
87
124
  try {
88
- // 先尝试删除所有进程
89
- try {
90
- await execPromise('pnpm pm2 delete all', { timeout: 5000 })
91
- } catch {
92
- // 忽略删除失败
93
- }
94
-
95
- // 然后停止守护进程
96
- await execPromise('pnpm pm2 kill', { timeout: 5000 })
97
- logger.success('PM2 守护进程已停止')
98
- } catch (killError) {
99
- // 如果 kill 失败(可能已经停止),继续执行清理
100
- logger.info('PM2 守护进程可能已停止')
125
+ await this.pm2Exec('delete all', { timeout: 5000 })
126
+ } catch {
127
+ // ignore
101
128
  }
102
129
 
103
- // 2. 清理 PM2 状态文件
104
- logger.info('正在清理 PM2 状态文件...')
105
- const pm2Home = join(homedir(), '.pm2')
106
- const stateFiles = ['dump.pm2', 'pm2.log', 'pm2.pid']
107
-
108
- for (const file of stateFiles) {
109
- const filePath = join(pm2Home, file)
110
- try {
111
- if (existsSync(filePath)) {
112
- rmSync(filePath, { force: true })
113
- logger.success(`已清理: ${file}`)
114
- }
115
- } catch (error) {
116
- // 忽略清理失败,继续执行
117
- logger.warn(`清理 ${file} 失败: ${error.message}`)
118
- }
119
- }
130
+ await this.pm2Exec('kill', { timeout: 5000 })
131
+ logger.success('PM2 守护进程已停止')
132
+ } catch {
133
+ logger.info('PM2 守护进程可能已停止')
134
+ }
135
+
136
+ const pm2Home = join(homedir(), '.pm2')
137
+ const stateFiles = ['dump.pm2', 'pm2.log', 'pm2.pid']
120
138
 
121
- // 3. 确保日志目录存在
122
- const projectRoot = process.cwd()
123
- const logDir = join(projectRoot, 'logs', 'pm2')
124
- if (!existsSync(logDir)) {
125
- mkdirSync(logDir, { recursive: true })
126
- logger.success('已创建日志目录')
139
+ for (const file of stateFiles) {
140
+ const filePath = join(pm2Home, file)
141
+ try {
142
+ if (existsSync(filePath)) {
143
+ rmSync(filePath, { force: true })
144
+ logger.success(`已清理 PM2 文件: ${file}`)
145
+ }
146
+ } catch (error) {
147
+ logger.warn(`清理 PM2 文件失败 (${file}): ${error.message}`)
127
148
  }
149
+ }
128
150
 
129
- logger.success('PM2 环境准备完成')
130
- } catch (error) {
131
- logger.warn(`准备 PM2 环境时出现警告: ${error.message}`)
132
- // 不抛出错误,继续尝试启动
151
+ const logDir = join(this.projectRoot, 'logs', 'pm2')
152
+ if (!existsSync(logDir)) {
153
+ mkdirSync(logDir, { recursive: true })
154
+ logger.success('已创建 PM2 日志目录')
133
155
  }
134
156
  }
135
157
 
136
- /**
137
- * 清理前端缓存
138
- * 清理 Next.js 用户端和 Vite 管理后台的构建缓存
139
- * 注意:此方法使用同步文件系统 API,无需 async/await
140
- */
141
- cleanFrontendCache() {
142
- logger.info('正在清理前端缓存...')
143
-
144
- const projectRoot = process.cwd()
145
- const cachePaths = [
146
- // Next.js 用户端 (apps/front)
147
- { path: join(projectRoot, 'apps/front/.next'), type: 'dir', name: 'Next.js 构建缓存' },
148
- { path: join(projectRoot, 'dist/front'), type: 'dir', name: 'Next.js 构建输出' },
149
- { path: join(projectRoot, 'apps/front/.eslintcache'), type: 'file', name: 'ESLint 缓存' },
150
- // Vite 管理后台 (apps/admin-front)
151
- {
152
- path: join(projectRoot, 'apps/admin-front/node_modules/.vite'),
153
- type: 'dir',
154
- name: 'Vite 开发服务器缓存',
155
- },
156
- { path: join(projectRoot, 'dist/admin-front'), type: 'dir', name: '管理后台构建输出' },
157
- {
158
- path: join(projectRoot, 'apps/admin-front/.eslintcache'),
159
- type: 'file',
160
- name: 'ESLint 缓存',
161
- },
162
- ]
163
-
164
- // 清理目录和文件
165
- for (const { path: cachePath, type, name } of cachePaths) {
158
+ cleanConfiguredPaths() {
159
+ if (this.preflight.cleanPaths.length === 0) return
160
+
161
+ logger.info('正在清理缓存路径...')
162
+
163
+ for (const rawPath of this.preflight.cleanPaths) {
164
+ const targetPath = this.resolvePath(rawPath)
166
165
  try {
167
- if (existsSync(cachePath)) {
168
- if (type === 'dir') {
169
- rmSync(cachePath, { recursive: true, force: true })
170
- logger.success(`已清理: ${name}`)
171
- } else {
172
- unlinkSync(cachePath)
173
- logger.success(`已清理: ${name}`)
174
- }
166
+ if (existsSync(targetPath)) {
167
+ rmSync(targetPath, { recursive: true, force: true })
168
+ logger.success(`已清理: ${rawPath}`)
175
169
  }
176
170
  } catch (error) {
177
- logger.warn(`清理 ${name} 失败: ${error.message}`)
171
+ logger.warn(`清理失败 (${rawPath}): ${error.message}`)
178
172
  }
179
173
  }
174
+ }
175
+
176
+ cleanTsBuildInfoFiles() {
177
+ if (this.preflight.cleanTsBuildInfoDirs.length === 0) return
180
178
 
181
- // 清理 TypeScript 构建信息文件 (*.tsbuildinfo)
182
- const tsBuildInfoPaths = [
183
- join(projectRoot, 'apps/front'),
184
- join(projectRoot, 'apps/admin-front'),
185
- ]
179
+ logger.info('正在清理 TypeScript 构建缓存...')
180
+
181
+ for (const rawDirPath of this.preflight.cleanTsBuildInfoDirs) {
182
+ const dirPath = this.resolvePath(rawDirPath)
183
+ if (!existsSync(dirPath)) continue
186
184
 
187
- for (const dirPath of tsBuildInfoPaths) {
188
185
  try {
189
- if (existsSync(dirPath)) {
190
- const files = readdirSync(dirPath)
191
- for (const file of files) {
192
- if (file.endsWith('.tsbuildinfo')) {
193
- const filePath = join(dirPath, file)
194
- try {
195
- unlinkSync(filePath)
196
- logger.success(`已清理: TypeScript 构建信息 (${file})`)
197
- } catch (error) {
198
- logger.warn(`清理 ${file} 失败: ${error.message}`)
199
- }
200
- }
186
+ const files = readdirSync(dirPath)
187
+ for (const file of files) {
188
+ if (!file.endsWith('.tsbuildinfo')) continue
189
+ const filePath = join(dirPath, file)
190
+ try {
191
+ unlinkSync(filePath)
192
+ logger.success(`已清理: ${join(rawDirPath, file)}`)
193
+ } catch (error) {
194
+ logger.warn(`清理失败 (${join(rawDirPath, file)}): ${error.message}`)
201
195
  }
202
196
  }
203
197
  } catch (error) {
204
- // 忽略目录不存在的错误
198
+ logger.warn(`读取目录失败 (${rawDirPath}): ${error.message}`)
205
199
  }
206
200
  }
201
+ }
207
202
 
208
- logger.success('前端缓存清理完成')
203
+ async pm2Exec(args, options = {}) {
204
+ return execPromise(`${this.pm2Bin} ${args}`, options)
209
205
  }
210
206
 
211
207
  async pm2Start() {
212
- logger.info('正在启动服务...')
213
- try {
214
- const { stderr } = await execPromise(`pnpm pm2 start ${this.configPath}`, { timeout: 30000 })
215
- if (stderr && !stderr.includes('[PM2]')) {
216
- logger.warn(stderr)
217
- }
218
- logger.success('服务启动成功')
219
- } catch (error) {
220
- throw new Error(`启动失败: ${error.message || error.stderr || error.stdout || '未知错误'}`)
208
+ logger.info('正在启动 PM2 服务...')
209
+ const { stderr } = await this.pm2Exec(`start "${this.configPath}"`, { timeout: 30000 })
210
+ if (stderr && !stderr.includes('[PM2]')) {
211
+ logger.warn(stderr)
221
212
  }
213
+ logger.success('服务启动成功')
222
214
  }
223
215
 
224
216
  async showStatus() {
225
217
  try {
226
- const { stdout } = await execPromise('pnpm pm2 list', { timeout: 5000 })
218
+ const { stdout } = await this.pm2Exec('list', { timeout: 5000 })
227
219
  console.log(`\n${stdout}`)
228
220
  } catch (error) {
229
221
  logger.error(`获取状态失败: ${error.message}`)
230
222
  }
231
223
  }
232
224
 
225
+ ensureKnownService(service) {
226
+ if (this.services.includes(service)) return true
227
+ logger.error(`未知服务: ${service}`)
228
+ logger.info(`可用服务: ${this.services.join(', ')}`)
229
+ return false
230
+ }
231
+
233
232
  async restart(service) {
234
- if (!this.services.includes(service)) {
235
- logger.error(`未知服务: ${service}`)
236
- logger.info(`可用服务: ${this.services.join(', ')}`)
237
- return
238
- }
233
+ if (!this.ensureKnownService(service)) return
239
234
 
240
235
  logger.info(`正在重启 ${service}...`)
241
236
  try {
242
- await execPromise(`pnpm pm2 restart ${service}`)
237
+ await this.pm2Exec(`restart ${service}`)
243
238
  logger.success(`${service} 重启成功`)
244
239
  await this.showStatus()
245
240
  } catch (error) {
@@ -248,21 +243,15 @@ class PM2StackManager {
248
243
  }
249
244
 
250
245
  async logs(service) {
251
- if (!this.services.includes(service)) {
252
- logger.error(`未知服务: ${service}`)
253
- logger.info(`可用服务: ${this.services.join(', ')}`)
254
- return
255
- }
246
+ if (!this.ensureKnownService(service)) return
256
247
 
257
248
  logger.info(`查看 ${service} 日志(按 Ctrl+C 返回)...`)
258
249
  console.log('')
259
250
 
260
- // 使用 spawn 以便实时显示日志
261
- const pm2Logs = spawn('pnpm', ['pm2', 'logs', service], {
251
+ const pm2Logs = spawn('bash', ['-lc', `${this.pm2Bin} logs ${service}`], {
262
252
  stdio: 'inherit',
263
253
  })
264
254
 
265
- // 等待用户按 Ctrl+C
266
255
  await new Promise(resolve => {
267
256
  pm2Logs.on('exit', resolve)
268
257
  })
@@ -272,16 +261,12 @@ class PM2StackManager {
272
261
  }
273
262
 
274
263
  async stop(service) {
275
- if (!this.services.includes(service)) {
276
- logger.error(`未知服务: ${service}`)
277
- logger.info(`可用服务: ${this.services.join(', ')}`)
278
- return
279
- }
264
+ if (!this.ensureKnownService(service)) return
280
265
 
281
266
  logger.info(`正在停止 ${service}...`)
282
267
  try {
283
- await execPromise(`pnpm pm2 stop ${service}`)
284
- logger.success(`${service} 已停止`)
268
+ await this.pm2Exec(`stop ${service}`)
269
+ logger.success(`${service} 停止成功`)
285
270
  await this.showStatus()
286
271
  } catch (error) {
287
272
  logger.error(`停止失败: ${error.message}`)
@@ -289,10 +274,10 @@ class PM2StackManager {
289
274
  }
290
275
 
291
276
  async monit() {
292
- logger.info('启动实时监控(按 Ctrl+C 返回)...')
277
+ logger.info('打开 PM2 实时监控(按 q 退出)...')
293
278
  console.log('')
294
279
 
295
- const pm2Monit = spawn('pnpm', ['pm2', 'monit'], {
280
+ const pm2Monit = spawn('bash', ['-lc', `${this.pm2Bin} monit`], {
296
281
  stdio: 'inherit',
297
282
  })
298
283
 
@@ -305,35 +290,29 @@ class PM2StackManager {
305
290
  }
306
291
 
307
292
  async stopAll() {
308
- logger.info('正在停止所有服务...')
293
+ if (!this.isRunning) return
294
+
295
+ logger.step('正在停止所有服务...')
309
296
  try {
310
- await execPromise('pnpm pm2 stop all')
311
- await execPromise('pnpm pm2 delete all')
297
+ await this.pm2Exec('stop all')
298
+ await this.pm2Exec('delete all')
312
299
  logger.success('所有服务已停止')
313
300
  this.isRunning = false
314
301
  } catch (error) {
315
- logger.error(`停止失败: ${error.message}`)
302
+ logger.error(`停止服务失败: ${error.message}`)
316
303
  }
317
304
  }
318
305
 
319
306
  showHelp() {
320
- console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
321
- console.log('🚀 服务访问链接:')
322
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
323
- console.log(' 📦 Backend (后端 API) → http://localhost:3000')
324
- console.log(' 📦 API 文档 (Swagger) → http://localhost:3000/doc')
325
- console.log(' 🌐 Front (用户端) → http://localhost:3001')
326
- console.log(' ⚙️ Admin (管理后台) → http://localhost:3500')
327
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
328
- console.log('\n📋 可用命令:')
329
- console.log(' r <service> - 重启服务 (例: r backend)')
330
- console.log(' l <service> - 查看日志 (例: l backend)')
331
- console.log(' s <service> - 停止服务 (例: s backend)')
307
+ console.log('\n可用命令:')
308
+ console.log(' r <service> - 重启服务')
309
+ console.log(' l <service> - 查看日志')
310
+ console.log(' s <service> - 停止服务')
332
311
  console.log(' list - 显示服务状态')
333
312
  console.log(' monit - 打开实时监控')
334
313
  console.log(' q / quit - 停止所有服务并退出')
335
314
  console.log(' help - 显示此帮助信息')
336
- console.log('\n📦 可用服务: backend, front, admin\n')
315
+ console.log(`\n可用服务: ${this.services.join(', ')}\n`)
337
316
  }
338
317
 
339
318
  showPrompt() {
@@ -341,7 +320,6 @@ class PM2StackManager {
341
320
  }
342
321
 
343
322
  startInteractive() {
344
- // 自动显示帮助信息和访问链接
345
323
  this.showHelp()
346
324
  this.showPrompt()
347
325
 
@@ -363,29 +341,20 @@ class PM2StackManager {
363
341
  switch (cmd.toLowerCase()) {
364
342
  case 'r':
365
343
  case 'restart':
366
- if (args[0]) {
367
- await this.restart(args[0])
368
- } else {
369
- logger.error('请指定服务名称,例如: r backend')
370
- }
344
+ if (args[0]) await this.restart(args[0])
345
+ else logger.error('请指定服务名称,例如: r backend')
371
346
  break
372
347
 
373
348
  case 'l':
374
349
  case 'logs':
375
- if (args[0]) {
376
- await this.logs(args[0])
377
- } else {
378
- logger.error('请指定服务名称,例如: l backend')
379
- }
380
- return // logs 命令会自己处理 prompt
350
+ if (args[0]) await this.logs(args[0])
351
+ else logger.error('请指定服务名称,例如: l backend')
352
+ return
381
353
 
382
354
  case 's':
383
355
  case 'stop':
384
- if (args[0]) {
385
- await this.stop(args[0])
386
- } else {
387
- logger.error('请指定服务名称,例如: s backend')
388
- }
356
+ if (args[0]) await this.stop(args[0])
357
+ else logger.error('请指定服务名称,例如: s backend')
389
358
  break
390
359
 
391
360
  case 'list':
@@ -396,7 +365,7 @@ class PM2StackManager {
396
365
  case 'monit':
397
366
  case 'monitor':
398
367
  await this.monit()
399
- return // monit 命令会自己处理 prompt
368
+ return
400
369
 
401
370
  case 'q':
402
371
  case 'quit':
@@ -429,7 +398,6 @@ class PM2StackManager {
429
398
  process.exit(0)
430
399
  })
431
400
 
432
- // 处理 Ctrl+C
433
401
  process.on('SIGINT', async () => {
434
402
  console.log('\n')
435
403
  await this.stopAll()
@@ -438,14 +406,7 @@ class PM2StackManager {
438
406
  }
439
407
  }
440
408
 
441
- // 主函数
442
- async function main() {
443
- const manager = new PM2StackManager()
409
+ export async function runPm2Stack(options = {}) {
410
+ const manager = new PM2StackManager(options)
444
411
  await manager.start()
445
412
  }
446
-
447
- main().catch(error => {
448
- logger.error('启动失败')
449
- logger.error(error.message)
450
- process.exit(1)
451
- })
@@ -3,12 +3,6 @@ import { logger } from '../../logger.js'
3
3
  export async function handleStart(cli, args) {
4
4
  const service = args[0] || 'dev'
5
5
 
6
- // 处理 stack 子命令 - PM2 交互式管理
7
- if (service === 'stack') {
8
- await import('./stack.js')
9
- return // stack.js 会接管执行流程
10
- }
11
-
12
6
  const environment = cli.determineEnvironment()
13
7
  const envKey = cli.normalizeEnvKey(environment)
14
8
 
package/lib/cli/dx-cli.js CHANGED
@@ -169,7 +169,7 @@ class DxCli {
169
169
  try {
170
170
  const generateConfig = this.commands?.db?.generate
171
171
  if (!generateConfig?.command) {
172
- throw new Error('未找到 db.generate 命令配置,请检查 scripts/config/commands.json')
172
+ throw new Error('未找到 db.generate 命令配置,请检查 dx/config/commands.json')
173
173
  }
174
174
 
175
175
  const envKey = this.normalizeEnvKey(environment)
@@ -834,6 +834,15 @@ class DxCli {
834
834
  return
835
835
  }
836
836
 
837
+ if (config.internal === 'pm2-stack') {
838
+ await withTempEnv(async () => {
839
+ const { runPm2Stack } = await import('./commands/stack.js')
840
+ const stackConfig = config.stack && typeof config.stack === 'object' ? config.stack : {}
841
+ await runPm2Stack(stackConfig)
842
+ })
843
+ return
844
+ }
845
+
837
846
  throw new Error(`未知 internal runner: ${config.internal}`)
838
847
  }
839
848