@quicktvui/web-cli 1.0.0-beta.2 → 1.0.0-beta.21

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,253 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * QuickTVUI Web Dev CLI
5
+ * 启动 web 开发服务器,无需手动配置入口文件
6
+ */
7
+
8
+ const path = require('path')
9
+ const fs = require('fs')
10
+ const signale = require('signale')
11
+ const minimist = require('minimist')
12
+ const { exec } = require('shelljs')
13
+
14
+ // 解析命令行参数
15
+ const args = minimist(process.argv.slice(2), {
16
+ string: ['port', 'config'],
17
+ boolean: ['help', 'open'],
18
+ alias: {
19
+ p: 'port',
20
+ c: 'config',
21
+ h: 'help',
22
+ o: 'open',
23
+ },
24
+ default: {
25
+ port: 39001,
26
+ open: true,
27
+ },
28
+ })
29
+
30
+ // 显示帮助信息
31
+ if (args.help) {
32
+ console.log(`
33
+ QuickTVUI Web CLI
34
+
35
+ Usage:
36
+ qt-web-cli [options]
37
+
38
+ Options:
39
+ -p, --port <port> 开发服务器端口 (默认: 39001)
40
+ -c, --config <path> 自定义 webpack 配置文件路径
41
+ -o, --open 自动打开浏览器
42
+ -h, --help 显示帮助信息
43
+
44
+ Examples:
45
+ qt-web-cli
46
+ qt-web-cli --port 8080
47
+ qt-web-cli --config ./my.webpack.js
48
+
49
+ 特点:
50
+ - 自动检测项目入口 (main-native.ts/js, main.ts/js)
51
+ - 零配置启动 web 开发服务器
52
+ `)
53
+ process.exit(0)
54
+ }
55
+
56
+ async function main() {
57
+ signale.pending('正在启动 QuickTVUI Web 开发服务器...')
58
+
59
+ // 查找项目根目录
60
+ const projectRoot = findProjectRoot()
61
+ if (!projectRoot) {
62
+ signale.error('无法找到项目根目录')
63
+ process.exit(1)
64
+ }
65
+
66
+ signale.info(`项目根目录: ${projectRoot}`)
67
+
68
+ // 读取 package.json
69
+ let pkg = {}
70
+ try {
71
+ pkg = require(path.join(projectRoot, 'package.json'))
72
+ } catch (e) {}
73
+
74
+ // 检测入口文件
75
+ let mainEntry
76
+ let entryType
77
+
78
+ // 入口候选列表
79
+ const entryCandidates = [
80
+ { name: 'src/main-native.ts', path: './src/main-native.ts', type: 'package' },
81
+ { name: 'src/main-native.js', path: './src/main-native.js', type: 'package' },
82
+ { name: 'src/main.ts', path: './src/main.ts', type: 'package' },
83
+ { name: 'src/main.js', path: './src/main.js', type: 'package' },
84
+ { name: 'main (package.json)', path: pkg.main, type: 'package' },
85
+ ]
86
+
87
+ for (const candidate of entryCandidates) {
88
+ if (!candidate.path) continue
89
+ const entryFullPath = path.resolve(projectRoot, candidate.path)
90
+ if (fs.existsSync(entryFullPath)) {
91
+ mainEntry = candidate.path
92
+ entryType = candidate.type
93
+ signale.info(`主入口: ${mainEntry}`)
94
+ break
95
+ }
96
+ }
97
+
98
+ if (!mainEntry) {
99
+ signale.error('无法找到有效的入口文件,请检查以下路径:')
100
+ signale.error(' - src/main-native.ts 或 src/main-native.js')
101
+ signale.error(' - src/main.ts 或 src/main.js')
102
+ signale.error(' - package.json 中的 main 字段')
103
+ process.exit(1)
104
+ }
105
+
106
+ const mainEntryPath = path.resolve(projectRoot, mainEntry)
107
+
108
+ // 获取 webpack 配置
109
+ let webpackConfigPath
110
+ if (args.config) {
111
+ webpackConfigPath = path.resolve(projectRoot, args.config)
112
+ } else {
113
+ webpackConfigPath = path.resolve(__dirname, '../lib/webpack.config.js')
114
+ }
115
+
116
+ // 设置环境变量
117
+ process.env.NODE_ENV = 'development'
118
+ process.env.QUICKTVUI_PROJECT_ROOT = projectRoot
119
+ process.env.QUICKTVUI_MAIN_ENTRY = mainEntryPath
120
+ process.env.QUICKTVUI_ENTRY_TYPE = entryType
121
+ process.env.QUICKTVUI_PORT = String(args.port)
122
+
123
+ // 检测 OpenSSL 版本
124
+ const needLegacyProvider = checkOpenSSLVersion(process.versions.openssl)
125
+ let nodeOptions = ''
126
+ if (needLegacyProvider) {
127
+ nodeOptions = '--openssl-legacy-provider'
128
+ signale.warn(`检测到 OpenSSL ${process.versions.openssl},已添加 --openssl-legacy-provider`)
129
+ }
130
+
131
+ // 构建 webpack 命令
132
+ const webpackArgs = ['serve', `--config "${webpackConfigPath}"`, `--port ${args.port}`, '--color']
133
+
134
+ const envPrefix = nodeOptions ? `NODE_OPTIONS="${nodeOptions}" ` : ''
135
+ const webpackCmd = `${envPrefix}npx webpack ${webpackArgs.join(' ')}`
136
+
137
+ signale.pending(`执行: ${webpackCmd}`)
138
+
139
+ // 如果需要打开浏览器,等待服务器就绪后打开
140
+ if (args.open) {
141
+ const url = `http://localhost:${args.port}`
142
+ waitForServer(url, 30000)
143
+ .then(() => {
144
+ openOrRefreshBrowser(url)
145
+ })
146
+ .catch((err) => {
147
+ signale.warn(`自动打开浏览器失败: ${err.message}`)
148
+ })
149
+ }
150
+
151
+ const result = exec(webpackCmd, { stdio: 'inherit' })
152
+ if (result.code !== 0) {
153
+ signale.error('启动开发服务器失败')
154
+ process.exit(1)
155
+ }
156
+ }
157
+
158
+ /**
159
+ * 等待服务器就绪
160
+ */
161
+ function waitForServer(url, timeout = 30000) {
162
+ const http = require('http')
163
+ const startTime = Date.now()
164
+
165
+ return new Promise((resolve, reject) => {
166
+ function check() {
167
+ if (Date.now() - startTime > timeout) {
168
+ reject(new Error('等待服务器超时'))
169
+ return
170
+ }
171
+
172
+ const req = http.get(url, (res) => {
173
+ if (res.statusCode === 200 || res.statusCode === 304) {
174
+ resolve()
175
+ } else {
176
+ setTimeout(check, 500)
177
+ }
178
+ })
179
+
180
+ req.on('error', () => {
181
+ setTimeout(check, 500)
182
+ })
183
+
184
+ req.setTimeout(1000, () => {
185
+ req.destroy()
186
+ setTimeout(check, 500)
187
+ })
188
+ }
189
+
190
+ check()
191
+ })
192
+ }
193
+
194
+ /**
195
+ * 打开或刷新浏览器(支持 macOS)
196
+ */
197
+ function openOrRefreshBrowser(url) {
198
+ const platform = process.platform
199
+
200
+ if (platform === 'darwin') {
201
+ // macOS: 使用 AppleScript 检查并刷新已存在的标签页
202
+ const appleScript = `
203
+ tell application "Safari"
204
+ activate
205
+ set found to false
206
+ repeat with w in windows
207
+ repeat with t in tabs of w
208
+ if URL of t starts with "http://localhost:" then
209
+ set URL of t to "${url}"
210
+ set found to true
211
+ exit repeat
212
+ end if
213
+ end repeat
214
+ if found then exit repeat
215
+ end repeat
216
+ if not found then
217
+ open location "${url}"
218
+ end if
219
+ end tell
220
+ `
221
+
222
+ exec(`osascript -e '${appleScript.replace(/\n/g, ' ')}'`, { silent: true })
223
+ signale.success('已打开/刷新浏览器')
224
+ } else {
225
+ // 其他平台:直接打开 URL
226
+ const openCmd = platform === 'win32' ? 'start' : 'xdg-open'
227
+ exec(`${openCmd} "${url}"`, { silent: true })
228
+ signale.success('已打开浏览器')
229
+ }
230
+ }
231
+
232
+ function findProjectRoot(startDir = process.cwd()) {
233
+ let currentDir = startDir
234
+ while (currentDir !== path.dirname(currentDir)) {
235
+ if (fs.existsSync(path.join(currentDir, 'package.json'))) {
236
+ return currentDir
237
+ }
238
+ currentDir = path.dirname(currentDir)
239
+ }
240
+ return null
241
+ }
242
+
243
+ function checkOpenSSLVersion(version) {
244
+ if (!version) return false
245
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version)
246
+ if (!match) return false
247
+ return parseInt(match[1], 10) >= 3
248
+ }
249
+
250
+ main().catch((err) => {
251
+ signale.error('启动失败:', err)
252
+ process.exit(1)
253
+ })
@@ -1,16 +1,65 @@
1
- // @quicktvui/web-cli - web entry (package)
2
- // 此文件由 CLI 自动加载,使用 @quicktvui/web-renderer
3
- // 主入口路径通过 __QUICKTVUI_MAIN_ENTRY__ 注入
1
+ // @quicktvui/web-cli - web entry
2
+ // 通过 @quicktvui/web-renderer 导入所有依赖
3
+ // 动态加载主入口,确保初始化顺序正确
4
4
 
