Skip to content

Commit 2b05a42

Browse files
committed
fix: Corrects port config precedence
1 parent 66aa783 commit 2b05a42

File tree

5 files changed

+106
-58
lines changed

5 files changed

+106
-58
lines changed

packages/cli/lib/commands/watch.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const runWebpack = require('../lib/webpack/run-webpack');
2-
const { toBool, warn } = require('../util');
2+
const { isPortFree, toBool, warn } = require('../util');
33
const { validateArgs } = require('./validate-args');
4+
const getPort = require('get-port');
45

56
const options = [
67
{
@@ -88,8 +89,7 @@ const options = [
8889
},
8990
{
9091
name: '-p, --port',
91-
description: 'Set server port',
92-
default: 8080,
92+
description: 'Set server port (default 8080)',
9393
},
9494
];
9595

@@ -105,6 +105,8 @@ async function command(src, argv) {
105105
argv.sw = toBool(argv.sw);
106106
}
107107

108+
argv.port = await determinePort(argv.port);
109+
108110
if (argv.https || process.env.HTTPS) {
109111
let { key, cert, cacert } = argv;
110112
if (key && cert) {
@@ -117,7 +119,23 @@ async function command(src, argv) {
117119
return runWebpack(argv, true);
118120
}
119121

122+
async function determinePort(port) {
123+
port = parseInt(port, 10);
124+
if (port) {
125+
if (!(await isPortFree(port))) {
126+
throw new Error(
127+
`Another process is already running on port ${port}. Please choose a different port.`
128+
);
129+
}
130+
} else {
131+
port = await getPort({ port: parseInt(process.env.PORT, 10) || 8080 });
132+
}
133+
134+
return port;
135+
}
136+
120137
module.exports = {
121138
command,
122139
options,
140+
determinePort,
123141
};

packages/cli/lib/lib/webpack/run-webpack.js

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const ip = require('ip');
22
const webpack = require('webpack');
3-
const getPort = require('get-port');
43
const { resolve } = require('path');
54
const clear = require('console-clear');
65
const { writeFile } = require('fs').promises;
@@ -12,15 +11,10 @@ const transformConfig = require('./transform-config');
1211
const { error, isDir, warn } = require('../../util');
1312

1413
async function devBuild(env) {
15-
let userPort = parseInt(process.env.PORT || env.port, 10) || 8080;
16-
env.port = await getPort({ port: userPort });
17-
1814
let config = await clientConfig(env);
1915

2016
await transformConfig(env, config);
2117

22-
let port = config.devServer.port;
23-
2418
let compiler = webpack(config);
2519
return new Promise((res, rej) => {
2620
compiler.hooks.emit.tapAsync('CliDevPlugin', (compilation, callback) => {
@@ -46,22 +40,15 @@ async function devBuild(env) {
4640
if (host === '0.0.0.0' && process.platform === 'win32') {
4741
host = 'localhost';
4842
}
49-
50-
let serverAddr = `${protocol}://${host}:${bold(port)}`;
51-
let localIpAddr = `${protocol}://${ip.address()}:${bold(port)}`;
43+
let serverAddr = `${protocol}://${host}:${bold(env.port)}`;
44+
let localIpAddr = `${protocol}://${ip.address()}:${bold(env.port)}`;
5245

5346
if (env['clear']) clear(true);
5447

5548
if (stats.hasErrors()) {
5649
process.stdout.write(red('Build failed!\n\n'));
5750
} else {
5851
process.stdout.write(green('Compiled successfully!\n\n'));
59-
60-
if (userPort !== port) {
61-
process.stdout.write(
62-
`Port ${bold(userPort)} is in use, using ${bold(port)} instead\n\n`
63-
);
64-
}
6552
process.stdout.write('You can view the application in browser.\n\n');
6653
process.stdout.write(`${bold('Local:')} ${serverAddr}\n`);
6754
process.stdout.write(`${bold('On Your Network:')} ${localIpAddr}\n`);
@@ -77,7 +64,7 @@ async function devBuild(env) {
7764
});
7865

7966
let server = new DevServer(compiler, c);
80-
server.listen(port);
67+
server.listen(env.port);
8168
res(server);
8269
});
8370
}

packages/cli/lib/util.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const { normalize, resolve } = require('path');
33
const { statSync, existsSync } = require('fs');
44
const symbols = require('./symbols');
55
const which = require('which');
6+
const net = require('net');
67

78
exports.isDir = function (str) {
89
return existsSync(str) && statSync(str).isDirectory();
@@ -54,3 +55,26 @@ exports.normalizeTemplatesResponse = function (repos = []) {
5455
exports.toBool = function (val) {
5556
return val === void 0 || (val === 'false' ? false : val);
5657
};
58+
59+
/**
60+
* Taken from: https://github.com/preactjs/wmr/blob/3401a9bfa6491d25108ad68688c067a7e17d0de5/packages/wmr/src/lib/net-utils.js#L4-Ll4
61+
* Check if a port is free
62+
* @param {number} port
63+
* @returns {Promise<boolean>}
64+
*/
65+
exports.isPortFree = async function (port) {
66+
try {
67+
await new Promise((resolve, reject) => {
68+
const server = net.createServer();
69+
server.unref();
70+
server.on('error', reject);
71+
server.listen({ port }, () => {
72+
server.close(resolve);
73+
});
74+
});
75+
return true;
76+
} catch (err) {
77+
if (err.code !== 'EADDRINUSE') throw err;
78+
return false;
79+
}
80+
};

packages/cli/tests/watch.test.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { readFile, writeFile } = require('fs').promises;
22
const { resolve } = require('path');
33
const startChrome = require('./lib/chrome');
44
const { create, watch } = require('./lib/cli');
5+
const { determinePort } = require('../lib/commands/watch');
56

67
const { loadPage, waitUntilExpression } = startChrome;
78
let chrome, server;
@@ -34,3 +35,59 @@ describe('preact', () => {
3435
server.close();
3536
});
3637
});
38+
39+
describe('should determine the correct port', () => {
40+
it('should prefer --port over $PORT', async () => {
41+
process.env.PORT = '4000';
42+
expect(await determinePort('3999')).toBe(3999);
43+
});
44+
45+
it('should use $PORT in the abscence of --port', async () => {
46+
process.env.PORT = '4001';
47+
expect(await determinePort()).toBe(4001);
48+
});
49+
50+
it('should use $PORT if --port is invalid', async () => {
51+
process.env.PORT = '4002';
52+
expect(await determinePort('invalid-port')).toBe(4002);
53+
});
54+
55+
it('should use 8080 if $PORT and --port are invalid', async () => {
56+
process.env.PORT = 'invalid-port-too';
57+
expect(await determinePort('invalid-port')).toBe(8080);
58+
});
59+
60+
it('should return an error if requested --port is taken', async () => {
61+
await Promise.all([determinePort(4003), determinePort(4003)]).catch(
62+
error => {
63+
expect(error.message).toMatch(
64+
new RegExp(
65+
/^Another process is already running on port 4003. Please choose a different port./g
66+
)
67+
);
68+
}
69+
);
70+
});
71+
72+
it('should fallback to random if $PORT or 8080 are taken and --port is not specified', async () => {
73+
process.env.PORT = '4004';
74+
await Promise.all([determinePort(), determinePort()]).then(values => {
75+
expect(values[0]).toBe(4004);
76+
expect(values[1]).toBeGreaterThanOrEqual(1024);
77+
expect(values[1]).toBeLessThanOrEqual(65535);
78+
});
79+
80+
// This is pretty awful, but would be the way to do it. get-port locks the port for ~30 seconds,
81+
// so if we want to test any behavior with our default (8080) twice, we'd have to wait :/
82+
//
83+
//await sleep(35000);
84+
85+
//process.env.PORT = undefined;
86+
//await Promise.all([determinePort(), determinePort()]).then(values => {
87+
// console.log(values);
88+
// expect(values[0]).toBe(8080);
89+
// expect(values[1]).toBeGreaterThanOrEqual(1024);
90+
// expect(values[1]).toBeLessThanOrEqual(65535);
91+
//});
92+
});
93+
});

yarn.lock

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2749,11 +2749,6 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
27492749
mime-types "~2.1.24"
27502750
negotiator "0.6.2"
27512751

2752-
acorn-es7-plugin@^1.1.7:
2753-
version "1.1.7"
2754-
resolved "https://registry.yarnpkg.com/acorn-es7-plugin/-/acorn-es7-plugin-1.1.7.tgz#f2ee1f3228a90eead1245f9ab1922eb2e71d336b"
2755-
integrity sha1-8u4fMiipDurRJF+asZIusucdM2s=
2756-
27572752
acorn-globals@^3.1.0:
27582753
version "3.1.0"
27592754
resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf"
@@ -2797,11 +2792,6 @@ acorn-walk@^8.0.0:
27972792
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.1.0.tgz#d3c6a9faf00987a5e2b9bdb506c2aa76cd707f83"
27982793
integrity sha512-mjmzmv12YIG/G8JQdQuz2MUDShEJ6teYpT5bmWA4q7iwoGen8xtt3twF3OvzIUl+Q06aWIjvnwQUKvQ6TtMRjg==
27992794

2800-
"acorn@>= 2.5.2 <= 5.7.5":
2801-
version "5.7.4"
2802-
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
2803-
integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
2804-
28052795
acorn@^4.0.4:
28062796
version "4.0.13"
28072797
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
@@ -6197,14 +6187,6 @@ extsprintf@^1.2.0:
61976187
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
61986188
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
61996189

6200-
fast-async@^6.3.7:
6201-
version "6.3.8"
6202-
resolved "https://registry.yarnpkg.com/fast-async/-/fast-async-6.3.8.tgz#031b9e1d5a84608b117b3e7c999ad477ed2b08a2"
6203-
integrity sha512-TjlooyqrYm/gOXjD2UHNwfrWkvTbzU105Nk4bvcRTeRoL+wIeK6rqbqDg3CN9z5p37cE2iXhP6SxQFz8OVIaUg==
6204-
dependencies:
6205-
nodent-compiler "^3.2.10"
6206-
nodent-runtime ">=3.2.1"
6207-
62086190
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
62096191
version "3.1.3"
62106192
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -10122,26 +10104,6 @@ node-releases@^1.1.71:
1012210104
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
1012310105
integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
1012410106

10125-
nodent-compiler@^3.2.10:
10126-
version "3.2.13"
10127-
resolved "https://registry.yarnpkg.com/nodent-compiler/-/nodent-compiler-3.2.13.tgz#149aefee22fe55f70e76ae7f1323e641e0c762e6"
10128-
integrity sha512-nzzWPXZwSdsWie34om+4dLrT/5l1nT/+ig1v06xuSgMtieJVAnMQFuZihUwREM+M7dFso9YoHfDmweexEXXrrw==
10129-
dependencies:
10130-
acorn ">= 2.5.2 <= 5.7.5"
10131-
acorn-es7-plugin "^1.1.7"
10132-
nodent-transform "^3.2.9"
10133-
source-map "^0.5.7"
10134-
10135-
nodent-runtime@>=3.2.1:
10136-
version "3.2.1"
10137-
resolved "https://registry.yarnpkg.com/nodent-runtime/-/nodent-runtime-3.2.1.tgz#9e2755d85e39f764288f0d4752ebcfe3e541e00e"
10138-
integrity sha512-7Ws63oC+215smeKJQCxzrK21VFVlCFBkwl0MOObt0HOpVQXs3u483sAmtkF33nNqZ5rSOQjB76fgyPBmAUrtCA==
10139-
10140-
nodent-transform@^3.2.9:
10141-
version "3.2.9"
10142-
resolved "https://registry.yarnpkg.com/nodent-transform/-/nodent-transform-3.2.9.tgz#ec11a6116b5476e60bc212371cf6b8e4c74f40b6"
10143-
integrity sha512-4a5FH4WLi+daH/CGD5o/JWRR8W5tlCkd3nrDSkxbOzscJTyTUITltvOJeQjg3HJ1YgEuNyiPhQbvbtRjkQBByQ==
10144-
1014510107
nopt@^4.0.1, nopt@^4.0.3:
1014610108
version "4.0.3"
1014710109
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
@@ -13428,7 +13390,7 @@ source-map-url@^0.4.0:
1342813390
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56"
1342913391
integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==
1343013392

13431-
source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7:
13393+
source-map@^0.5.0, source-map@^0.5.6:
1343213394
version "0.5.7"
1343313395
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
1343413396
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=

0 commit comments

Comments
 (0)