Skip to content

Commit 27b0c64

Browse files
committed
Refactor
1 parent 2d6ac87 commit 27b0c64

File tree

6 files changed

+155
-171
lines changed

6 files changed

+155
-171
lines changed

index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export type Options = {
6262
/**
6363
The path to the PHP binary.
6464
65+
Can be useful if you have multiple versions of PHP installed.
66+
6567
@default 'php'
6668
*/
6769
readonly binary?: string;

index.js

Lines changed: 63 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,63 +6,58 @@ import open from 'open';
66
import binaryVersionCheck from 'binary-version-check';
77
import 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>(Fatal error|Parse error|Warning|Notice)<\/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>(Fatal error|Parse error|Warning|Notice)<\/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

6762
export 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
}

readme.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,21 @@ npm install php-server
1717
```js
1818
import phpServer from 'php-server';
1919

20+
// Basic usage
2021
const server = await phpServer();
2122
console.log(`PHP server running at ${server.url}`);
23+
24+
// With custom configuration
25+
const server2 = await phpServer({
26+
port: 8080,
27+
hostname: 'localhost',
28+
base: './public',
29+
open: true, // Opens browser automatically
30+
});
31+
32+
// Clean up when done
33+
server.stop();
34+
server2.stop();
2235
```
2336

2437
## API
@@ -41,7 +54,7 @@ Type: `object`
4154
Type: `number`\
4255
Default: `0`
4356

44-
The port on which you want to access the server.
57+
The port on which you want to access the webserver.
4558

4659
Specify `0` to use a random port.
4760

@@ -121,3 +134,14 @@ Type: `object`\
121134
Default: `{}`
122135

123136
Add custom [INI directives](https://php.net/manual/en/ini.list.php).
137+
138+
**Example:**
139+
140+
```js
141+
const server = await phpServer({
142+
directives: {
143+
memory_limit: '256M',
144+
max_execution_time: '60',
145+
},
146+
});
147+
```

test/open-url.test.js

Lines changed: 0 additions & 54 deletions
This file was deleted.

test/stdio-flowing.test.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
import process from 'node:process';
2+
import {fileURLToPath} from 'node:url';
3+
import path from 'node:path';
14
import {test} from 'node:test';
25
import assert from 'node:assert/strict';
36
import phpServer from '../index.js';
47

8+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
9+
10+
process.chdir(__dirname);
11+
512
test('stdout/stderr are flowing to avoid blocking', async () => {
6-
const server = await phpServer({base: 'test/fixtures/200'});
13+
const server = await phpServer({base: 'fixtures/200'});
714
try {
815
assert.equal(server.stdout?.readableFlowing, true);
916
assert.equal(server.stderr?.readableFlowing, true);

0 commit comments

Comments
 (0)