5
5
  console.log('[Web Renderer] === Starting initialization ===')
6
6
 
7
- import { initWebRenderer, startWebRenderer } from '@quicktvui/web-renderer'
7
+ import {
8
+ initAsyncLocalStorage,
9
+ initAutoProxy,
10
+ setupSceneBuilder,
11
+ createWebEngine,
12
+ applyAllPatches,
13
+ TVFocusManager,
14
+ startWebEngine,
15
+ APP_NAME,
16
+ IJKPlayerComponent,
17
+ } from '@quicktvui/web-renderer'
8
18
 
9
- initWebRenderer()
19
+ // 初始化 async localStorage
20
+ initAsyncLocalStorage()
10
21
 
22
+ // 初始化自动代理
23
+ initAutoProxy()
24
+
25
+ // 注册 IJKPlayerComponent
26
+ global.__WEB_COMPONENTS__ = global.__WEB_COMPONENTS__ || {}
27
+ global.__WEB_COMPONENTS__['IJKPlayerComponent'] = IJKPlayerComponent
28
+
29
+ // 设置 SceneBuilder
30
+ setupSceneBuilder()
31
+
32
+ // 创建 Web 引擎
33
+ const engine = createWebEngine()
34
+
35
+ // 应用所有补丁
36
+ applyAllPatches(engine)
37
+
38
+ // 初始化 TV 焦点管理器
39
+ global.__TV_FOCUS_MANAGER__ = new TVFocusManager()
40
+ console.log('[Web Renderer] TVFocusManager initialized')
41
+
42
+ // 注入全局 CSS
43
+ const styleEl = document.createElement('style')
44
+ styleEl.id = 'web-platform-reset'
45
+ styleEl.textContent = `
46
+ #app, #app * { align-items: flex-start; }
47
+ [style*="align-items"] { align-items: var(--align-items, center) !important; }
48
+ `
49
+ document.head.appendChild(styleEl)
50
+ console.log('[Web Renderer] Global CSS reset injected')
51
+
52
+ // 动态加载主入口,等待加载完成后再启动引擎
11
53
  console.log('[Web Renderer] Importing main module...')
