@quicktvui/web-cli 1.0.7 → 2.1.0
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 +101 -113
- package/bin/qt-web-cli-watch.js +0 -0
- package/bin/qt-web-cli.js +401 -100
- package/lib/BundleWatcher.js +192 -0
- package/lib/DevBuildManager.js +295 -0
- package/lib/DevServer.js +586 -0
- package/lib/HotReloader.js +142 -0
- package/lib/index.js +52 -122
- package/package.json +42 -19
- package/templates/dev-renderer.html +357 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BundleWatcher - 监听 dist/dev 目录变化,检测 bundle 文件就绪
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 监听 dist/dev 目录的文件变化
|
|
6
|
+
* 2. 检测 index.bundle 入口文件是否就绪
|
|
7
|
+
* 3. 文件变化时通知回调
|
|
8
|
+
* 4. 支持防抖,避免频繁触发
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const chokidar = require('chokidar')
|
|
12
|
+
const path = require('path')
|
|
13
|
+
const fs = require('fs')
|
|
14
|
+
const signale = require('signale')
|
|
15
|
+
|
|
16
|
+
class BundleWatcher {
|
|
17
|
+
constructor(projectRoot, options = {}) {
|
|
18
|
+
this.projectRoot = projectRoot
|
|
19
|
+
this.distDir = path.join(projectRoot, 'dist', 'dev')
|
|
20
|
+
this.watcher = null
|
|
21
|
+
this.bundleEntry = null
|
|
22
|
+
this.onChange = options.onChange || null
|
|
23
|
+
this.onReady = options.onReady || null
|
|
24
|
+
this.debounceMs = options.debounceMs || 800
|
|
25
|
+
this._debounceTimer = null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 检测 bundle 入口文件
|
|
30
|
+
*/
|
|
31
|
+
detectBundleEntry() {
|
|
32
|
+
if (!fs.existsSync(this.distDir)) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 优先查找 index.bundle
|
|
37
|
+
const indexPath = path.join(this.distDir, 'index.bundle')
|
|
38
|
+
if (fs.existsSync(indexPath)) {
|
|
39
|
+
return 'index.bundle'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 兼容 index.android.js
|
|
43
|
+
const androidPath = path.join(this.distDir, 'index.android.js')
|
|
44
|
+
if (fs.existsSync(androidPath)) {
|
|
45
|
+
return 'index.android.js'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 获取 bundle 的完整 URL 路径(相对于服务器根目录)
|
|
53
|
+
*/
|
|
54
|
+
getBundleUrlPath() {
|
|
55
|
+
const entry = this.detectBundleEntry()
|
|
56
|
+
if (!entry) return null
|
|
57
|
+
return `/dist/dev/${entry}`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 获取所有 bundle 文件列表
|
|
62
|
+
*/
|
|
63
|
+
listBundleFiles() {
|
|
64
|
+
if (!fs.existsSync(this.distDir)) {
|
|
65
|
+
return []
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const files = []
|
|
69
|
+
const entries = fs.readdirSync(this.distDir)
|
|
70
|
+
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
const fullPath = path.join(this.distDir, entry)
|
|
73
|
+
const stat = fs.statSync(fullPath)
|
|
74
|
+
|
|
75
|
+
if (stat.isFile()) {
|
|
76
|
+
files.push({
|
|
77
|
+
name: entry,
|
|
78
|
+
path: fullPath,
|
|
79
|
+
size: stat.size,
|
|
80
|
+
mtime: stat.mtime,
|
|
81
|
+
urlPath: `/dist/dev/${entry}`,
|
|
82
|
+
isEntry: entry === 'index.bundle' || entry === 'index.android.js',
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return files
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 启动监听
|
|
92
|
+
*/
|
|
93
|
+
start() {
|
|
94
|
+
// 确保 dist 目录存在
|
|
95
|
+
if (!fs.existsSync(this.distDir)) {
|
|
96
|
+
fs.mkdirSync(this.distDir, { recursive: true })
|
|
97
|
+
signale.info(`创建 dist/dev 目录: ${this.distDir}`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 先检测已有 bundle
|
|
101
|
+
const existingEntry = this.detectBundleEntry()
|
|
102
|
+
if (existingEntry) {
|
|
103
|
+
this.bundleEntry = existingEntry
|
|
104
|
+
signale.success(`检测到已有构建产物: dist/dev/${existingEntry}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 启动 chokidar 监听
|
|
108
|
+
this.watcher = chokidar.watch(this.distDir, {
|
|
109
|
+
ignored: [/node_modules/, /\.DS_Store/, /Thumbs\.db/],
|
|
110
|
+
ignoreInitial: true,
|
|
111
|
+
persistent: true,
|
|
112
|
+
awaitWriteFinish: {
|
|
113
|
+
stabilityThreshold: 300,
|
|
114
|
+
pollInterval: 100,
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
this.watcher.on('add', (filePath) => this._handleFileChange(filePath, 'add'))
|
|
119
|
+
this.watcher.on('change', (filePath) => this._handleFileChange(filePath, 'change'))
|
|
120
|
+
this.watcher.on('unlink', (filePath) => this._handleFileChange(filePath, 'unlink'))
|
|
121
|
+
|
|
122
|
+
this.watcher.on('ready', () => {
|
|
123
|
+
signale.info('BundleWatcher 已就绪,正在监听 dist/dev/ 目录变化...')
|
|
124
|
+
if (this.onReady) this.onReady()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
this.watcher.on('error', (error) => {
|
|
128
|
+
signale.error('BundleWatcher 错误:', error.message)
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 处理文件变化
|
|
134
|
+
*/
|
|
135
|
+
_handleFileChange(filePath, eventType) {
|
|
136
|
+
const relativePath = path.relative(this.distDir, filePath)
|
|
137
|
+
const isBundle = this._isBundleFile(filePath)
|
|
138
|
+
|
|
139
|
+
if (isBundle) {
|
|
140
|
+
// 检测入口文件
|
|
141
|
+
const entry = this.detectBundleEntry()
|
|
142
|
+
if (entry && entry !== this.bundleEntry) {
|
|
143
|
+
this.bundleEntry = entry
|
|
144
|
+
signale.success(`检测到 bundle 入口: dist/dev/${entry}`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 防抖通知
|
|
148
|
+
clearTimeout(this._debounceTimer)
|
|
149
|
+
this._debounceTimer = setTimeout(() => {
|
|
150
|
+
signale.info(`文件变化: ${relativePath} (${eventType})`)
|
|
151
|
+
if (this.onChange) {
|
|
152
|
+
this.onChange({
|
|
153
|
+
type: eventType,
|
|
154
|
+
file: relativePath,
|
|
155
|
+
fullPath: filePath,
|
|
156
|
+
isBundle: true,
|
|
157
|
+
isEntry: relativePath === 'index.bundle' || relativePath === 'index.android.js',
|
|
158
|
+
bundleEntry: this.detectBundleEntry(),
|
|
159
|
+
bundleUrlPath: this.getBundleUrlPath(),
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
}, this.debounceMs)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 判断是否为 bundle 文件
|
|
168
|
+
*/
|
|
169
|
+
_isBundleFile(filePath) {
|
|
170
|
+
const basename = path.basename(filePath)
|
|
171
|
+
return (
|
|
172
|
+
basename.endsWith('.bundle') ||
|
|
173
|
+
basename.endsWith('.js') ||
|
|
174
|
+
basename.endsWith('.map') ||
|
|
175
|
+
/assets[\\/]/.test(filePath)
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 停止监听
|
|
181
|
+
*/
|
|
182
|
+
stop() {
|
|
183
|
+
clearTimeout(this._debounceTimer)
|
|
184
|
+
if (this.watcher) {
|
|
185
|
+
this.watcher.close()
|
|
186
|
+
this.watcher = null
|
|
187
|
+
signale.info('BundleWatcher 已停止')
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = BundleWatcher
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevBuildManager - 管理项目的 dev 构建进程
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 检测项目的 dev 脚本
|
|
6
|
+
* 2. 调用 npm run dev 启动构建
|
|
7
|
+
* 3. 监听子进程 stdout/stderr,检测构建就绪信号
|
|
8
|
+
* 4. 管理子进程生命周期(启动/停止/重启)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { spawn } = require('child_process')
|
|
12
|
+
const path = require('path')
|
|
13
|
+
const fs = require('fs')
|
|
14
|
+
const signale = require('signale')
|
|
15
|
+
|
|
16
|
+
// 构建就绪的检测关键词(不区分大小写匹配)
|
|
17
|
+
// 匹配 webpack 4/5 / qt-dev / hippy-debug-server 的各种输出格式
|
|
18
|
+
const READY_SIGNALS = [
|
|
19
|
+
'webpack compiled',
|
|
20
|
+
'compiled successfully',
|
|
21
|
+
'compiled with warnings',
|
|
22
|
+
'webpack build is watching',
|
|
23
|
+
// webpack 4 输出: "webpack is watching the files…"
|
|
24
|
+
'watching the files',
|
|
25
|
+
// webpack 输出 "[built]" 标记(如:[./src/routes.js] 3.19 KiB {index} [built])
|
|
26
|
+
'] [built]',
|
|
27
|
+
// hidden modules 输出(webpack 编译完成的最终行)
|
|
28
|
+
'hidden modules',
|
|
29
|
+
// qt-dev / hippy-debug-server 的输出
|
|
30
|
+
'bundle is valid',
|
|
31
|
+
'bundle created',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
// 编译成功但进程因非编译原因退出的信号(如 adb 连接失败)
|
|
35
|
+
// 这些信号表明编译已完成,不应重启进程
|
|
36
|
+
const COMPILATION_DONE_SIGNALS = ['[built]', 'hidden modules', 'bundle is valid', 'bundle created']
|
|
37
|
+
|
|
38
|
+
class DevBuildManager {
|
|
39
|
+
constructor(projectRoot, options = {}) {
|
|
40
|
+
this.projectRoot = projectRoot
|
|
41
|
+
this.pkg = options.pkg || {}
|
|
42
|
+
this.childProcess = null
|
|
43
|
+
this.isReady = false
|
|
44
|
+
this.onReady = options.onReady || null
|
|
45
|
+
this.onOutput = options.onOutput || null
|
|
46
|
+
this.onError = options.onError || null
|
|
47
|
+
this.devScriptName = options.devScript || 'dev'
|
|
48
|
+
this._restartCount = 0
|
|
49
|
+
this._maxRestarts = 3
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 检测项目可用的 dev 脚本
|
|
54
|
+
*/
|
|
55
|
+
detectDevScript() {
|
|
56
|
+
const scripts = this.pkg.scripts || {}
|
|
57
|
+
|
|
58
|
+
// 按优先级检测
|
|
59
|
+
const candidates = [this.devScriptName, 'dev', 'dev:android', 'build:dev']
|
|
60
|
+
for (const name of candidates) {
|
|
61
|
+
if (scripts[name]) {
|
|
62
|
+
return { name, script: scripts[name] }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 检测 dist/dev 目录是否已有构建产物
|
|
71
|
+
*/
|
|
72
|
+
detectExistingBundle() {
|
|
73
|
+
const distDevDir = path.join(this.projectRoot, 'dist', 'dev')
|
|
74
|
+
|
|
75
|
+
if (!fs.existsSync(distDevDir)) {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 查找 index.bundle
|
|
80
|
+
const indexPath = path.join(distDevDir, 'index.bundle')
|
|
81
|
+
if (fs.existsSync(indexPath)) {
|
|
82
|
+
const stat = fs.statSync(indexPath)
|
|
83
|
+
return {
|
|
84
|
+
entry: 'index.bundle',
|
|
85
|
+
path: indexPath,
|
|
86
|
+
size: stat.size,
|
|
87
|
+
mtime: stat.mtime,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 查找 index.android.js (兼容旧模式)
|
|
92
|
+
const androidPath = path.join(distDevDir, 'index.android.js')
|
|
93
|
+
if (fs.existsSync(androidPath)) {
|
|
94
|
+
const stat = fs.statSync(androidPath)
|
|
95
|
+
return {
|
|
96
|
+
entry: 'index.android.js',
|
|
97
|
+
path: androidPath,
|
|
98
|
+
size: stat.size,
|
|
99
|
+
mtime: stat.mtime,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 启动 dev 构建
|
|
108
|
+
*/
|
|
109
|
+
start() {
|
|
110
|
+
const devScript = this.detectDevScript()
|
|
111
|
+
if (!devScript) {
|
|
112
|
+
signale.error('项目未配置 dev 脚本,请在 package.json 中添加 "dev" 脚本')
|
|
113
|
+
signale.error('示例: "dev": "qt-dev android -c ./scripts/quicktvui-webpack.dev.ts"')
|
|
114
|
+
return Promise.reject(new Error('No dev script found'))
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
signale.info(`检测到 dev 脚本: npm run ${devScript.name} → ${devScript.script}`)
|
|
118
|
+
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
this._startProcess(devScript.name, resolve, reject)
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 启动子进程
|
|
126
|
+
*/
|
|
127
|
+
_startProcess(scriptName, resolve, reject) {
|
|
128
|
+
const isWindows = process.platform === 'win32'
|
|
129
|
+
const cmd = isWindows ? 'npm.cmd' : 'npm'
|
|
130
|
+
const args = ['run', scriptName]
|
|
131
|
+
|
|
132
|
+
signale.pending(`正在启动 dev 构建: npm run ${scriptName} ...`)
|
|
133
|
+
|
|
134
|
+
this.childProcess = spawn(cmd, args, {
|
|
135
|
+
cwd: this.projectRoot,
|
|
136
|
+
stdio: 'pipe',
|
|
137
|
+
shell: isWindows,
|
|
138
|
+
env: {
|
|
139
|
+
...process.env,
|
|
140
|
+
NODE_ENV: 'development',
|
|
141
|
+
FORCE_COLOR: '1',
|
|
142
|
+
// 阻止 webpack-dev-server 自动打开浏览器
|
|
143
|
+
// web-cli 自己管理浏览器打开逻辑
|
|
144
|
+
BROWSER: 'none',
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
let stdoutBuffer = ''
|
|
149
|
+
let stderrBuffer = ''
|
|
150
|
+
let resolved = false
|
|
151
|
+
|
|
152
|
+
// 去除 ANSI 颜色码的正则(用于构建就绪信号检测)
|
|
153
|
+
const ANSI_REGEX = /\x1b\[[0-9;]*[a-zA-Z]/g
|
|
154
|
+
|
|
155
|
+
this.childProcess.stdout.on('data', (data) => {
|
|
156
|
+
const text = data.toString()
|
|
157
|
+
stdoutBuffer += text
|
|
158
|
+
|
|
159
|
+
// 输出到控制台
|
|
160
|
+
if (this.onOutput) {
|
|
161
|
+
this.onOutput(text, 'stdout')
|
|
162
|
+
} else {
|
|
163
|
+
process.stdout.write(text)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 检测构建就绪信号(不区分大小写)
|
|
167
|
+
// 先去除 ANSI 颜色码再匹配,避免颜色码将关键词截断
|
|
168
|
+
if (!resolved && !this.isReady) {
|
|
169
|
+
const cleanBuffer = stdoutBuffer.replace(ANSI_REGEX, '')
|
|
170
|
+
const lowerBuffer = cleanBuffer.toLowerCase()
|
|
171
|
+
for (const signal of READY_SIGNALS) {
|
|
172
|
+
if (lowerBuffer.includes(signal.toLowerCase())) {
|
|
173
|
+
this.isReady = true
|
|
174
|
+
resolved = true
|
|
175
|
+
signale.success('dev 构建已完成,bundle 已就绪')
|
|
176
|
+
if (this.onReady) this.onReady()
|
|
177
|
+
resolve()
|
|
178
|
+
break
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
this.childProcess.stderr.on('data', (data) => {
|
|
185
|
+
const text = data.toString()
|
|
186
|
+
stderrBuffer += text
|
|
187
|
+
|
|
188
|
+
if (this.onOutput) {
|
|
189
|
+
this.onOutput(text, 'stderr')
|
|
190
|
+
} else {
|
|
191
|
+
process.stderr.write(text)
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
this.childProcess.on('error', (err) => {
|
|
196
|
+
signale.error('dev 构建进程启动失败:', err.message)
|
|
197
|
+
if (this.onError) this.onError(err)
|
|
198
|
+
if (!resolved) {
|
|
199
|
+
resolved = true
|
|
200
|
+
reject(err)
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
this.childProcess.on('exit', (code, signal) => {
|
|
205
|
+
this.childProcess = null
|
|
206
|
+
|
|
207
|
+
if (code !== 0 && code !== null) {
|
|
208
|
+
// 检查编译是否实际已完成(即使 READY_SIGNALS 没匹配到)
|
|
209
|
+
// 场景:webpack 编译输出 "[built]" 但进程随后因 adb 等原因退出
|
|
210
|
+
const compilationDone = this.isReady || this._checkCompilationDone(stdoutBuffer)
|
|
211
|
+
const bundleExists = !!this.detectExistingBundle()
|
|
212
|
+
|
|
213
|
+
if (compilationDone && bundleExists) {
|
|
214
|
+
signale.warn(`dev 进程已退出 (code: ${code}),但编译产物已生成,web 预览不受影响`)
|
|
215
|
+
this.isReady = true
|
|
216
|
+
if (!resolved) {
|
|
217
|
+
resolved = true
|
|
218
|
+
if (this.onReady) this.onReady()
|
|
219
|
+
resolve()
|
|
220
|
+
}
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
signale.warn(`dev 构建进程退出 (code: ${code}, signal: ${signal})`)
|
|
225
|
+
|
|
226
|
+
// 自动重启
|
|
227
|
+
if (!resolved && this._restartCount < this._maxRestarts) {
|
|
228
|
+
this._restartCount++
|
|
229
|
+
signale.pending(`正在重启 dev 构建 (第 ${this._restartCount} 次)...`)
|
|
230
|
+
setTimeout(() => {
|
|
231
|
+
this._startProcess(scriptName, resolve, reject)
|
|
232
|
+
}, 2000)
|
|
233
|
+
} else if (!resolved) {
|
|
234
|
+
reject(new Error(`dev 构建进程异常退出 (code: ${code})`))
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
signale.info('dev 构建进程已退出')
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// 超时检测 - 60 秒内未就绪则认为可能有问题
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
if (!resolved && !this.isReady) {
|
|
244
|
+
// 检查 dist/dev 目录是否已有产物
|
|
245
|
+
const bundle = this.detectExistingBundle()
|
|
246
|
+
if (bundle) {
|
|
247
|
+
signale.warn('未检测到构建完成信号,但发现已有构建产物,尝试使用')
|
|
248
|
+
this.isReady = true
|
|
249
|
+
resolved = true
|
|
250
|
+
resolve()
|
|
251
|
+
} else {
|
|
252
|
+
signale.warn('等待构建完成中... (可能需要较长时间)')
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}, 60000)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 检查 stdout 缓冲区是否包含编译完成的信号
|
|
260
|
+
* 用于处理编译成功但进程因非编译原因(如 adb 失败)退出的情况
|
|
261
|
+
*/
|
|
262
|
+
_checkCompilationDone(stdoutBuffer) {
|
|
263
|
+
const ANSI_REGEX = /\x1b\[[0-9;]*[a-zA-Z]/g
|
|
264
|
+
const cleanBuffer = stdoutBuffer.replace(ANSI_REGEX, '')
|
|
265
|
+
const lowerBuffer = cleanBuffer.toLowerCase()
|
|
266
|
+
return COMPILATION_DONE_SIGNALS.some((signal) => lowerBuffer.includes(signal.toLowerCase()))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 停止 dev 构建进程
|
|
271
|
+
*/
|
|
272
|
+
stop() {
|
|
273
|
+
if (this.childProcess) {
|
|
274
|
+
signale.info('正在停止 dev 构建进程...')
|
|
275
|
+
this.childProcess.kill('SIGTERM')
|
|
276
|
+
|
|
277
|
+
// 强制退出
|
|
278
|
+
setTimeout(() => {
|
|
279
|
+
if (this.childProcess) {
|
|
280
|
+
this.childProcess.kill('SIGKILL')
|
|
281
|
+
this.childProcess = null
|
|
282
|
+
}
|
|
283
|
+
}, 5000)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 检查进程是否正在运行
|
|
289
|
+
*/
|
|
290
|
+
isRunning() {
|
|
291
|
+
return this.childProcess !== null && this.childProcess.exitCode === null
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = DevBuildManager
|