@azure/static-web-apps-cli
Version:
Azure Static Web Apps CLI
223 lines • 7.37 kB
JavaScript
import chalk from "chalk";
import getPort from "get-port";
import net from "node:net";
import ora from "ora";
import waitOn from "wait-on";
import { confirmChooseRandomPort } from "../prompts.js";
import { logger } from "./logger.js";
const VALID_PORT_MIN = 1024;
const VALID_PORT_MAX = 65535;
/**
* Check if a given remote address and port are accepting TCP connections.
* @param Object host and port of the server to check.
* @returns The resolved port number if the given server does not accept TCP connections. 0 if the port is already taken.
*/
export async function isAcceptingTcpConnections({ host, port }) {
port = Number(port);
logger.silly(`Checking if ${host}:${port} is accepting TCP connections...`);
// Check if current port is beyond the MAX valid port range
if (port > VALID_PORT_MAX) {
logger.silly(`Port ${port} is beyond the valid port range (${VALID_PORT_MAX}).`);
return 0;
}
return new Promise((resolve) => {
const socket = net.createConnection(port, host);
socket
.once("error", () => {
socket.end();
resolve(port);
})
.once("connect", async () => {
resolve(0);
socket.end();
});
});
}
/**
* Ask if the user wants to use a new port number, and if yes return the new port number.
* @returns A new port number if the user accepts or 0 if he refuses.
*/
export async function askNewPort() {
const confirm = await confirmChooseRandomPort(true);
return confirm ? getPort() : 0;
}
/**
* Check if a given URL string is a valid URL.
* @param url The URL string to check.
* @returns True if the URL string is a valid URL. False otherwise.
*/
export function isHttpUrl(url) {
if (!url) {
return false;
}
try {
const uri = new URL(url);
return uri.protocol.startsWith("http") || uri.protocol.startsWith("ws");
}
catch {
return false;
}
}
/**
* Check if a given URL string is a valid https URL.
* @param url The URL string to check.
* @returns True if the URL string is a valid https URL. False otherwise.
*/
export function isHttpsUrl(url) {
if (!url) {
return false;
}
try {
const uri = new URL(url);
return uri.protocol.startsWith("https");
}
catch {
return false;
}
}
/**
* Checks if a given server is up and accepting connection.
* @param url An HTTP URL.
* @param timeout Maximum time in ms to wait before exiting with failure (1) code,
default Infinity.
*/
export async function validateDevServerConfig(url, timeout) {
logger.silly(`Validating dev server config:`);
logger.silly({
url,
timeout,
});
let { hostname, port } = parseUrl(url);
try {
const resolvedPortNumber = await isAcceptingTcpConnections({ port, host: hostname });
if (resolvedPortNumber !== 0) {
const spinner = ora();
let waitOnOneOfResources = hostname === "localhost" ? [`tcp:127.0.0.1:${port}`, `tcp:localhost:${port}`] : [`tcp:${hostname}:${port}`];
spinner.start(`Waiting for ${chalk.green(url)} to be ready`);
let promises = waitOnOneOfResources.map((resource) => {
return waitOn({
resources: [resource],
delay: 1000, // initial delay in ms, default 0
interval: 100, // poll interval in ms, default 250ms
simultaneous: 1, // limit to 1 connection per resource at a time
timeout: timeout ? timeout * 1000 : timeout, // timeout in ms, default Infinity
tcpTimeout: 1000, // tcp timeout in ms, default 300ms
window: 1000, // stabilization time in ms, default 750ms
strictSSL: false,
verbose: false, // force disable verbose logs even if SWA_CLI_DEBUG is enabled
})
.then(() => {
logger.silly(`Connected to ${resource} successfully`);
return resource;
})
.catch((err) => {
logger.silly(`Could not connect to ${resource}`);
throw err;
});
});
try {
await Promise.any(promises);
spinner.succeed(`${url} validated successfully`);
spinner.clear();
}
catch {
spinner.fail();
logger.error(`Could not connect to "${url}". Is the server up and running?`, true);
}
}
}
catch (err) {
if (err.message.includes("EACCES")) {
logger.error(`Port "${port}" cannot be used. You might need elevated or admin privileges. Or, use a valid port from ${VALID_PORT_MIN} to ${VALID_PORT_MAX}.`, true);
}
else {
logger.error(err.message, true);
}
}
}
/**
* Parse a given URL and return its protocol, port, host and hostname.
* @param url The URL string to check.
* @returns Protocol, port, host and hostname extracted from the URL.
*/
export function parseUrl(url) {
if (!url) {
throw new Error(`Address: ${url} is malformed!`);
}
let { protocol, port, host, hostname } = new URL(url);
if (port === "") {
switch (protocol) {
case "http:":
port = "80";
break;
case "https:":
port = "443";
break;
}
}
return {
protocol,
port: Number(port),
host,
hostname,
};
}
/**
* Construct a valid URL string from a host, port and protocol.
* @param host A host address.
* @param port (optional) A host port.
* @param protocol (optional) A host protocol.
* @throws {Error} if the URL is malformed.
* @returns
*/
export function address(host, port = 80, protocol = `http`) {
if (!host) {
throw new Error(`Host value is not set`);
}
let url = port === 80 ? `${protocol}://${host}` : `${protocol}://${host}:${port}`;
if (isHttpUrl(url)) {
return url;
}
else {
throw new Error(`Address: ${url} is malformed!`);
}
}
export function response({ status, headers, cookies, body = "" }) {
if (typeof status !== "number") {
throw Error("TypeError: status code must be a number.");
}
body = body || null;
const res = {
status,
cookies,
headers: {
status,
"Content-Type": "application/json",
...headers,
},
body,
};
return res;
}
export function parsePort(port) {
const portNumber = parseInt(port, 10);
if (isNaN(portNumber)) {
logger.error(`Port "${port}" is not a number.`, true);
}
else {
if (portNumber < VALID_PORT_MIN || portNumber > VALID_PORT_MAX) {
logger.error(`Port "${port}" is out of range. Valid ports are from ${VALID_PORT_MIN} to ${VALID_PORT_MAX}.`, true);
}
}
return portNumber;
}
export function hostnameToIpAdress(hostnameOrIpAddress) {
if (hostnameOrIpAddress === "localhost") {
return "127.0.0.1";
}
return hostnameOrIpAddress;
}
export function isValidIpAddress(ip) {
return net.isIP(ip);
}
//# sourceMappingURL=net.js.map