async function bootServer(options, configWatchFiles) {
	options = await normalizeOptions(options, 'start', configWatchFiles);

	options.middleware = [].concat(
		// @ts-ignore-next
		options.middleware || [],

		wmrMiddleware({
			...options,
			onError: sendError,
			onChange: sendChanges
		}),
		injectWmrMiddleware(options)
	);

	// eslint-disable-next-line
	function sendError(err) {
		if (app.ws.clients.size > 0) {
			app.ws.broadcast({
				type: 'error',
				error: err.clientMessage || err.message,
				codeFrame: kl.stripColors(err.codeFrame),
				stack: parseStackTrace(err.stack)
			});
		}
	}

	// eslint-disable-next-line
	function sendChanges({ changes, reload }) {
		if (options.reload || reload) {
			app.ws.broadcast({ type: 'reload' });
		} else {
			app.ws.broadcast({
				type: 'update',
				changes
			});
		}
	}

	const app = await server(options);

	let resolveActualPort;
	let actualPort = new Promise(r => (resolveActualPort = r));
	const closeServer = makeCloseable(app.server);
	app.listen(options.port, options.host, () => {
		const addresses = getServerAddresses(app.server.address(), {
			host: options.host,
			https: app.http2
		});

		const message = `dev server running at:`;
		process.stdout.write(formatBootMessage(message, addresses));

		// If the port was `0` than the OS picks a random
		// free port. Get the actual port here so that we
		// can reconnect to the same server from the client.
		const port = +app.server.address().port;
		resolveActualPort(port);
	});

	return {
		resolvePort: actualPort,
		async close() {
			app.ws.broadcast({
				type: 'info',
				message: 'Server restarting...',
				kind: 'restart'
			});
			app.ws.close();
			await closeServer();
		}
	};
}
@param {Parameters<server>[0] & OtherOptions} options
@param {string[]} configWatchFiles
@returns {Promise<{ close: () => Promise<void>, resolvePort: Promise<number>}>}