66 */
77
88import * as fs from 'node:fs'
9+ import { createServer } from 'node:http'
910import * as path from 'node:path'
1011import * as process from 'node:process'
11- import { USB_IDS_SOURCE } from './config'
12+ import { UI_LOCAL_BASE_URL , USB_IDS_SOURCE } from './config'
1213import { fetchUsbIdsData , loadVersionInfo , saveUsbIdsToFile , saveVersionInfo } from './core'
1314import { shouldUpdate } from './parser'
1415import { logger } from './utils'
@@ -135,6 +136,112 @@ export function checkUpdate(): void {
135136 }
136137}
137138
139+ /**
140+ * 启动静态web服务器
141+ */
142+ export async function startWebServer ( port = 3000 ) : Promise < void > {
143+ try {
144+ const root = process . cwd ( )
145+ const distDir = path . join ( root , 'dist' , 'ui' )
146+
147+ // 检查dist/ui目录是否存在
148+ if ( ! fs . existsSync ( distDir ) ) {
149+ logger . error ( 'dist/ui目录不存在,请先运行构建命令: pnpm run build:app' )
150+ process . exit ( 1 )
151+ }
152+
153+ // 创建HTTP服务器
154+ const server = createServer ( ( req , res ) => {
155+ console . log ( 'req url' , req . url )
156+ // 重定向根路径到UI_LOCAL_BASE_URL
157+ if ( req . url === '/' ) {
158+ res . writeHead ( 302 , {
159+ Location : UI_LOCAL_BASE_URL ,
160+ } )
161+ res . end ( )
162+ return
163+ }
164+
165+ let filePath = path . join ( distDir , req . url === UI_LOCAL_BASE_URL
166+ ? 'index.html'
167+ : req . url ?. replace ( UI_LOCAL_BASE_URL , '' ) || '' )
168+
169+ console . log ( 'file path' , filePath )
170+
171+ // 安全检查,防止路径遍历攻击
172+ if ( ! filePath . startsWith ( distDir ) ) {
173+ res . writeHead ( 403 )
174+ res . end ( 'Forbidden' )
175+ return
176+ }
177+
178+ // 处理usb.ids.json和usb.ids.version.json
179+ if ( filePath . includes ( 'usb.ids.json' ) || filePath . includes ( 'usb.ids.version.json' ) ) {
180+ // usb.ids.json和usb.ids.version.json与在dist同级目录
181+ filePath = path . join ( root , req . url ! . replace ( UI_LOCAL_BASE_URL , '' ) )
182+ console . log ( 'json file path' , filePath )
183+ }
184+
185+ // 如果文件不存在,返回index.html(用于SPA路由)
186+ if ( ! fs . existsSync ( filePath ) ) {
187+ filePath = path . join ( distDir , 'index.html' )
188+ }
189+
190+ // 读取文件
191+ fs . readFile ( filePath , ( err , data ) => {
192+ if ( err ) {
193+ res . writeHead ( 404 )
194+ res . end ( 'Not Found' )
195+ return
196+ }
197+
198+ // 设置Content-Type
199+ const ext = path . extname ( filePath )
200+ const contentType = {
201+ '.html' : 'text/html' ,
202+ '.js' : 'application/javascript' ,
203+ '.css' : 'text/css' ,
204+ '.json' : 'application/json' ,
205+ '.png' : 'image/png' ,
206+ '.jpg' : 'image/jpeg' ,
207+ '.gif' : 'image/gif' ,
208+ '.svg' : 'image/svg+xml' ,
209+ '.ico' : 'image/x-icon' ,
210+ } [ ext ] || 'text/plain'
211+
212+ res . writeHead ( 200 , { 'Content-Type' : contentType } )
213+ res . end ( data )
214+ } )
215+ } )
216+
217+ // 启动服务器
218+ server . listen ( port , ( ) => {
219+ logger . success ( `usb.ids Web UI服务器已启动!` )
220+ logger . info ( `访问地址: http://localhost:${ port } ${ UI_LOCAL_BASE_URL } ` )
221+ logger . info ( '按 Control+C 停止服务器' )
222+ } )
223+
224+ // 保持服务器运行,直到手动停止
225+ return new Promise < void > ( ( resolve , reject ) => {
226+ // 监听服务器错误
227+ server . on ( 'error' , ( error ) => {
228+ logger . error ( `服务器错误: ${ error . message } ` )
229+ reject ( error )
230+ } )
231+
232+ // 服务器关闭时resolve Promise
233+ server . on ( 'close' , ( ) => {
234+ logger . success ( '服务器已停止' )
235+ resolve ( )
236+ } )
237+ } )
238+ }
239+ catch ( error ) {
240+ logger . error ( `启动web服务器失败: ${ ( error as Error ) . message } ` )
241+ process . exit ( 1 )
242+ }
243+ }
244+
138245/**
139246 * 显示帮助信息
140247 */
@@ -150,16 +257,20 @@ USB设备数据管理工具
150257 update, fetch 更新USB设备数据
151258 version, info 显示当前版本信息
152259 check 检查是否需要更新
260+ ui 启动web界面服务器
153261 help 显示此帮助信息
154262
155263选项:
156264 --force 强制更新(忽略时间检查)
265+ --port <port> 指定web服务器端口(默认3000)
157266
158267示例:
159268 usb-ids update
160269 usb-ids update --force
161270 usb-ids version
162271 usb-ids check
272+ usb-ids ui
273+ usb-ids ui --port 8080
163274` )
164275}
165276
@@ -185,6 +296,23 @@ export async function runCli(): Promise<void> {
185296 checkUpdate ( )
186297 break
187298
299+ case 'ui' : {
300+ // 解析端口参数
301+ const portIndex = args . indexOf ( '--port' )
302+ let port = 3000
303+ if ( portIndex !== - 1 && args [ portIndex + 1 ] ) {
304+ const parsedPort = Number . parseInt ( args [ portIndex + 1 ] , 10 )
305+ if ( ! Number . isNaN ( parsedPort ) && parsedPort > 0 && parsedPort < 65536 ) {
306+ port = parsedPort
307+ }
308+ else {
309+ logger . error ( '无效的端口号,使用默认端口3000' )
310+ }
311+ }
312+ await startWebServer ( port )
313+ break
314+ }
315+
188316 case 'help' :
189317 case '--help' :
190318 case '-h' :
0 commit comments