@quicktvui/web-cli 1.0.0-beta.39 → 1.0.0-beta.40

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.
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * QuickTVUI Web CLI Watcher
5
+ * 文件监听器 - 监听文件变化并重启开发服务器
6
+ */
7
+
8
+ const path = require('path')
9
+ const fs = require('fs')
10
+ const { spawn } = require('child_process')
11
+ const signale = require('signale')
12
+
13
+ // 查找项目根目录
14
+ function findProjectRoot(startDir = process.cwd()) {
15
+ let currentDir = startDir
16
+ while (currentDir !== path.dirname(currentDir)) {
17
+ if (fs.existsSync(path.join(currentDir, 'package.json'))) {
18
+ return currentDir
19
+ }
20
+ currentDir = path.dirname(currentDir)
21
+ }
22
+ return null
23
+ }
24
+
25
+ const projectRoot = findProjectRoot()
26
+ if (!projectRoot) {
27
+ signale.error('无法找到项目根目录')
28
+ process.exit(1)
29
+ }
30
+
31
+ let childProcess = null
32
+ let isRestarting = false
33
+ let isFirstStart = true
34
+
35
+ function startDevServer() {
36
+ const cliPath = path.join(__dirname, 'qt-web-cli.js')
37
+ const args = [
38
+ cliPath,
39
+ ...process.argv.slice(2).filter((arg) => arg !== '--watch' && arg !== '-w'),
40
+ ]
41
+
42
+ signale.info(`启动开发服务器: node ${args.join(' ')}`)
43
+
44
+ // 只在第一次启动时打开浏览器
45
+ const env = { ...process.env }
46
+ if (!isFirstStart) {
47
+ env.QUICKTVUI_NO_OPEN = 'true'
48
+ }
49
+ isFirstStart = false
50
+
51
+ childProcess = spawn('node', args, {
52
+ cwd: projectRoot,
53
+ stdio: 'inherit',
54
+ env,
55
+ })
56
+
57
+ childProcess.on('error', (err) => {
58
+ signale.error(`启动失败: ${err.message}`)
59
+ process.exit(1)
60
+ })
61
+
62
+ childProcess.on('exit', (code, signal) => {
63
+ if (!isRestarting && code !== 0) {
64
+ signale.error(`开发服务器异常退出 (code: ${code}, signal: ${signal})`)
65
+ process.exit(code || 1)
66
+ }
67
+ })
68
+ }
69
+
70
+ function stopDevServer() {
71
+ return new Promise((resolve) => {
72
+ if (childProcess) {
73
+ signale.info('正在停止开发服务器...')
74
+
75
+ let resolved = false
76
+
77
+ childProcess.on('exit', () => {
78
+ if (!resolved) {
79
+ resolved = true
80
+ signale.success('开发服务器已停止')
81
+ resolve()
82
+ }
83
+ })
84
+
85
+ // 先发送 SIGTERM 信号优雅退出
86
+ childProcess.kill('SIGTERM')
87
+
88
+ // 如果 10 秒后还没退出,强制杀死
89
+ setTimeout(() => {
90
+ if (childProcess && !resolved) {
91
+ signale.warn('服务器未能在 10 秒内退出,强制终止...')
92
+ childProcess.kill('SIGKILL')
93
+ }
94
+ }, 10000)
95
+ } else {
96
+ resolve()
97
+ }
98
+ })
99
+ }
100
+
101
+ async function restartDevServer() {
102
+ if (isRestarting) return
103
+ isRestarting = true
104
+
105
+ signale.pending('检测到文件变化,正在重启开发服务器...')
106
+
107
+ // 清理 dist 目录
108
+ const distPath = path.join(projectRoot, 'dist/web')
109
+ if (fs.existsSync(distPath)) {
110
+ try {
111
+ const rimraf = require('rimraf')
112
+ rimraf.sync(distPath)
113
+ signale.info('已清理 dist/web 目录')
114
+ } catch (e) {
115
+ signale.warn('清理 dist 目录失败:', e.message)
116
+ }
117
+ }
118
+
119
+ await stopDevServer()
120
+
121
+ // 等待 3 秒确保端口完全释放
122
+ signale.info('等待端口释放...')
123
+ await new Promise((resolve) => setTimeout(resolve, 3000))
124
+
125
+ // 重新启动
126
+ startDevServer()
127
+
128
+ // 等待 5 秒后再允许下一次重启
129
+ setTimeout(() => {
130
+ isRestarting = false
131
+ }, 5000)
132
+ }
133
+
134
+ function setupWatcher() {
135
+ const chokidar = require('chokidar')
136
+
137
+ // 监听的目录
138
+ const watchPaths = [
139
+ path.join(projectRoot, 'src'),
140
+ path.join(projectRoot, 'packages/quicktvui'),
141
+ path.join(projectRoot, 'packages/component'),
142
+ ]
143
+
144
+ signale.info(`监听目录: ${watchPaths.map((p) => path.relative(projectRoot, p)).join(', ')}`)
145
+
146
+ const watcher = chokidar.watch(watchPaths, {
147
+ ignored: [/node_modules/, /\.git/, /dist/, /\.DS_Store/, /\.log$/, /\?.*$/, /\.map$/],
148
+ persistent: true,
149
+ ignoreInitial: true,
150
+ usePolling: true,
151
+ interval: 100,
152
+ })
153
+
154
+ watcher
155
+ .on('ready', () => {
156
+ signale.success('文件监听器已就绪')
157
+ })
158
+ .on('change', (filePath) => {
159
+ const relativePath = path.relative(projectRoot, filePath)
160
+ signale.info(`文件已修改: ${relativePath}`)
161
+ restartDevServer()
162
+ })
163
+ .on('add', (filePath) => {
164
+ const relativePath = path.relative(projectRoot, filePath)
165
+ signale.info(`文件已添加: ${relativePath}`)
166
+ restartDevServer()
167
+ })
168
+ .on('unlink', (filePath) => {
169
+ const relativePath = path.relative(projectRoot, filePath)
170
+ signale.info(`文件已删除: ${relativePath}`)
171
+ restartDevServer()
172
+ })
173
+ .on('error', (error) => {
174
+ signale.error(`文件监听错误: ${error}`)
175
+ })
176
+
177
+ signale.success('文件监听已启动 (--watch 模式)')
178
+
179
+ // 处理退出信号
180
+ process.on('SIGINT', async () => {
181
+ signale.info('正在关闭...')
182
+ await stopDevServer()
183
+ watcher.close()
184
+ process.exit(0)
185
+ })
186
+
187
+ process.on('SIGTERM', async () => {
188
+ await stopDevServer()
189
+ watcher.close()
190
+ process.exit(0)
191
+ })
192
+ }
193
+
194
+ // 主流程
195
+ signale.pending('正在启动 QuickTVUI Web 开发服务器 (watch 模式)...')
196
+ signale.info(`项目根目录: ${projectRoot}`)
197
+
198
+ // 启动开发服务器
199
+ startDevServer()
200
+
201
+ // 设置文件监听
202
+ setupWatcher()
package/bin/qt-web-cli.js CHANGED
@@ -14,16 +14,18 @@ const { exec } = require('shelljs')
14
14
  // 解析命令行参数
