UNPKG

@mesh-tech/mesh-cli

Version:

CLI for Mesh platform development utilities

130 lines (129 loc) 5.29 kB
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"); }