@ranger1/dx 0.1.77 → 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.
- package/README.md +92 -31
- package/bin/dx.js +3 -3
- package/lib/cli/commands/stack.js +198 -237
- package/lib/cli/commands/start.js +0 -6
- package/lib/cli/dx-cli.js +10 -1
- package/lib/cli/help.js +4 -4
- package/lib/{opencode-initial.js → codex-initial.js} +3 -82
- package/package.json +1 -2
- package/@opencode/agents/__pycache__/gh_review_harvest.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/pr_context.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/pr_precheck.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/pr_review_aggregate.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/test_pr_review_aggregate.cpython-314-pytest-9.0.2.pyc +0 -0
- package/@opencode/agents/__pycache__/test_pr_review_aggregate.cpython-314.pyc +0 -0
- package/@opencode/agents/claude-reviewer.md +0 -82
- package/@opencode/agents/codex-reviewer.md +0 -83
- package/@opencode/agents/gemini-reviewer.md +0 -82
- package/@opencode/agents/gh-thread-reviewer.md +0 -122
- package/@opencode/agents/gh_review_harvest.py +0 -292
- package/@opencode/agents/pr-context.md +0 -82
- package/@opencode/agents/pr-fix.md +0 -243
- package/@opencode/agents/pr-precheck.md +0 -89
- package/@opencode/agents/pr-review-aggregate.md +0 -151
- package/@opencode/agents/pr_context.py +0 -351
- package/@opencode/agents/pr_precheck.py +0 -505
- package/@opencode/agents/pr_review_aggregate.py +0 -868
- package/@opencode/agents/test_pr_review_aggregate.py +0 -701
- package/@opencode/commands/doctor.md +0 -271
- package/@opencode/commands/git-commit-and-pr.md +0 -282
- package/@opencode/commands/git-release.md +0 -642
- package/@opencode/commands/oh_attach.json +0 -92
- package/@opencode/commands/opencode_attach.json +0 -29
- package/@opencode/commands/opencode_attach.py +0 -142
- 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,
|
|
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.
|
|
33
|
-
this.
|
|
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(
|
|
41
|
-
logger.info('
|
|
42
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
91
|
+
await this.prepareBeforeStart()
|
|
92
|
+
await this.pm2Start()
|
|
93
|
+
this.isRunning = true
|
|
94
|
+
await this.showStatus()
|
|
95
|
+
this.startInteractive()
|
|
96
|
+
}
|
|
50
97
|
|
|
51
|
-
|
|
52
|
-
|
|
98
|
+
async prepareBeforeStart() {
|
|
99
|
+
logger.info('正在执行 stack 启动前检查...')
|
|
53
100
|
|
|
54
|
-
|
|
55
|
-
await this.
|
|
56
|
-
|
|
101
|
+
if (this.preflight.pm2Reset) {
|
|
102
|
+
await this.resetPm2State()
|
|
103
|
+
}
|
|
57
104
|
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
104
|
-
logger.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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(
|
|
168
|
-
|
|
169
|
-
|
|
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(
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
203
|
+
async pm2Exec(args, options = {}) {
|
|
204
|
+
return execPromise(`${this.pm2Bin} ${args}`, options)
|
|
209
205
|
}
|
|
210
206
|
|
|
211
207
|
async pm2Start() {
|
|
212
|
-
logger.info('
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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('
|
|
277
|
+
logger.info('打开 PM2 实时监控(按 q 退出)...')
|
|
293
278
|
console.log('')
|
|
294
279
|
|
|
295
|
-
const pm2Monit = spawn('
|
|
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
|
-
|
|
293
|
+
if (!this.isRunning) return
|
|
294
|
+
|
|
295
|
+
logger.step('正在停止所有服务...')
|
|
309
296
|
try {
|
|
310
|
-
await
|
|
311
|
-
await
|
|
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(
|
|
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('
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
377
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 命令配置,请检查
|
|
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
|
|
package/lib/cli/help.js
CHANGED
|
@@ -11,8 +11,8 @@ export function showHelp() {
|
|
|
11
11
|
'',
|
|
12
12
|
'命令:',
|
|
13
13
|
' start [service] [环境标志] 启动/桥接服务',
|
|
14
|
-
' service:
|
|
15
|
-
' stack:
|
|
14
|
+
' service: 由 dx/config/commands.json 的 start.* 决定(默认: dev)',
|
|
15
|
+
' stack: 需在 commands.json 配置 start.stack(internal: pm2-stack)',
|
|
16
16
|
' 环境标志: --dev, --staging, --prod, --test, --e2e(支持别名 --development、--production 等)',
|
|
17
17
|
" 说明: 传入 --staging 时会加载 '.env.staging(.local)' 层,同时复用生产构建/启动流程",
|
|
18
18
|
'',
|
|
@@ -68,7 +68,7 @@ export function showHelp() {
|
|
|
68
68
|
'',
|
|
69
69
|
' status 查看系统状态',
|
|
70
70
|
'',
|
|
71
|
-
' initial 初始化
|
|
71
|
+
' initial 初始化 codex 模板到 ~/.codex(覆盖同名文件)',
|
|
72
72
|
'',
|
|
73
73
|
'选项:',
|
|
74
74
|
' --dev, --development 使用开发环境',
|
|
@@ -82,7 +82,7 @@ export function showHelp() {
|
|
|
82
82
|
' -V, --version 显示版本号',
|
|
83
83
|
'',
|
|
84
84
|
'示例:',
|
|
85
|
-
' dx start stack # PM2
|
|
85
|
+
' dx start stack # PM2 交互式服务栈(需在 commands.json 配置 start.stack)',
|
|
86
86
|
' dx start backend --dev # 启动后端开发服务',
|
|
87
87
|
' dx start front --dev # 启动用户前端开发服务',
|
|
88
88
|
' dx start admin --dev # 启动管理后台开发服务',
|