12
54
  import(__QUICKTVUI_MAIN_ENTRY__)
13
-
14
- console.log('[Web Renderer] Starting engine...')
15
- startWebRenderer()
16
- console.log('[Web Renderer] === Initialization complete ===')
55
+ .then(() => {
56
+ console.log('[Web Renderer] Main module loaded')
57
+ console.log('[Web Renderer] Starting engine...')
58
+ startWebEngine(engine, APP_NAME)
59
+ setTimeout(() => {
60
+ console.log('[Web Renderer] === Initialization complete ===')
61
+ }, 1000)
62
+ })
63
+ .catch((err) => {
64
+ console.error('[Web Renderer] Failed to load main module:', err)
65
+ })
@@ -9,7 +9,6 @@ const fs = require('fs')
9
9
  // 从环境变量获取配置
10
10
  const projectRoot = process.env.QUICKTVUI_PROJECT_ROOT || process.cwd()
11
11
  const mainEntry = process.env.QUICKTVUI_MAIN_ENTRY || path.resolve(projectRoot, 'src/main.ts')
12
- const entryType = process.env.QUICKTVUI_ENTRY_TYPE || 'package'
13
12
  const port = parseInt(process.env.QUICKTVUI_PORT || '39001', 10)
14
13
 
15
14
  // 尝试加载项目的 package.json
