@quicktvui/web-cli 1.0.8 → 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/bin/qt-web-cli.js CHANGED
@@ -1,8 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * QuickTVUI Web Dev CLI
5
- * 启动 web 开发服务器,无需手动配置入口文件
4
+ * QuickTVUI Web CLI v2
5
+ *
6
+ * v2 架构:委托构建 + Bundle 加载模式
7
+ * - 调用项目的 dev 脚本构建
8
+ * - 监听 dist/dev 目录
9
+ * - 启动轻量静态服务器加载 dist/dev 的 bundle
10
+ *
11
+ * v1 回退:当项目没有 dev 脚本时,使用原有的 webpack 自构建模式
6
12
  */
7
13
 
8
14
  const path = require('path')
@@ -13,8 +19,8 @@ const { exec } = require('shelljs')
13
19
 
14
20
  // 解析命令行参数
15
21
  const args = minimist(process.argv.slice(2), {
16
- string: ['port', 'config'],
17
- boolean: ['help', 'open', 'v', 'info', 'version', 'watch'],
22
+ string: ['port', 'config', 'devScript', 'distDir'],
23
+ boolean: ['help', 'open', 'v', 'info', 'version', 'watch', 'noAutoBuild', 'v1'],
18
24
  alias: {
19
25
  p: 'port',
20
26
  c: 'config',
@@ -26,6 +32,8 @@ const args = minimist(process.argv.slice(2), {
26
32
  port: 39001,
27
33
  open: process.env.QUICKTVUI_NO_OPEN !== 'true',
28
34
  watch: false,
35
+ noAutoBuild: false,
36
+ v1: false,
29
37
  },
30
38
  })
31
39
 
@@ -36,12 +44,12 @@ if (args.v || args.version) {
36
44
  process.exit(0)
37
45
  }
38
46
 
39
- // 显示详细信息(包括 web-renderer 版本)
47
+ // 显示详细信息
40
48
  if (args.info) {
41
49
  const cliPkg = require(path.join(__dirname, '../package.json'))
42
50
  console.log(`@quicktvui/web-cli v${cliPkg.version}`)
51
+ console.log(` Mode: v2 (delegate build)`)
43
52
  try {
44
- // 尝试多种路径查找 web-renderer
45
53
  let rendererPkg
46
54
  const possiblePaths = [
47
55
  '@quicktvui/web-renderer/package.json',
@@ -52,9 +60,7 @@ if (args.info) {
52
60
  try {
53
61
  rendererPkg = require(pkgPath)
54
62
  break
55
- } catch (e) {
56
- // 继续尝试下一个路径
57
- }
63
+ } catch (e) {}
58
64
  }
59
65
  if (rendererPkg) {
60
66
  console.log(`@quicktvui/web-renderer v${rendererPkg.version}`)
@@ -70,57 +76,270 @@ if (args.info) {
70
76
  // 显示帮助信息
71
77
  if (args.help) {
72
78
  console.log(`
73
- QuickTVUI Web CLI
79
+ QuickTVUI Web CLI v2
74
80
 
75
81
  Usage:
76
82
  qt-web-cli [options]
77
83
 
78
84
  Options:
79
- -p, --port <port> 开发服务器端口 (默认: 39001)
80
- -c, --config <path> 自定义 webpack 配置文件路径
81
- -o, --open 自动打开浏览器
82
- -w, --watch 监听文件变化自动重启服务器
83
- -h, --help 显示帮助信息
84
- -v, --version 显示 CLI 版本
85
- --info 显示 CLI web-renderer 版本
85
+ -p, --port <port> 开发服务器端口 (默认: 39001)
86
+ -o, --open 自动打开浏览器
87
+ -h, --help 显示帮助信息
88
+ -v, --version 显示 CLI 版本
89
+ --info 显示 CLI 和 web-renderer 版本
90
+ --dev-script <name> 指定要运行的 npm script (默认: dev)
91
+ --dist-dir <path> 指定构建产物目录 (默认: dist/dev)
92
+ --no-auto-build 跳过自动调用 dev 脚本,仅启动服务器
93
+ --v1 使用 v1 模式 (webpack 自构建,已废弃)
94
+
95
+ v2 模式 (默认):
96
+ 自动检测项目的 dev 脚本,调用 npm run dev 构建产物
97
+ 监听 dist/dev/ 目录变化,通过 web-runtime 加载 bundle
98
+ 支持热更新 (SSE)
99
+
100
+ v1 模式 (--v1):
101
+ 使用内置 webpack 配置自行构建项目 (已废弃)
86
102
 
87
103
  Examples:
88
- qt-web-cli
89
- qt-web-cli --port 8080
90
- qt-web-cli --config ./my.webpack.js
91
- qt-web-cli --watch
92
- qt-web-cli -v
93
- qt-web-cli --info
94
-
95
- 特点:
96
- - 自动检测项目入口 (main-native.ts/js, main.ts/js)
97
- - 零配置启动 web 开发服务器
98
- - 支持文件变化自动重启 (--watch)
104
+ qt-web-cli # v2 模式,自动构建+启动
105
+ qt-web-cli --no-auto-build # v2 模式,仅启动服务器(需先手动 npm run dev)
106
+ qt-web-cli --dev-script build # 使用 build 脚本替代 dev
107
+ qt-web-cli --port 8080 # 指定端口
108
+ qt-web-cli --v1 # 使用 v1 模式
99
109
  `)
100
110
  process.exit(0)
101
111
  }
102
112
 
113
+ // ============================================================================
114
+ // 主入口
115
+ // ============================================================================
116
+
103
117
  async function main() {
104
- // 如果启用了 watch 模式,使用 watcher 脚本
105
- if (args.watch) {
106
- const { spawn } = require('child_process')
107
- const watcherPath = path.join(__dirname, 'qt-web-cli-watch.js')
108
- const watcherArgs = process.argv.slice(2).filter((arg) => arg !== '--watch' && arg !== '-w')
109
-
110
- const watcher = spawn('node', [watcherPath, ...watcherArgs], {
111
- cwd: process.cwd(),
112
- stdio: 'inherit',
113
- env: process.env,
114
- })
118
+ // 强制使用 v1 模式
119
+ if (args.v1) {
120
+ signale.warn('v1 模式已废弃,建议迁移到 v2 模式')
121
+ return runV1()
122
+ }
123
+
124
+ signale.pending('正在启动 QuickTVUI Web CLI v2 ...')
125
+
126
+ // 查找项目根目录
127
+ const projectRoot = findProjectRoot()
128
+ if (!projectRoot) {
129
+ signale.error('无法找到项目根目录')
130
+ process.exit(1)
131
+ }
132
+
133
+ signale.info(`项目根目录: ${projectRoot}`)
134
+
135
+ // 读取 package.json
136
+ let pkg = {}
137
+ try {
138
+ pkg = require(path.join(projectRoot, 'package.json'))
139
+ } catch (e) {}
140
+
141
+ // 检测是否支持 v2 模式
142
+ const devScript = detectDevScript(pkg, args.devScript)
143
+ const distDir = args.distDir || 'dist/dev'
144
+ const distFullPath = path.join(projectRoot, distDir)
145
+
146
+ if (!devScript && !fs.existsSync(distFullPath)) {
147
+ signale.warn('项目未配置 dev 脚本且 dist/dev 目录不存在,回退到 v1 模式')
148
+ return runV1()
149
+ }
150
+
151
+ // ============================================================================
152
+ // v2 模式
153
+ // ============================================================================
154
+
155
+ const DevBuildManager = require('../lib/DevBuildManager')
156
+ const BundleWatcher = require('../lib/BundleWatcher')
157
+ const DevServer = require('../lib/DevServer')
158
+ const HotReloader = require('../lib/HotReloader')
159
+
160
+ // 初始化各模块
161
+ const hotReloader = new HotReloader()
162
+ const bundleWatcher = new BundleWatcher(projectRoot, {
163
+ onChange: (event) => {
164
+ if (event.isBundle) {
165
+ // 通知浏览器刷新
166
+ hotReloader.notifyBundleUpdate({
167
+ file: event.file,
168
+ bundleEntry: event.bundleEntry,
169
+ bundleUrlPath: event.bundleUrlPath,
170
+ })
171
+ }
172
+ },
173
+ })
174
+
175
+ const devServer = new DevServer(projectRoot, {
176
+ port: args.port,
177
+ hotReloader,
178
+ bundleUrlPath: `/dist/dev/index.bundle`,
179
+ })
180
+
181
+ // 记录是否已打开浏览器(避免重复打开)
182
+ let browserOpened = false
183
+
184
+ const devBuild = new DevBuildManager(projectRoot, {
185
+ pkg,
186
+ devScript: args.devScript || 'dev',
187
+ onOutput: (text, stream) => {
188
+ // 将构建输出透传到控制台
189
+ if (stream === 'stdout') {
190
+ process.stdout.write(text)
191
+ } else {
192
+ process.stderr.write(text)
193
+ }
194
+ },
195
+ onReady: () => {
196
+ // 构建完成后,通知浏览器
197
+ hotReloader.notifyBuildStatus('ready', '构建完成')
198
+
199
+ // 更新 bundle URL
200
+ const bundleUrlPath = bundleWatcher.getBundleUrlPath()
201
+ if (bundleUrlPath) {
202
+ devServer.setBundleUrlPath(bundleUrlPath)
203
+ }
204
+
205
+ // 构建就绪后打开浏览器(只打开一次)
206
+ if (args.open && !browserOpened) {
207
+ browserOpened = true
208
+ const urlPath = bundleUrlPath || '/dist/dev/index.bundle'
209
+ const url = `http://localhost:${args.port}?bundle=${urlPath}`
210
+ openBrowser(url)
211
+ }
212
+ },
213
+ })
214
+
215
+ // 检测已有构建产物
216
+ const existingBundle = devBuild.detectExistingBundle()
217
+ const shouldAutoBuild = !args.noAutoBuild && devScript
218
+
219
+ if (existingBundle) {
220
+ signale.success(
221
+ `检测到已有构建产物: ${distDir}/${existingBundle.entry} (${formatSize(existingBundle.size)})`
222
+ )
223
+
224
+ // 更新 bundle URL
225
+ devServer.setBundleUrlPath(`/dist/dev/${existingBundle.entry}`)
226
+ } else if (shouldAutoBuild) {
227
+ signale.info('未检测到构建产物,将自动启动 dev 构建')
228
+ } else {
229
+ signale.warn('未检测到构建产物,且未启用自动构建')
230
+ signale.warn('请先手动运行: npm run dev')
231
+ }
232
+
233
+ // 启动文件监听
234
+ bundleWatcher.start()
235
+
236
+ // ============================================================================
237
+ // 端口预检测
238
+ // ============================================================================
239
+ const net = require('net')
240
+
241
+ // 检测 web-cli 端口(39001)
242
+ const webCliPortAvailable = await checkPortAvailable(net, args.port)
243
+ if (!webCliPortAvailable) {
244
+ const processInfo = getPortProcessInfo(args.port)
245
+ const pidInfo = processInfo
246
+ ? processInfo.name
247
+ ? `PID: ${processInfo.pid} (${processInfo.name})`
248
+ : `PID: ${processInfo.pid}`
249
+ : '无法获取进程信息'
250
+ signale.warn(`Web CLI 端口 ${args.port} 已被占用: ${pidInfo}`)
251
+
252
+ const killed = await promptKillPort(args.port, processInfo)
253
+ if (!killed) {
254
+ signale.error(`端口 ${args.port} 已被占用,请使用 --port 指定其他端口`)
255
+ process.exit(1)
256
+ }
257
+ }
258
+
259
+ // 检测 debug-server 端口(38989)— 仅在使用 qt-dev 的项目中提示
260
+ // Vue3 项目使用 qt-dev,会启动 debug-server 监听 38989
261
+ // Vue2 项目使用纯 webpack,不涉及 38989 端口
262
+ const isQtDevProject =
263
+ devScript &&
264
+ (devScript.script.includes('qt-dev') ||
265
+ devScript.script.includes('es3-debug-server') ||
266
+ devScript.script.includes('hippy-debug-server'))
267
+ if (isQtDevProject) {
268
+ const debugServerPort = 38989
269
+ const debugPortAvailable = await checkPortAvailable(net, debugServerPort)
270
+ if (!debugPortAvailable) {
271
+ const processInfo = getPortProcessInfo(debugServerPort)
272
+ const pidInfo = processInfo
273
+ ? processInfo.name
274
+ ? `PID: ${processInfo.pid} (${processInfo.name})`
275
+ : `PID: ${processInfo.pid}`
276
+ : '无法获取进程信息'
277
+ signale.warn(`Debug Server 端口 ${debugServerPort} 已被占用: ${pidInfo}`)
278
+ signale.warn('如遇 dev 启动失败,请先关闭占用该端口的进程')
279
+ }
280
+ }
115
281
 
116
- watcher.on('exit', (code) => {
117
- process.exit(code || 0)
282
+ // 先启动开发服务器(确保浏览器打开时服务器已就绪)
283
+ try {
284
+ await devServer.start()
285
+ signale.success(`开发服务器已启动: http://localhost:${args.port}`)
286
+
287
+ // 构建状态通知
288
+ hotReloader.notifyBuildStatus(
289
+ existingBundle ? 'ready' : 'building',
290
+ existingBundle ? 'Bundle 已就绪' : '等待构建完成...'
291
+ )
292
+
293
+ // 仅在不需要自动构建且已有产物时立即打开浏览器
294
+ // 否则等 dev 构建完成后再打开(在 onReady 回调中)
295
+ if (args.open && existingBundle && !shouldAutoBuild && !browserOpened) {
296
+ browserOpened = true
297
+ const bundleUrlPath = bundleWatcher.getBundleUrlPath() || '/dist/dev/index.bundle'
298
+ const url = `http://localhost:${args.port}?bundle=${bundleUrlPath}`
299
+ openBrowser(url)
300
+ }
301
+ } catch (err) {
302
+ signale.error('开发服务器启动失败:', err.message)
303
+ process.exit(1)
304
+ }
305
+
306
+ // 启动 dev 构建(服务器已就绪,构建完成后在 onReady 回调中打开浏览器)
307
+ if (shouldAutoBuild && !existingBundle) {
308
+ try {
309
+ await devBuild.start()
310
+ } catch (err) {
311
+ signale.error('dev 构建启动失败:', err.message)
312
+ signale.warn('你可以手动运行 npm run dev,然后使用 --no-auto-build 重新启动 CLI')
313
+ }
314
+ } else if (shouldAutoBuild && existingBundle) {
315
+ // 已有构建产物,在后台启动 dev 构建
316
+ // 等待 webpack 编译完成后再打开浏览器(而不是立即打开)
317
+ signale.info('已有构建产物,后台启动 dev 构建用于热更新...')
318
+ devBuild.start().catch((err) => {
319
+ signale.warn('后台 dev 构建启动失败:', err.message)
118
320
  })
321
+ }
119
322
 
120
- return
323
+ // 优雅退出
324
+ const cleanup = () => {
325
+ signale.info('正在停止...')
326
+ devBuild.stop()
327
+ bundleWatcher.stop()
328
+ devServer.stop()
329
+ signale.success('已停止')
330
+ process.exit(0)
121
331
  }
122
332
 
123
- signale.pending('正在启动 QuickTVUI Web 开发服务器...')
333
+ process.on('SIGINT', cleanup)
334
+ process.on('SIGTERM', cleanup)
335
+ }
336
+
337
+ // ============================================================================
338
+ // v1 模式 (webpack 自构建,已废弃)
339
+ // ============================================================================
340
+
341
+ function runV1() {
342
+ signale.pending('正在启动 QuickTVUI Web CLI v1 (webpack 模式)...')
124
343
 
125
344
  // 查找项目根目录
126
345
  const projectRoot = findProjectRoot()
@@ -141,7 +360,6 @@ async function main() {
141
360
  let mainEntry
142
361
  let entryType
143
362
 
144
- // 入口候选列表
145
363
  const entryCandidates = [
146
364
  { name: 'src/main-native.ts', path: './src/main-native.ts', type: 'package' },
147
365
  { name: 'src/main-native.js', path: './src/main-native.js', type: 'package' },
@@ -162,10 +380,7 @@ async function main() {
162
380
  }
163
381
 
164
382
  if (!mainEntry) {
165
- signale.error('无法找到有效的入口文件,请检查以下路径:')
166
- signale.error(' - src/main-native.ts 或 src/main-native.js')
167
- signale.error(' - src/main.ts 或 src/main.js')
168
- signale.error(' - package.json 中的 main 字段')
383
+ signale.error('无法找到有效的入口文件')
169
384
  process.exit(1)
170
385
  }
171
386
 
@@ -194,17 +409,15 @@ async function main() {
194
409
  }
195
410
 
196
411
  /**
197
- * 使用 webpack Node API 启动开发服务器
412
+ * 使用 webpack Node API 启动开发服务器 (v1)
198
413
  */
199
414
  function startDevServer(configPath, port, shouldOpen, userConfigPath) {
200
415
  const webpack = require('webpack')
201
416
  const WebpackDevServer = require('webpack-dev-server')
202
417
  const { merge } = require('webpack-merge')
203
418
 
204
- // 加载内置配置
205
419
  let config = require(configPath)
206
420
 
207
- // 如果用户指定了自定义配置,进行合并
208
421
  if (userConfigPath) {
209
422
  if (!fs.existsSync(userConfigPath)) {
210
423
  signale.error(`自定义配置文件不存在: ${userConfigPath}`)
@@ -230,8 +443,7 @@ function startDevServer(configPath, port, shouldOpen, userConfigPath) {
230
443
  signale.success(`开发服务器已启动: http://localhost:${port}`)
231
444
 
232
445
  if (shouldOpen) {
233
- const url = `http://localhost:${port}`
234
- openBrowser(url)
446
+ openBrowser(`http://localhost:${port}`)
235
447
  }
236
448
  })
237
449
 
@@ -243,45 +455,45 @@ function startDevServer(configPath, port, shouldOpen, userConfigPath) {
243
455
  })
244
456
  }
245
457
 
246
- /**
247
- * 等待服务器就绪
248
- */
249
- function waitForServer(url, timeout = 30000) {
250
- const http = require('http')
251
- const startTime = Date.now()
252
-
253
- return new Promise((resolve, reject) => {
254
- function check() {
255
- if (Date.now() - startTime > timeout) {
256
- reject(new Error('等待服务器超时'))
257
- return
258
- }
458
+ // ============================================================================
459
+ // 工具函数
460
+ // ============================================================================
259
461
 
260
- const req = http.get(url, (res) => {
261
- if (res.statusCode === 200 || res.statusCode === 304) {
262
- resolve()
263
- } else {
264
- setTimeout(check, 500)
265
- }
266
- })
267
-
268
- req.on('error', () => {
269
- setTimeout(check, 500)
270
- })
462
+ function findProjectRoot(startDir = process.cwd()) {
463
+ let currentDir = startDir
464
+ while (currentDir !== path.dirname(currentDir)) {
465
+ if (fs.existsSync(path.join(currentDir, 'package.json'))) {
466
+ return currentDir
467
+ }
468
+ currentDir = path.dirname(currentDir)
469
+ }
470
+ return null
471
+ }
271
472
 
272
- req.setTimeout(1000, () => {
273
- req.destroy()
274
- setTimeout(check, 500)
275
- })
473
+ function detectDevScript(pkg, scriptName) {
474
+ const scripts = pkg.scripts || {}
475
+ const candidates = [scriptName, 'dev', 'dev:android', 'build:dev']
476
+ for (const name of candidates) {
477
+ if (name && scripts[name]) {
478
+ return { name, script: scripts[name] }
276
479
  }
480
+ }
481
+ return null
482
+ }
277
483
 
278
- check()
279
- })
484
+ function checkOpenSSLVersion(version) {
485
+ if (!version) return false
486
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version)
487
+ if (!match) return false
488
+ return parseInt(match[1], 10) >= 3
489
+ }
490
+
491
+ function formatSize(bytes) {
492
+ if (bytes < 1024) return bytes + ' B'
493
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
494
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
280
495
  }
281
496
 
282
- /**
283
- * 打开浏览器
284
- */
285
497
  function openBrowser(url) {
286
498
  const platform = process.platform
287
499
  const command =
@@ -294,30 +506,119 @@ function openBrowser(url) {
294
506
 
295
507
  if (result.code === 0) {
296
508
  signale.success('已打开浏览器')
297
- return
509
+ } else {
510
+ signale.warn(`自动打开浏览器失败,请手动访问: ${url}`)
298
511
  }
512
+ }
299
513
 
300
- signale.warn(`自动打开浏览器失败,请手动访问: ${url}`)
514
+ /**
515
+ * 检测端口是否可用(通过尝试绑定)
516
+ */
517
+ function checkPortAvailable(net, port) {
518
+ return new Promise((resolve) => {
519
+ const tester = net.createServer()
520
+ tester.once('error', (err) => {
521
+ if (err.code === 'EADDRINUSE') {
522
+ resolve(false)
523
+ } else {
524
+ resolve(false)
525
+ }
526
+ })
527
+ tester.once('listening', () => {
528
+ tester.close()
529
+ resolve(true)
530
+ })
531
+ tester.listen(port)
532
+ })
301
533
  }
302
534
 
303
- function findProjectRoot(startDir = process.cwd()) {
304
- let currentDir = startDir
305
- while (currentDir !== path.dirname(currentDir)) {
306
- if (fs.existsSync(path.join(currentDir, 'package.json'))) {
307
- return currentDir
535
+ /**
536
+ * 获取占用端口的进程信息
537
+ */
538
+ function getPortProcessInfo(port) {
539
+ try {
540
+ const { execSync } = require('child_process')
541
+ const isWindows = process.platform === 'win32'
542
+ let cmd
543
+ if (isWindows) {
544
+ cmd = `netstat -ano | findstr :${port} | findstr LISTENING`
545
+ } else {
546
+ cmd = `lsof -i :${port} -P -n -t 2>/dev/null`
308
547
  }
309
- currentDir = path.dirname(currentDir)
548
+ const result = execSync(cmd, { encoding: 'utf-8', timeout: 5000 }).trim()
549
+ if (!result) return null
550
+
551
+ if (isWindows) {
552
+ const match = result.match(/LISTENING\s+(\d+)/)
553
+ return match ? { pid: match[1], name: '' } : null
554
+ } else {
555
+ const pid = result.split('\n')[0].trim()
556
+ if (!pid) return null
557
+ let name = ''
558
+ try {
559
+ name = execSync(`ps -p ${pid} -o comm= 2>/dev/null`, { encoding: 'utf-8' }).trim()
560
+ } catch (e) {
561
+ /* ignore */
562
+ }
563
+ return { pid, name: name || 'unknown' }
564
+ }
565
+ } catch (e) {
566
+ return null
310
567
  }
311
- return null
312
568
  }
313
569
 
314
- function checkOpenSSLVersion(version) {
315
- if (!version) return false
316
- const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version)
317
- if (!match) return false
318
- return parseInt(match[1], 10) >= 3
570
+ /**
571
+ * 提示用户是否杀掉占用端口的进程
572
+ */
573
+ function promptKillPort(port, processInfo) {
574
+ return new Promise((resolve) => {
575
+ const readline = require('readline')
576
+ const rl = readline.createInterface({
577
+ input: process.stdin,
578
+ output: process.stdout,
579
+ })
580
+
581
+ const timeout = setTimeout(() => {
582
+ rl.close()
583
+ resolve(false)
584
+ }, 30000)
585
+
586
+ rl.question(` 是否杀掉该进程并继续? [y/N] `, (answer) => {
587
+ clearTimeout(timeout)
588
+ rl.close()
589
+ const confirmed = (answer || '').trim().toLowerCase() === 'y'
590
+
591
+ if (confirmed) {
592
+ if (!processInfo || !processInfo.pid) {
593
+ signale.error('无法获取占用端口的进程 PID,请手动查找并关闭')
594
+ resolve(false)
595
+ return
596
+ }
597
+ try {
598
+ const { execSync } = require('child_process')
599
+ const isWindows = process.platform === 'win32'
600
+ if (isWindows) {
601
+ execSync(`taskkill /PID ${processInfo.pid} /F`, { encoding: 'utf-8' })
602
+ } else {
603
+ execSync(`kill -9 ${processInfo.pid}`, { encoding: 'utf-8' })
604
+ }
605
+ signale.success(`已杀掉进程 ${processInfo.pid}`)
606
+ setTimeout(() => resolve(true), 500)
607
+ } catch (e) {
608
+ signale.error(`杀掉进程失败: ${e.message}`)
609
+ resolve(false)
610
+ }
611
+ } else {
612
+ resolve(false)
613
+ }
614
+ })
615
+ })
319
616
  }
320
617
 
618
+ // ============================================================================
619
+ // 启动
620
+ // ============================================================================
621
+
321
622
  main().catch((err) => {
322
623
  signale.error('启动失败:', err)
323
624
  process.exit(1)