15
15
  const args = minimist(process.argv.slice(2), {
16
16
  string: ['port', 'config'],
17
- boolean: ['help', 'open', 'v', 'info', 'version'],
17
+ boolean: ['help', 'open', 'v', 'info', 'version', 'watch'],
18
18
  alias: {
19
19
  p: 'port',
20
20
  c: 'config',
21
21
  h: 'help',
22
22
  o: 'open',
23
+ w: 'watch',
23
24
  },
24
25
  default: {
25
26
  port: 39001,
26
- open: true,
27
+ open: process.env.QUICKTVUI_NO_OPEN !== 'true',
28
+ watch: false,
27
29
  },
28
30
  })
29
31
 
@@ -77,6 +79,7 @@ Options:
77
79
  -p, --port <port> 开发服务器端口 (默认: 39001)
78
80
  -c, --config <path> 自定义 webpack 配置文件路径
79
81
  -o, --open 自动打开浏览器
82
+ -w, --watch 监听文件变化自动重启服务器
80
83
  -h, --help 显示帮助信息
81
84
  -v, --version 显示 CLI 版本
82
85
  --info 显示 CLI 和 web-renderer 版本
@@ -85,17 +88,38 @@ Examples:
85
88
  qt-web-cli
86
89
  qt-web-cli --port 8080
87
90
  qt-web-cli --config ./my.webpack.js
91
+ qt-web-cli --watch
88
92
  qt-web-cli -v
89
93
  qt-web-cli --info
90
94
 
91
95
  特点:
92
96
  - 自动检测项目入口 (main-native.ts/js, main.ts/js)
93
97
  - 零配置启动 web 开发服务器
