@@ -6,115 +6,178 @@ const path = require('path');
66const https = require ( 'https' ) ;
77const readline = require ( 'readline' ) ;
88
9+ const colors = {
10+ reset : '\x1b[0m' ,
11+ info : '\x1b[38;2;120;220;255m' ,
12+ warn : '\x1b[38;2;255;200;87m' ,
13+ success : '\x1b[38;2;0;255;170m' ,
14+ error : '\x1b[38;2;255;120;140m' ,
15+ highlight : '\x1b[38;2;220;180;255m'
16+ } ;
17+
18+ const tags = {
19+ info : '[INFO]' ,
20+ warn : '[WARN]' ,
21+ success : '[DONE]' ,
22+ error : '[FAIL]' ,
23+ missing : '[MISSING]'
24+ } ;
25+
26+ const logger = {
27+ info ( message ) {
28+ console . log ( `${ colors . info } ${ tags . info } ${ message } ${ colors . reset } ` ) ;
29+ } ,
30+ warn ( message ) {
31+ console . warn ( `${ colors . warn } ${ tags . warn } ${ message } ${ colors . reset } ` ) ;
32+ } ,
33+ success ( message ) {
34+ console . log ( `${ colors . success } ${ tags . success } ${ message } ${ colors . reset } ` ) ;
35+ } ,
36+ error ( message ) {
37+ console . error ( `${ colors . error } ${ tags . error } ${ message } ${ colors . reset } ` ) ;
38+ } ,
39+ missing ( file , url , destination ) {
40+ console . log ( `${ colors . highlight } ${ tags . missing } ${ file } ${ colors . reset } ` ) ;
41+ console . log ( `${ colors . info } Source URL: ${ url } ${ colors . reset } ` ) ;
42+ console . log ( `${ colors . info } Save To: ${ destination } ${ colors . reset } ` ) ;
43+ }
44+ } ;
45+
46+ const promptArrow = '\x1b[38;2;0;200;255m\u2192\x1b[0m' ;
47+ const promptLabel = '\x1b[37m' ;
48+ const promptValue = '\x1b[38;2;0;255;255m' ;
49+
950const userInputReader = readline . createInterface ( {
1051 input : process . stdin ,
11- output : process . stdout ,
52+ output : process . stdout
1253} ) ;
1354
1455let port = parseInt ( process . argv [ 2 ] || '4000' , 10 ) ;
1556if ( isNaN ( port ) || port < 1 || port > 65535 ) {
16- console . error ( '端口号无效,使用默认端口 4000') ;
57+ logger . warn ( 'Invalid port number. Using default port 4000') ;
1758 port = 4000 ;
1859}
1960
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` ) ;
61+ logger . info ( `Local server running at http://127.0.0.1:${ port } ` ) ;
2562
2663function streamDownload ( url , filePath ) {
2764 return new Promise ( ( resolve , reject ) => {
2865 const fileStream = fs . createWriteStream ( filePath ) ;
2966 const request = https . get ( url , ( response ) => {
3067 if ( response . statusCode !== 200 ) {
3168 fs . unlink ( filePath , ( ) => { } ) ;
32- reject ( new Error ( `Request failed, status code: ${ response . statusCode } ` ) ) ;
69+ reject ( new Error ( `HTTP ${ response . statusCode } ` ) ) ;
3370 return ;
3471 }
3572 response . pipe ( fileStream ) ;
3673 } ) ;
74+
3775 fileStream . on ( 'finish' , ( ) => {
3876 fileStream . close ( ( ) => {
39- const stats = fs . statSync ( filePath ) ;
40- if ( stats . size === 0 ) {
41- fs . unlink ( filePath , ( ) => { } ) ;
42- reject ( new Error ( 'Downloaded file is empty' ) ) ;
43- } else {
44- resolve ( stats . size ) ;
77+ try {
78+ const stats = fs . statSync ( filePath ) ;
79+ if ( stats . size === 0 ) {
80+ fs . unlink ( filePath , ( ) => { } ) ;
81+ reject ( new Error ( 'received empty file' ) ) ;
82+ } else {
83+ resolve ( stats . size ) ;
84+ }
85+ } catch ( err ) {
86+ reject ( new Error ( `stat error: ${ err . message } ` ) ) ;
4587 }
4688 } ) ;
4789 } ) ;
90+
4891 request . on ( 'error' , ( err ) => {
4992 fs . unlink ( filePath , ( ) => { } ) ;
50- reject ( new Error ( `Request error: ${ err . message } ` ) ) ;
93+ reject ( new Error ( `request error: ${ err . message } ` ) ) ;
5194 } ) ;
95+
5296 fileStream . on ( 'error' , ( err ) => {
5397 fs . unlink ( filePath , ( ) => { } ) ;
54- reject ( new Error ( `File stream error: ${ err . message } ` ) ) ;
98+ reject ( new Error ( `file stream error: ${ err . message } ` ) ) ;
5599 } ) ;
56100 } ) ;
57101}
58102
59103async function downloadFileWithRetries ( url , filePath , maxRetries = 3 ) {
60104 fs . mkdirSync ( path . dirname ( filePath ) , { recursive : true } ) ;
61105 if ( fs . existsSync ( filePath ) ) {
62- console . log ( `文件已存在,跳过下载: ${ filePath } `) ;
106+ logger . info ( `Skip download, file already exists → ${ filePath } `) ;
63107 return ;
64108 }
109+
65110 for ( let attempt = 1 ; attempt <= maxRetries ; attempt ++ ) {
66111 try {
67112 const fileSize = await streamDownload ( url , filePath ) ;
68- console . log ( `下载完成: ${ filePath } (${ fileSize } bytes)`) ;
113+ logger . success ( `Downloaded ${ filePath } (${ fileSize } bytes)`) ;
69114 return ;
70115 } catch ( error ) {
71- console . log ( `第 ${ attempt } /${ maxRetries } 次尝试失败: ${ filePath } : ${ error . message } ` ) ;
72- if ( attempt === maxRetries ) {
73- console . error ( `重试 ${ maxRetries } 次后,下载失败: ${ filePath } ` ) ;
116+ if ( attempt < maxRetries ) {
117+ logger . warn ( `Retry ${ attempt } /${ maxRetries } for ${ filePath } — ${ error . message } (${ url } )` ) ;
118+ } else {
119+ logger . error ( `Download failed for ${ filePath } from ${ url } after ${ maxRetries } retries: ${ error . message } ` ) ;
74120 }
75121 }
76122 }
77123}
78124
79125userInputReader . question ( `${ promptArrow } ${ promptLabel } 请输入资源所在的根 URL: \x1b[0m` , ( baseUrl ) => {
80- console . log ( ` ${ promptArrow } ${ promptLabel } 根 URL 已设置为: ${ promptValue } ${ baseUrl } \x1b[0m `) ;
126+ logger . info ( ` 根 URL 已设置为: ${ baseUrl } `) ;
81127 userInputReader . close ( ) ;
82128
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' ) ;
129+ const divider = '─' . repeat ( 56 ) ;
130+ const introLines = [
131+ '监控已启动' ,
132+ `下一步: 打开 http://127.0.0.1:${ port } ` ,
133+ `缺失的资源会自动从 ${ baseUrl } 拉取`
134+ ] ;
135+
136+ console . log ( ) ;
137+ logger . info ( divider ) ;
138+ introLines . forEach ( ( line ) => logger . info ( line ) ) ;
139+ logger . info ( divider ) ;
140+ console . log ( ) ;
88141
89142 const httpServerProcess = exec ( `npx http-server -p ${ port } ` ) ;
90143
91144 httpServerProcess . stdout . on ( 'data' , async ( data ) => {
92145 const output = data . toString ( ) ;
93146 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 ) ;
147+ const matches = [ ...output . matchAll ( missingFileRegex ) ] ;
148+
149+ if ( matches . length === 0 ) {
150+ return ;
151+ }
152+
153+ const ignoreList = [
154+ '/.well-known/appspecific/com.chrome.devtools.json'
155+ ] ;
156+
157+ for ( const match of matches ) {
158+ const encodedPath = match [ 1 ] . replace ( / " / g , '' ) ;
159+ const decodedPath = decodeURIComponent ( encodedPath ) ;
160+ if ( ignoreList . includes ( decodedPath ) ) {
161+ continue ;
109162 }
163+
164+ const localFilePath = path . join ( process . cwd ( ) , decodedPath ) ;
165+ const remoteFileUrl = `${ baseUrl } ${ encodedPath } ` ;
166+
167+ logger . missing ( decodedPath , remoteFileUrl , localFilePath ) ;
168+ await downloadFileWithRetries ( remoteFileUrl , localFilePath ) ;
110169 }
111170 } ) ;
112171
113172 httpServerProcess . stderr . on ( 'data' , ( data ) => {
114- console . error ( 'http-server 错误:' , data . toString ( ) ) ;
173+ logger . error ( `http-server stderr: ${ data . toString ( ) . trim ( ) } ` ) ;
174+ } ) ;
175+
176+ httpServerProcess . on ( 'error' , ( err ) => {
177+ logger . error ( `Unable to start http-server: ${ err . message } ` ) ;
115178 } ) ;
116179
117180 httpServerProcess . on ( 'close' , ( code ) => {
118- console . log ( `http-server 进程已退出,退出码: ${ code } ` ) ;
181+ logger . info ( `http-server process exited with code ${ code } ` ) ;
119182 } ) ;
120183} ) ;
0 commit comments