@@ -6,152 +6,115 @@ const path = require('path');
66const https = require ( 'https' ) ;
77const readline = require ( 'readline' ) ;
88
9- const rl = readline . createInterface ( {
9+ const userInputReader = readline . createInterface ( {
1010 input : process . stdin ,
1111 output : process . stdout ,
1212} ) ;
1313
14- // 获取命令行参数中的端口号
15- let port = process . argv [ 2 ] || 4000 ;
16-
17- // 验证端口号是否有效
18- port = parseInt ( port ) ;
14+ let port = parseInt ( process . argv [ 2 ] || '4000' , 10 ) ;
1915if ( isNaN ( port ) || port < 1 || port > 65535 ) {
20- console . error ( 'Invalid port number. Using default port 4000' ) ;
16+ console . error ( '端口号无效,使用默认端口 4000' ) ;
2117 port = 4000 ;
2218}
2319
24- // 颜色代码
25- const arrow = '\x1b[38;2;0;200;255m\u2192\x1b[0m' ;
26- const label = '\x1b[37m' ;
27- const value = '\x1b[38;2;0;255;255m' ;
28-
29- console . log ( `${ arrow } ${ label } Local: ${ value } http://127.0.0.1:${ port } \x1b[0m` ) ;
30-
31- rl . question ( `${ arrow } ${ label } Please enter the base URL: \x1b[0m` , ( baseUrl ) => {
32- console . log ( `${ arrow } ${ label } Base URL: ${ value } ${ baseUrl } \x1b[0m` ) ;
33- rl . close ( ) ;
34-
35- const server = exec ( `http-server -p ${ port } ` ) ;
36-
37- // 创建目录的函数
38- function ensureDirectoryExists ( filePath ) {
39- const dirname = path . dirname ( filePath ) ;
40- if ( fs . existsSync ( dirname ) ) {
41- return true ;
42- }
43- ensureDirectoryExists ( dirname ) ;
44- fs . mkdirSync ( dirname ) ;
45- }
46-
47- // 下载文件的函数
48- function downloadFile ( url , filePath , retryCount = 3 ) {
49- ensureDirectoryExists ( filePath ) ;
50-
51- if ( fs . existsSync ( filePath ) ) {
52- console . log ( `File already exists: ${ filePath } ` ) ;
53- return ;
54- }
55-
56- const file = fs . createWriteStream ( filePath ) ;
57- let fileSize = 0 ;
58-
59- let currentRetry = 0 ;
60-
61- const startDownload = ( ) => {
62- fileSize = 0 ;
63-
64- https . get ( url , ( response ) => {
65- if ( response . statusCode !== 200 ) {
66- file . close ( ) ;
20+ const promptArrow = '\x1b[38;2;0;200;255m\u2192\x1b[0m' ;
21+ const promptLabel = '\x1b[37m' ;
22+ const promptValue = '\x1b[38;2;0;255;255m' ;
23+
24+ console . log ( `${ promptArrow } ${ promptLabel } Local server running at: ${ promptValue } http://127.0.0.1:${ port } \x1b[0m` ) ;
25+
26+ function streamDownload ( url , filePath ) {
27+ return new Promise ( ( resolve , reject ) => {
28+ const fileStream = fs . createWriteStream ( filePath ) ;
29+ const request = https . get ( url , ( response ) => {
30+ if ( response . statusCode !== 200 ) {
31+ fs . unlink ( filePath , ( ) => { } ) ;
32+ reject ( new Error ( `Request failed, status code: ${ response . statusCode } ` ) ) ;
33+ return ;
34+ }
35+ response . pipe ( fileStream ) ;
36+ } ) ;
37+ fileStream . on ( 'finish' , ( ) => {
38+ fileStream . close ( ( ) => {
39+ const stats = fs . statSync ( filePath ) ;
40+ if ( stats . size === 0 ) {
6741 fs . unlink ( filePath , ( ) => { } ) ;
68- if ( currentRetry < retryCount ) {
69- currentRetry ++ ;
70- console . log ( `Retrying download (${ currentRetry } /${ retryCount } ) for ${ filePath } due to HTTP status ${ response . statusCode } ` ) ;
71- startDownload ( ) ;
72- } else {
73- console . error ( `Failed to download ${ filePath } : HTTP Status ${ response . statusCode } after ${ retryCount } retries` ) ;
74- }
75- return ;
76- }
77-
78- response . on ( 'data' , ( chunk ) => {
79- fileSize += chunk . length ;
80- } ) ;
81-
82- response . pipe ( file ) ;
83-
84- file . on ( 'finish' , ( ) => {
85- file . close ( ( ) => {
86- if ( fileSize === 0 ) {
87- fs . unlink ( filePath , ( ) => {
88- if ( currentRetry < retryCount ) {
89- currentRetry ++ ;
90- console . log ( `Retrying download (${ currentRetry } /${ retryCount } ) for ${ filePath } due to empty file` ) ;
91- startDownload ( ) ;
92- } else {
93- console . error ( `Failed to download ${ filePath } : File is empty after ${ retryCount } retries` ) ;
94- }
95- } ) ;
96- } else {
97- console . log ( `Downloaded: ${ filePath } (${ fileSize } bytes)` ) ;
98- }
99- } ) ;
100- } ) ;
101- } ) . on ( 'error' , ( err ) => {
102- file . close ( ) ;
103- fs . unlink ( filePath , ( ) => {
104- if ( currentRetry < retryCount ) {
105- currentRetry ++ ;
106- console . log ( `Retrying download (${ currentRetry } /${ retryCount } ) for ${ filePath } due to error: ${ err . message } ` ) ;
107- startDownload ( ) ;
108- } else {
109- console . error ( `Download failed for ${ filePath } after ${ retryCount } retries:` , err . message ) ;
110- }
111- } ) ;
112- } ) ;
113- } ;
114-
115- file . on ( 'error' , ( err ) => {
116- file . close ( ) ;
117- fs . unlink ( filePath , ( ) => {
118- if ( currentRetry < retryCount ) {
119- currentRetry ++ ;
120- console . log ( `Retrying download (${ currentRetry } /${ retryCount } ) for ${ filePath } due to file write error: ${ err . message } ` ) ;
121- startDownload ( ) ;
42+ reject ( new Error ( 'Downloaded file is empty' ) ) ;
12243 } else {
123- console . error ( `File write error for ${ filePath } after ${ retryCount } retries:` , err . message ) ;
44+ resolve ( stats . size ) ;
12445 }
12546 } ) ;
12647 } ) ;
48+ request . on ( 'error' , ( err ) => {
49+ fs . unlink ( filePath , ( ) => { } ) ;
50+ reject ( new Error ( `Request error: ${ err . message } ` ) ) ;
51+ } ) ;
52+ fileStream . on ( 'error' , ( err ) => {
53+ fs . unlink ( filePath , ( ) => { } ) ;
54+ reject ( new Error ( `File stream error: ${ err . message } ` ) ) ;
55+ } ) ;
56+ } ) ;
57+ }
12758
128- startDownload ( ) ;
59+ async function downloadFileWithRetries ( url , filePath , maxRetries = 3 ) {
60+ fs . mkdirSync ( path . dirname ( filePath ) , { recursive : true } ) ;
61+ if ( fs . existsSync ( filePath ) ) {
62+ console . log ( `文件已存在,跳过下载: ${ filePath } ` ) ;
63+ return ;
12964 }
65+ for ( let attempt = 1 ; attempt <= maxRetries ; attempt ++ ) {
66+ try {
67+ const fileSize = await streamDownload ( url , filePath ) ;
68+ console . log ( `下载完成: ${ filePath } (${ fileSize } bytes)` ) ;
69+ return ;
70+ } catch ( error ) {
71+ console . log ( `第 ${ attempt } /${ maxRetries } 次尝试失败: ${ filePath } : ${ error . message } ` ) ;
72+ if ( attempt === maxRetries ) {
73+ console . error ( `重试 ${ maxRetries } 次后,下载失败: ${ filePath } ` ) ;
74+ }
75+ }
76+ }
77+ }
13078
131- server . stdout . on ( 'data' , ( data ) => {
132- const output = data . toString ( ) ;
133- const regex = / " G E T ( \/ .* ?\. [ a - z A - Z 0 - 9 ] + ) " E r r o r \( 4 0 4 \) : / g;
134- const matches = [ ...output . matchAll ( regex ) ] ;
79+ userInputReader . question ( `${ promptArrow } ${ promptLabel } 请输入资源所在的根 URL: \x1b[0m` , ( baseUrl ) => {
80+ console . log ( `${ promptArrow } ${ promptLabel } 根 URL 已设置为: ${ promptValue } ${ baseUrl } \x1b[0m` ) ;
81+ userInputReader . close ( ) ;
13582
136- if ( matches . length > 0 ) {
137- matches . forEach ( ( match ) => {
138- const encodedMissingFile = match [ 1 ] . replace ( / " / g , '' ) ;
139- const decodedMissingFile = decodeURIComponent ( encodedMissingFile ) ;
140- console . log ( 'Missing file:' , decodedMissingFile ) ;
83+ console . log ( '\n--------------------------------------------------------' ) ;
84+ console . log ( ` ${ promptArrow } ${ promptLabel } 监控已启动。` ) ;
85+ console . log ( ` ${ promptArrow } ${ promptLabel } 下一步: 请用浏览器打开 ${ promptValue } http://127.0.0.1: ${ port } ${ promptLabel } ` ) ;
86+ console . log ( ` ${ promptArrow } ${ promptLabel } 脚本将会自动从 ${ promptValue } ${ baseUrl } ${ promptLabel } 下载任何缺失的资源。` ) ;
87+ console . log ( '--------------------------------------------------------\n' ) ;
14188
142- const localPath = path . join ( process . cwd ( ) , decodedMissingFile ) ;
143- const downloadUrl = `${ baseUrl } ${ encodedMissingFile } ` ;
89+ const httpServerProcess = exec ( `http-server -p ${ port } ` ) ;
14490
145- downloadFile ( downloadUrl , localPath ) ;
146- } ) ;
91+ httpServerProcess . stdout . on ( 'data' , async ( data ) => {
92+ const output = data . toString ( ) ;
93+ const missingFileRegex = / " G E T ( \/ .* ?) " E r r o r \( 4 0 4 \) : / g;
94+ const missingFileMatches = [ ...output . matchAll ( missingFileRegex ) ] ;
95+ if ( missingFileMatches . length > 0 ) {
96+ const ignoreList = [
97+ '/.well-known/appspecific/com.chrome.devtools.json' ,
98+ ] ;
99+ for ( const match of missingFileMatches ) {
100+ const encodedFilePath = match [ 1 ] . replace ( / " / g, '' ) ;
101+ const decodedFilePath = decodeURIComponent ( encodedFilePath ) ;
102+ if ( ignoreList . includes ( decodedFilePath ) ) {
103+ continue ;
104+ }
105+ console . log ( '检测到缺失文件:' , decodedFilePath ) ;
106+ const localFilePath = path . join ( process . cwd ( ) , decodedFilePath ) ;
107+ const remoteFileUrl = `${ baseUrl } ${ encodedFilePath } ` ;
108+ await downloadFileWithRetries ( remoteFileUrl , localFilePath ) ;
109+ }
147110 }
148111 } ) ;
149112
150- server . stderr . on ( 'data' , ( data ) => {
151- console . error ( 'stderr :' , data . toString ( ) ) ;
113+ httpServerProcess . stderr . on ( 'data' , ( data ) => {
114+ console . error ( 'http-server 错误 :' , data . toString ( ) ) ;
152115 } ) ;
153116
154- server . on ( 'close' , ( code ) => {
155- console . log ( `http-server process exited with code ${ code } ` ) ;
117+ httpServerProcess . on ( 'close' , ( code ) => {
118+ console . log ( `http-server 进程已退出,退出码: ${ code } ` ) ;
156119 } ) ;
157120} ) ;
0 commit comments