@mesh-tech/mesh-cli
Version:
CLI for Mesh platform development utilities
130 lines (129 loc) • 5.29 kB
JavaScript
import * as net from "net";
import { execFileSync } from "child_process";
import { LOCAL_AWS_CONFIG } from "./seed.js";
export async function upsertLocalSecret(secretId, value, client) {
const { SecretsManagerClient, CreateSecretCommand, PutSecretValueCommand } = await import("@aws-sdk/client-secrets-manager");
const sm = client ?? new SecretsManagerClient(LOCAL_AWS_CONFIG);
const secretString = JSON.stringify(value);
try {
await sm.send(new CreateSecretCommand({ Name: secretId, SecretString: secretString }));
}
catch (err) {
if (err?.name === "ResourceExistsException") {
await sm.send(new PutSecretValueCommand({ SecretId: secretId, SecretString: secretString }));
}
else {
throw err;
}
}
}
export function probeTcp(port, opts = {}) {
const { host = "127.0.0.1", timeoutMs = 2000 } = opts;
return new Promise((resolve) => {
const socket = net.connect({ host, port, timeout: timeoutMs });
socket.once("connect", () => {
socket.destroy();
resolve(true);
});
socket.once("error", () => resolve(false));
socket.once("timeout", () => {
socket.destroy();
resolve(false);
});
});
}
export function hostPortsOf(endpoints) {
const ports = new Set();
const fromUrl = (raw) => {
try {
const u = new URL(raw.includes("://") ? raw : `tcp://${raw}`);
const port = Number(u.port || (u.protocol === "https:" ? 443 : u.protocol === "http:" ? 80 : NaN));
if (Number.isInteger(port) && port > 0)
ports.add(port);
}
catch {
}
};
for (const e of endpoints) {
if (e.probe.kind === "tcp")
ports.add(e.probe.port);
else if (e.probe.kind === "http")
fromUrl(e.probe.url);
fromUrl(e.url);
}
return [...ports].sort((a, b) => a - b);
}
export function parseDockerPsPorts(output, ignorePrefix) {
const held = new Map();
for (const line of output.split("\n")) {
const tab = line.indexOf("\t");
if (tab < 0)
continue;
const name = line.slice(0, tab).trim();
if (!name || name.startsWith(ignorePrefix))
continue;
for (const m of line.slice(tab + 1).matchAll(/:(\d+)->\d+\/(?:tcp|udp)/g)) {
const port = Number(m[1]);
if (!held.has(port))
held.set(port, name);
}
}
return held;
}
const defaultPortConflictIo = {
dockerPs: () => {
try {
return execFileSync("docker", ["ps", "--format", "{{.Names}}\t{{.Ports}}"], {
encoding: "utf-8",
stdio: ["ignore", "pipe", "ignore"],
});
}
catch {
return "";
}
},
processOn: (port) => {
try {
const out = execFileSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-Fc"], {
encoding: "utf-8",
stdio: ["ignore", "pipe", "ignore"],
});
const cmd = out.split("\n").find((l) => l.startsWith("c"));
return cmd ? cmd.slice(1) : null;
}
catch {
return null;
}
},
listening: (port) => probeTcp(port, { timeoutMs: 300 }),
};
export async function findPortConflicts(ports, ignorePrefix, io = defaultPortConflictIo) {
const unique = [...new Set(ports)].sort((a, b) => a - b);
const bound = (await Promise.all(unique.map(async (port) => ((await io.listening(port)) ? port : null)))).filter((p) => p !== null);
if (bound.length === 0)
return [];
const containers = parseDockerPsPorts(io.dockerPs(), ignorePrefix);
return bound.map((port) => {
const container = containers.get(port);
if (container)
return { port, holder: { kind: "container", name: container } };
const process = io.processOn(port);
return { port, holder: process ? { kind: "process", name: process } : null };
});
}
export function describePortConflicts(conflicts, moveHints = new Map()) {
const lines = conflicts.flatMap(({ port, holder }) => {
const remedy = holder?.kind === "container"
? `docker stop ${holder.name}`
: `lsof -nP -iTCP:${port} -sTCP:LISTEN, then stop what it names`;
const what = holder ? `held by ${holder.kind} ${holder.name}` : "held by something lsof would not name";
const move = moveHints.get(port);
return move
? [` ${String(port).padEnd(5)} ${what.padEnd(38)} (${remedy})`, ` ${"".padEnd(5)} or: ${move}`]
: [` ${String(port).padEnd(5)} ${what.padEnd(38)} (${remedy})`];
});
return (`Cannot start the local platform — ${conflicts.length === 1 ? "a host port it needs is" : "host ports it needs are"} already in use:\n` +
lines.join("\n") +
'\nStop what holds the port (or move it), then run mesh start again. A port lost this way does not fail as "port in use": the container never joins the network and a later service reports an unrelated error.' +
"\nIf the port is held by something you cannot stop and the stack can live without it: mesh start --skip-port-check");
}