98
+ - 支持文件变化自动重启 (--watch)
94
99
  `)
95
100
  process.exit(0)
96
101
  }
97
102
 
98
103
  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
+ })
115
+
116
+ watcher.on('exit', (code) => {
117
+ process.exit(code || 0)
118
+ })
119
+
120
+ return
121
+ }
122
+
99
123
  signale.pending('正在启动 QuickTVUI Web 开发服务器...')
100
124
 
101
125
  // 查找项目根目录
@@ -119,9 +143,6 @@ async function main() {
119
143
 
120
144
  // 入口候选列表
121
145
  const entryCandidates = [
122
- { name: 'webMain (package.json)', path: pkg.webMain, type: 'web' },
123
- { name: 'src/main-web.ts', path: './src/main-web.ts', type: 'web' },
124
- { name: 'src/main-web.js', path: './src/main-web.js', type: 'web' },
125
146
  { name: 'src/main-native.ts', path: './src/main-native.ts', type: 'package' },
126
147
  { name: 'src/main-native.js', path: './src/main-native.js', type: 'package' },
127
148
  { name: 'src/main.ts', path: './src/main.ts', type: 'package' },
@@ -142,8 +163,6 @@ async function main() {
142
163
 
143
164
  if (!mainEntry) {
144
165
  signale.error('无法找到有效的入口文件,请检查以下路径:')
145
- signale.error(' - package.json 中的 webMain 字段')
146
- signale.error(' - src/main-web.ts 或 src/main-web.js')
147
166
  signale.error(' - src/main-native.ts 或 src/main-native.js')
148
167
  signale.error(' - src/main.ts 或 src/main.js')
149
168
  signale.error(' - package.json 中的 main 字段')
@@ -257,10 +276,6 @@ function waitForServer(url, timeout = 30000) {
257
276
  */
258
277
  function openBrowser(url) {
259
278
  const platform = process.platform
260
- if (platform === 'darwin' && focusExistingBrowserPage(url)) {
261
- signale.success('已切换到已打开的浏览器页面')
262
- return
263
- }
264
279
  const command =
265
280
  platform === 'darwin'
266
281
  ? `open "${url}"`
@@ -277,57 +292,6 @@ function openBrowser(url) {
277
292
  signale.warn(`自动打开浏览器失败,请手动访问: ${url}`)
278
293
  }
279
294
 
280
- function focusExistingBrowserPage(url) {
281
- const match = /^https?:\/\/[^/]+/.exec(url)
282
- const targetPrefix = match ? match[0] : url
283
- const browserApps = ['Google Chrome', 'Safari', 'Arc', 'Microsoft Edge', 'Brave Browser']
284
- const script = `
285
- set targetPrefix to "${escapeAppleScriptString(targetPrefix)}"
286
- set browserApps to {${browserApps.map((app) => `"${escapeAppleScriptString(app)}"`).join(', ')}}
287
- repeat with appName in browserApps
288
- tell application "System Events"
289
- set isRunning to (name of processes) contains (appName as text)
290
- end tell
291
- if isRunning then
292
- if (appName as text) is "Safari" then
293
- tell application "Safari"
294
- repeat with w in windows
295
- repeat with t in tabs of w
296
- if URL of t starts with targetPrefix then
297
- set current tab of w to t
298
- set index of w to 1
299
- activate
300
- return "FOUND"
301
- end if
302
- end repeat
303
- end repeat
304
- end tell
305
- else
306
- tell application (appName as text)
307
- repeat with w in windows
308
- repeat with t in tabs of w
309
- if URL of t starts with targetPrefix then
310
- set active tab index of w to (index of t)
311
- set index of w to 1
312
- activate
313
- return "FOUND"
314
- end if
315
- end repeat
316
- end repeat
317
- end tell
318
- end if
319
- end if
320
- end repeat
321
- return "NOT_FOUND"
322
- `.trim()
323
- const result = exec(`osascript <<'APPLESCRIPT'\n${script}\nAPPLESCRIPT`, { silent: true })
324
- return result.code === 0 && String(result.stdout || '').includes('FOUND')
325
- }
326
-
327
- function escapeAppleScriptString(value) {
328
- return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')
329
- }
330
-
331
295
  function findProjectRoot(startDir = process.cwd()) {
332
296
  let currentDir = startDir
333
297
  while (currentDir !== path.dirname(currentDir)) {
@@ -24,9 +24,6 @@ try {
24
24
  function detectAlias() {
25
25
  const alias = {
26
26
  '@': path.resolve(projectRoot, 'src'),
27
- // 使用本地打包的 es3-router
28
- '@extscreen/es3-router':
29
- '/Volumes/WD/Users/chendd/Documents/huan/es/es-vue3/packages/ESRouter/dist/index.js',
30
27
  }
31
28
 
32
29
  const jsconfigPath = path.join(projectRoot, 'jsconfig.json')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quicktvui/web-cli",
3
- "version": "1.0.0-beta.39",
3
+ "version": "1.0.0-beta.40",
4
4
  "description": "CLI tool for QuickTVUI web development - zero configuration",
5
5
  "author": "QuickTVUI Team",
6
6
  "license": "Apache-2.0",
@@ -34,7 +34,9 @@
34
34
  "@babel/core": "^7.23.0",
35
35
  "babel-loader": "^9.1.0",
36
36
  "ts-loader": "^9.4.0",
37
- "regenerator-runtime": "^0.14.0"
37
+ "regenerator-runtime": "^0.14.0",
38
+ "chokidar": "^3.5.3",
39
+ "rimraf": "^5.0.0"
38
40
  },
39
41
  "peerDependenciesMeta": {
40
42
  "less-loader": {
@@ -53,4 +55,4 @@
53
55
  "engines": {
54
56
  "node": ">=16.0.0"
55
57
  }
56
- }
58
+ }