@@ -6,63 +6,58 @@ import open from 'open';
66import binaryVersionCheck from 'binary-version-check' ;
77import getPort from 'get-port' ;
88
9- const isServerRunning = ( hostname , port , pathname ) => new Promise ( ( resolve , reject ) => {
10- ( async ( ) => {
11- const retryDelay = 50 ;
12- const maxRetries = 20 ; // Give up after 1 second
13- let retryCount = 0 ;
14-
15- const fetchErrorDetails = async statusCode => {
16- try {
17- const response = await fetch ( `http://${ hostname } :${ port } ${ pathname } ` ) ;
18- const body = await response . text ( ) ;
19- // Extract the error message from the HTML response
20- // PHP errors are wrapped in <b> tags: "<b>Fatal error</b>: ..."
21- const errorMatch = body . match ( / < b > ( F a t a l e r r o r | P a r s e e r r o r | W a r n i n g | N o t i c e ) < \/ b > : \s * ( [ ^ < \n ] + ) / ) ;
22- if ( errorMatch ) {
23- return `Server returned ${ statusCode } error: ${ errorMatch [ 1 ] } : ${ errorMatch [ 2 ] } ` ;
24- }
25-
26- return `Server returned ${ statusCode } error. Please check your PHP application for possible errors.` ;
27- } catch {
28- return `Server returned ${ statusCode } error. Could not fetch error details.` ;
9+ const isServerRunning = async ( serverUrl , pathname ) => {
10+ const retryDelay = 50 ;
11+ const maxRetries = 20 ; // Give up after 1 second
12+ let retryCount = 0 ;
13+
14+ const fetchErrorDetails = async statusCode => {
15+ try {
16+ const response = await fetch ( `${ serverUrl } ${ pathname } ` ) ;
17+ const body = await response . text ( ) ;
18+ // Extract the error message from the HTML response
19+ // PHP errors are wrapped in <b> tags: "<b>Fatal error</b>: ..."
20+ const errorMatch = body . match ( / < b > ( F a t a l e r r o r | P a r s e e r r o r | W a r n i n g | N o t i c e ) < \/ b > : \s * ( [ ^ < \n ] + ) / ) ;
21+ if ( errorMatch ) {
22+ return `Server returned ${ statusCode } error: ${ errorMatch [ 1 ] } : ${ errorMatch [ 2 ] } ` ;
2923 }
30- } ;
31-
32- const checkServer = async ( ) => {
33- try {
34- const response = await fetch ( `http://${ hostname } :${ port } ${ pathname } ` , {
35- method : 'HEAD' ,
36- } ) ;
37-
38- const statusCodeType = Number . parseInt ( response . status . toString ( ) [ 0 ] , 10 ) ;
39- if ( [ 2 , 3 , 4 ] . includes ( statusCodeType ) ) {
40- resolve ( ) ;
41- return ;
42- }
43-
44- if ( statusCodeType === 5 ) {
45- const errorMessage = await fetchErrorDetails ( response . status ) ;
46- reject ( new Error ( errorMessage ) ) ;
47- return ;
48- }
49-
50- await setTimeout ( retryDelay ) ;
51- await checkServer ( ) ;
52- } catch ( error ) {
53- if ( ++ retryCount > maxRetries ) {
54- reject ( new Error ( `Could not start the PHP server: ${ error . message } ` ) ) ;
55- return ;
56- }
57-
58- await setTimeout ( retryDelay ) ;
59- await checkServer ( ) ;
24+
25+ return `Server returned ${ statusCode } error. Please check your PHP application for possible errors.` ;
26+ } catch {
27+ return `Server returned ${ statusCode } error. Could not fetch error details.` ;
28+ }
29+ } ;
30+
31+ const checkServer = async ( ) => {
32+ try {
33+ const response = await fetch ( `${ serverUrl } ${ pathname } ` , {
34+ method : 'HEAD' ,
35+ } ) ;
36+
37+ const statusCodeType = Math . trunc ( response . status / 100 ) ;
38+ if ( [ 2 , 3 , 4 ] . includes ( statusCodeType ) ) {
39+ return ;
40+ }
41+
42+ if ( statusCodeType === 5 ) {
43+ const errorMessage = await fetchErrorDetails ( response . status ) ;
44+ throw new Error ( errorMessage ) ;
6045 }
61- } ;
6246
63- await checkServer ( ) ;
64- } ) ( ) ;
65- } ) ;
47+ await setTimeout ( retryDelay ) ;
48+ await checkServer ( ) ;
49+ } catch ( error ) {
50+ if ( ++ retryCount > maxRetries ) {
51+ throw new Error ( `Could not start the PHP server: ${ error . message } ` ) ;
52+ }
53+
54+ await setTimeout ( retryDelay ) ;
55+ await checkServer ( ) ;
56+ }
57+ } ;
58+
59+ await checkServer ( ) ;
60+ } ;
6661
6762export default async function phpServer ( options ) {
6863 options = {
@@ -125,22 +120,28 @@ export default async function phpServer(options) {
125120
126121 subprocess . ref ( ) ;
127122
128- process . on ( 'exit' , ( ) => {
123+ // Clean up subprocess when the parent process exits
124+ const exitHandler = ( ) => {
129125 subprocess . kill ( ) ;
130- } ) ;
126+ } ;
127+
128+ process . once ( 'exit' , exitHandler ) ;
129+
130+ // Remove the exit handler when the server is manually stopped
131+ const originalStop = ( ) => {
132+ process . off ( 'exit' , exitHandler ) ;
133+ subprocess . kill ( ) ;
134+ } ;
131135
132136 let pathname = '/' ;
133137 let openUrl = url ;
134138
135139 if ( typeof options . open === 'string' ) {
136140 // Check if it's an absolute URL
137141 try {
138- // eslint-disable-next-line no-new
139- new URL ( options . open ) ;
142+ const parsedUrl = new URL ( options . open ) ;
140143 // It's an absolute URL, use it as-is
141144 openUrl = options . open ;
142- // Extract pathname for server check
143- const parsedUrl = new URL ( options . open ) ;
144145 pathname = parsedUrl . pathname ;
145146 } catch {
146147 // It's a relative path, append to base URL
@@ -152,7 +153,7 @@ export default async function phpServer(options) {
152153 // Check when the server is ready. Tried doing it by listening
153154 // to the child process `data` event, but it's not triggered...
154155 try {
155- await isServerRunning ( options . hostname , options . port , pathname ) ;
156+ await isServerRunning ( url , pathname ) ;
156157 } catch ( error ) {
157158 subprocess . kill ( ) ;
158159 throw error ;
@@ -166,8 +167,6 @@ export default async function phpServer(options) {
166167 stdout : subprocess . stdout ,
167168 stderr : subprocess . stderr ,
168169 url,
169- stop ( ) {
170- subprocess . kill ( ) ;
171- } ,
170+ stop : originalStop ,
172171 } ;
173172}
0 commit comments