@@ -67,6 +66,20 @@ function detectModules() {
67
66
  if (fs.existsSync(cwdNodeModules) && !modules.includes(cwdNodeModules)) {
68
67
  modules.push(cwdNodeModules)
69
68
  }
69
+ // 添加 web-cli 自身的 node_modules 路径(支持全局安装)
70
+ const cliNodeModules = path.resolve(__dirname, '../node_modules')
71
+ if (fs.existsSync(cliNodeModules) && !modules.includes(cliNodeModules)) {
72
+ modules.push(cliNodeModules)
73
+ }
74
+ // 添加全局 node_modules 路径
75
+ try {
76
+ const globalNodeModules = require('child_process')
77
+ .execSync('npm root -g', { encoding: 'utf-8' })
78
+ .trim()
79
+ if (fs.existsSync(globalNodeModules) && !modules.includes(globalNodeModules)) {
80
+ modules.push(globalNodeModules)
81
+ }
82
+ } catch (e) {}
70
83
  return modules
71
84
  }
72
85
 
@@ -87,21 +100,8 @@ function detectCSSLoader() {
87
100
 
88
101
  const cssLoader = detectCSSLoader()
89
102
 
90
- // 根据类型选择入口文件
91
- let entryFiles
92
-
93
- if (entryType === 'webmain') {
94
- // 直接使用 webMain 作为入口
95
- entryFiles = ['regenerator-runtime/runtime', mainEntry]
96
- } else if (entryType === 'local') {
97
- // 使用 entry-local.js + main 入口
98
- const entryLocalFile = path.resolve(__dirname, 'entry-local.js')
99
- entryFiles = ['regenerator-runtime/runtime', entryLocalFile, mainEntry]
100
- } else {
101
- // 使用 entry-package.js + main 入口
102
- const entryPackageFile = path.resolve(__dirname, 'entry-package.js')
103
- entryFiles = ['regenerator-runtime/runtime', entryPackageFile, mainEntry]
104
- }
103
+ // 入口文件
104
+ const entryPackageFile = path.resolve(__dirname, 'entry-package.js')
105
105
 
106
106
  module.exports = {
107
107
  mode: 'development',
@@ -110,7 +110,6 @@ module.exports = {
110
110
  devServer: {
111
111
  port,
112
112
  hot: true,
113
- liveReload: true,
114
113
  proxy: [
115
114
  {
116
115
  context: ['/proxy'],
@@ -138,26 +137,21 @@ module.exports = {
138
137
  },
139
138
  },
140
139
  ],
141
- static: {
142
- directory: path.join(projectRoot, 'public'),
143
- publicPath: '/',
144
- },
145
140
  client: {
146
141
  overlay: { errors: true, warnings: false },
147
142
  },
148
143
  },
149
144
 
150
145
  watchOptions: {
151
- aggregateTimeout: 1500,
152
- poll: 1000,
146
+ aggregateTimeout: 300,
153
147
  ignored: /node_modules/,
154
148
  },
155
149
 
156
- devtool: 'source-map',
150
+ devtool: 'eval-cheap-module-source-map',
157
151
 
158
- // 多入口配置:先加载初始化代码,再加载主入口
152
+ // 入口配置:支持 HMR
159
153
  entry: {
160
- index: entryFiles,
154
+ index: ['regenerator-runtime/runtime', entryPackageFile],
161
155
  },
162
156
 
163
157
  output: {
@@ -175,28 +169,10 @@ module.exports = {
175
169
  template: path.resolve(__dirname, '../templates/web-renderer.html'),
176
170
  title: pkg.name || 'QuickTVUI Web',
177
171
  }),
178
- // 注入主入口路径和项目根目录,以及预计算的模块路径
172
+ // 注入主入口路径和环境变量
179
173
  new (require('webpack').DefinePlugin)({
180
174
  __QUICKTVUI_PROJECT_ROOT__: JSON.stringify(projectRoot),
181
175
  __QUICKTVUI_MAIN_ENTRY__: JSON.stringify(mainEntry),
182
- // 预计算 src/web 模块路径(浏览器环境无法使用 path 模块)
183
- __QUICKTVUI_MODULE_ASYNC_LOCAL_STORAGE__: JSON.stringify(
184
- path.join(projectRoot, 'src/web/core/asyncLocalStorage')
185
- ),
186
- __QUICKTVUI_MODULE_AUTO_PROXY__: JSON.stringify(
187
- path.join(projectRoot, 'src/web/core/autoProxy')
188
- ),
189
- __QUICKTVUI_MODULE_SCENE_BUILDER__: JSON.stringify(
190
- path.join(projectRoot, 'src/web/core/SceneBuilder')
191
- ),
192
- __QUICKTVUI_MODULE_WEB_ENGINE__: JSON.stringify(path.join(projectRoot, 'src/web')),
193
- __QUICKTVUI_MODULE_PATCHES__: JSON.stringify(path.join(projectRoot, 'src/web/core/patches')),
194
- __QUICKTVUI_MODULE_TV_FOCUS_MANAGER__: JSON.stringify(
195
- path.join(projectRoot, 'src/web/core/TVFocusManager')
196
- ),
197
- __QUICKTVUI_MODULE_PAGE_LIFECYCLE__: JSON.stringify(
198
- path.join(projectRoot, 'src/web/core/pageLifecycle')
199
- ),
200
176
  process: JSON.stringify({
201
177
  env: { NODE_ENV: 'development' },
202
178
  browser: true,
@@ -216,7 +192,10 @@ module.exports = {
216
192
  use: [
217
193
  {
218
194
  loader: 'vue-loader',
219
- options: { compilerOptions: { whitespace: 'condense' } },
195
+ options: {
196
+ compilerOptions: { whitespace: 'condense' },
197
+ hotReload: true,
198
+ },
220
199
  },
221
200
  'scope-loader',
222
201
  ],
@@ -296,11 +275,6 @@ module.exports = {
296
275
 
297
276
  performance: { hints: false },
298
277
 
299
- // 忽略 entry-local.js 中动态 require 的警告(这是预期行为)
278
+ // 忽略动态 require 的警告
300
279
  ignoreWarnings: [/Critical dependency: the request of a dependency is an expression/],
301
-
302
- cache: {
303
- type: 'filesystem',
304
- buildDependencies: { config: [__filename] },
305
- },
306
280
  }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@quicktvui/web-cli",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.21",
4
4
  "description": "CLI tool for QuickTVUI web development - zero configuration",
5
5
  "author": "QuickTVUI Team",
6
6
  "license": "Apache-2.0",
7
7
  "bin": {
8
- "qt-web-dev": "./bin/qt-web-dev.js"
8
+ "qt-web-cli": "./bin/qt-web-cli.js"
9
9
  },
10
10
  "main": "lib/index.js",
11
11
  "files": [
@@ -23,27 +23,34 @@
23
23
  "dependencies": {
24
24
  "minimist": "^1.2.8",
25
25
  "shelljs": "^0.10.0",
26
- "signale": "^1.4.0"
27
- },
28
- "peerDependencies": {
29
- "webpack": "^5.0.0",
30
- "webpack-cli": "^5.0.0",
31
- "webpack-dev-server": "^4.0.0",
26
+ "signale": "^1.4.0",
27
+ "@quicktvui/web-renderer": "^1.0.6",
28
+ "scope-loader": "^1.0.3",
29
+ "webpack": "^5.89.0",
30
+ "webpack-cli": "^5.1.0",
31
+ "webpack-dev-server": "^4.15.0",
32
32
  "vue-loader": "^17.0.0",
33
- "html-webpack-plugin": "^5.0.0",
34
- "@babel/core": "^7.0.0",
35
- "babel-loader": "^9.0.0",
33
+ "html-webpack-plugin": "^5.5.0",
34
+ "@babel/core": "^7.23.0",
35
+ "babel-loader": "^9.1.0",
36
36
  "ts-loader": "^9.4.0",
37
- "css-loader": "^6.0.0",
38
- "@extscreen/es3-router": ">=3.0.1"
37
+ "regenerator-runtime": "^0.14.0"
39
38
  },
40
39
  "peerDependenciesMeta": {
41
- "less-loader": { "optional": true },
42
- "sass-loader": { "optional": true },
43
- "sass": { "optional": true },
44
- "less": { "optional": true }
40
+ "less-loader": {
41
+ "optional": true
42
+ },
43
+ "sass-loader": {
44
+ "optional": true
45
+ },
46
+ "sass": {
47
+ "optional": true
48
+ },
49
+ "less": {
50
+ "optional": true
51
+ }
45
52
  },
46
53
  "engines": {
47
54
  "node": ">=16.0.0"
48
55
  }
49
- }
56
+ }
package/bin/qt-web-dev.js DELETED
@@ -1,160 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * QuickTVUI Web Dev CLI
5
- * 启动 web 开发服务器,无需手动配置入口文件
6
- */
7
-
8
- const path = require('path')
9
- const fs = require('fs')
10
- const signale = require('signale')
11
- const minimist = require('minimist')
12
- const { exec } = require('shelljs')
13
-
14
- // 解析命令行参数
15
- const args = minimist(process.argv.slice(2), {
16
- string: ['port', 'config'],
17
- boolean: ['help', 'open'],
18
- alias: {
19
- p: 'port',
20
- c: 'config',
21
- h: 'help',
22
- o: 'open',
23
- },
24
- default: {
25
- port: 39001,
26
- open: false,
27
- },
28
- })
29
-
30
- // 显示帮助信息
31
- if (args.help) {
32
- console.log(`
33
- QuickTVUI Web Dev CLI
34
-
35
- Usage:
36
- qt-web-dev [options]
37
-
38
- Options:
39
- -p, --port <port> 开发服务器端口 (默认: 39001)
40
- -c, --config <path> 自定义 webpack 配置文件路径
41
- -o, --open 自动打开浏览器
42
- -h, --help 显示帮助信息
43
-
44
- Examples:
45
- qt-web-dev
46
- qt-web-dev --port 8080
47
- qt-web-dev --config ./my.webpack.js
48
-
49
- 特点:
50
- - 自动检测项目入口,优先使用 webMain
51
- - 若无 webMain,自动使用 src/web 或 @quicktvui/web-renderer 包
52
- - 零配置启动 web 开发服务器
53
- `)
54
- process.exit(0)
55
- }
56
-
57
- async function main() {
58
- signale.pending('正在启动 QuickTVUI Web 开发服务器...')
59
-
60
- // 查找项目根目录
61
- const projectRoot = findProjectRoot()
62
- if (!projectRoot) {
63
- signale.error('无法找到项目根目录')
64
- process.exit(1)
65
- }
66
-
67
- signale.info(`项目根目录: ${projectRoot}`)
68
-
69
- // 读取 package.json
70
- let pkg = {}
71
- try {
72
- pkg = require(path.join(projectRoot, 'package.json'))
73
- } catch (e) {}
74
-
75
- // 检测入口文件
76
- let mainEntry
77
- let entryType
78
-
79
- if (pkg.webMain) {
80
- // 项目已有 webMain,直接使用
81
- mainEntry = pkg.webMain
82
- entryType = 'webmain'
83
- signale.info(`主入口: ${mainEntry} (webMain)`)
84
- } else if (fs.existsSync(path.join(projectRoot, 'src/web/index.js'))) {
85
- // 有 src/web 目录,使用 entry-local.js 包装 main
86
- mainEntry = pkg.main || './src/main.ts'
87
- entryType = 'local'
88
- signale.info(`主入口: ${mainEntry}`)
89
- signale.info(`Web 渲染器: src/web (本地)`)
90
- } else {
91
- // 使用 @quicktvui/web-renderer 包
92
- mainEntry = pkg.main || './src/main.ts'
93
- entryType = 'package'
94
- signale.info(`主入口: ${mainEntry}`)
95
- signale.info(`Web 渲染器: @quicktvui/web-renderer (包)`)
96
- }
97
-
98
- const mainEntryPath = path.resolve(projectRoot, mainEntry)
99
-
100
- // 获取 webpack 配置
101
- let webpackConfigPath
102
- if (args.config) {
103
- webpackConfigPath = path.resolve(projectRoot, args.config)
104
- } else {
105
- webpackConfigPath = path.resolve(__dirname, '../lib/webpack.config.js')
106
- }
107
-
108
- // 设置环境变量
109
- process.env.NODE_ENV = 'development'
110
- process.env.QUICKTVUI_PROJECT_ROOT = projectRoot
111
- process.env.QUICKTVUI_MAIN_ENTRY = mainEntryPath
112
- process.env.QUICKTVUI_ENTRY_TYPE = entryType
113
- process.env.QUICKTVUI_PORT = String(args.port)
114
-
115
- // 检测 OpenSSL 版本
116
- const needLegacyProvider = checkOpenSSLVersion(process.versions.openssl)
117
- let nodeOptions = ''
118
- if (needLegacyProvider) {
119
- nodeOptions = '--openssl-legacy-provider'
120
- signale.warn(`检测到 OpenSSL ${process.versions.openssl},已添加 --openssl-legacy-provider`)
121
- }
122
-
123
- // 构建 webpack 命令
124
- const webpackArgs = ['serve', `--config "${webpackConfigPath}"`, `--port ${args.port}`, '--color']
125
- if (args.open) webpackArgs.push('--open')
126
-
127
- const envPrefix = nodeOptions ? `NODE_OPTIONS="${nodeOptions}" ` : ''
128
- const webpackCmd = `${envPrefix}npx webpack ${webpackArgs.join(' ')}`
129
-
130
- signale.pending(`执行: ${webpackCmd}`)
131
-
132
- const result = exec(webpackCmd, { stdio: 'inherit' })
133
- if (result.code !== 0) {
134
- signale.error('启动开发服务器失败')
135
- process.exit(1)
136
- }
137
- }
138
-
139
- function findProjectRoot(startDir = process.cwd()) {
140
- let currentDir = startDir
141
- while (currentDir !== path.dirname(currentDir)) {
142
- if (fs.existsSync(path.join(currentDir, 'package.json'))) {
143
- return currentDir
144
- }
145
- currentDir = path.dirname(currentDir)
146
- }
147
- return null
148
- }
149
-
150
- function checkOpenSSLVersion(version) {
151
- if (!version) return false
152
- const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version)
153
- if (!match) return false
154
- return parseInt(match[1], 10) >= 3
155
- }
156
-
157
- main().catch((err) => {
158
- signale.error('启动失败:', err)
159
- process.exit(1)
160
- })