Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/little-toys-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'preact-cli': patch
---

Changes port config precedence to: --port -> \$PORT -> 8080 (default)
24 changes: 21 additions & 3 deletions packages/cli/lib/commands/watch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const runWebpack = require('../lib/webpack/run-webpack');
const { toBool, warn } = require('../util');
const { isPortFree, toBool, warn } = require('../util');
const { validateArgs } = require('./validate-args');
const getPort = require('get-port');

const options = [
{
Expand Down Expand Up @@ -88,8 +89,7 @@ const options = [
},
{
name: '-p, --port',
description: 'Set server port',
default: 8080,
description: 'Set server port (default 8080)',
},
];

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

argv.port = await determinePort(argv.port);

if (argv.https || process.env.HTTPS) {
let { key, cert, cacert } = argv;
if (key && cert) {
Expand All @@ -117,7 +119,23 @@ async function command(src, argv) {
return runWebpack(argv, true);
}

async function determinePort(port) {
port = parseInt(port, 10);
if (port) {
if (!(await isPortFree(port))) {
throw new Error(
`Another process is already running on port ${port}. Please choose a different port.`
);
}
} else {
port = await getPort({ port: parseInt(process.env.PORT, 10) || 8080 });
}

return port;
}

module.exports = {
command,
options,
determinePort,
};
19 changes: 3 additions & 16 deletions packages/cli/lib/lib/webpack/run-webpack.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const ip = require('ip');
const webpack = require('webpack');
const getPort = require('get-port');
const { resolve } = require('path');
const clear = require('console-clear');
const { writeFile } = require('fs').promises;
Expand All @@ -12,15 +11,10 @@ const transformConfig = require('./transform-config');
const { error, isDir, warn } = require('../../util');

async function devBuild(env) {
let userPort = parseInt(process.env.PORT || env.port, 10) || 8080;
env.port = await getPort({ port: userPort });

let config = await clientConfig(env);

await transformConfig(env, config);

let port = config.devServer.port;

let compiler = webpack(config);
return new Promise((res, rej) => {
compiler.hooks.emit.tapAsync('CliDevPlugin', (compilation, callback) => {
Expand All @@ -46,22 +40,15 @@ async function devBuild(env) {
if (host === '0.0.0.0' && process.platform === 'win32') {
host = 'localhost';
}

let serverAddr = `${protocol}://${host}:${bold(port)}`;
let localIpAddr = `${protocol}://${ip.address()}:${bold(port)}`;
let serverAddr = `${protocol}://${host}:${bold(env.port)}`;
let localIpAddr = `${protocol}://${ip.address()}:${bold(env.port)}`;

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

if (stats.hasErrors()) {
process.stdout.write(red('Build failed!\n\n'));
} else {
process.stdout.write(green('Compiled successfully!\n\n'));

if (userPort !== port) {
process.stdout.write(
`Port ${bold(userPort)} is in use, using ${bold(port)} instead\n\n`
);
}
process.stdout.write('You can view the application in browser.\n\n');
process.stdout.write(`${bold('Local:')} ${serverAddr}\n`);
process.stdout.write(`${bold('On Your Network:')} ${localIpAddr}\n`);
Expand All @@ -77,7 +64,7 @@ async function devBuild(env) {
});

let server = new DevServer(compiler, c);
server.listen(port);
server.listen(env.port);
res(server);
});
}
Expand Down
24 changes: 24 additions & 0 deletions packages/cli/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const { normalize, resolve } = require('path');
const { statSync, existsSync } = require('fs');
const symbols = require('./symbols');
const which = require('which');
const net = require('net');

exports.isDir = function (str) {
return existsSync(str) && statSync(str).isDirectory();
Expand Down Expand Up @@ -54,3 +55,26 @@ exports.normalizeTemplatesResponse = function (repos = []) {
exports.toBool = function (val) {
return val === void 0 || (val === 'false' ? false : val);
};

/**
* Taken from: https://github.com/preactjs/wmr/blob/3401a9bfa6491d25108ad68688c067a7e17d0de5/packages/wmr/src/lib/net-utils.js#L4-Ll4
* Check if a port is free
* @param {number} port
* @returns {Promise<boolean>}
*/
exports.isPortFree = async function (port) {
try {
await new Promise((resolve, reject) => {
const server = net.createServer();
server.unref();
server.on('error', reject);
server.listen({ port }, () => {
server.close(resolve);
});
});
return true;
} catch (err) {
if (err.code !== 'EADDRINUSE') throw err;
return false;
}
};
57 changes: 57 additions & 0 deletions packages/cli/tests/watch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { readFile, writeFile } = require('fs').promises;
const { resolve } = require('path');
const startChrome = require('./lib/chrome');
const { create, watch } = require('./lib/cli');
const { determinePort } = require('../lib/commands/watch');

const { loadPage, waitUntilExpression } = startChrome;
let chrome, server;
Expand Down Expand Up @@ -34,3 +35,59 @@ describe('preact', () => {
server.close();
});
});

describe('should determine the correct port', () => {
it('should prefer --port over $PORT', async () => {
process.env.PORT = '4000';
expect(await determinePort('3999')).toBe(3999);
});

it('should use $PORT in the abscence of --port', async () => {
process.env.PORT = '4001';
expect(await determinePort()).toBe(4001);
});

it('should use $PORT if --port is invalid', async () => {
process.env.PORT = '4002';
expect(await determinePort('invalid-port')).toBe(4002);
});

it('should use 8080 if $PORT and --port are invalid', async () => {
process.env.PORT = 'invalid-port-too';
expect(await determinePort('invalid-port')).toBe(8080);
});

it('should return an error if requested --port is taken', async () => {
await Promise.all([determinePort(4003), determinePort(4003)]).catch(
error => {
expect(error.message).toMatch(
new RegExp(
/^Another process is already running on port 4003. Please choose a different port./g
)
);
}
);
});

it('should fallback to random if $PORT or 8080 are taken and --port is not specified', async () => {
process.env.PORT = '4004';
await Promise.all([determinePort(), determinePort()]).then(values => {
expect(values[0]).toBe(4004);
expect(values[1]).toBeGreaterThanOrEqual(1024);
expect(values[1]).toBeLessThanOrEqual(65535);
});

// This is pretty awful, but would be the way to do it. get-port locks the port for ~30 seconds,
// so if we want to test any behavior with our default (8080) twice, we'd have to wait :/
//
//await sleep(35000);

//process.env.PORT = undefined;
//await Promise.all([determinePort(), determinePort()]).then(values => {
// console.log(values);
// expect(values[0]).toBe(8080);
// expect(values[1]).toBeGreaterThanOrEqual(1024);
// expect(values[1]).toBeLessThanOrEqual(65535);
//});
});
});