mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-21 22:52:19 +03:00
Extrai 7 grupos de comandos do monolito bin/cli-commands.mjs (2853 linhas)
para módulos individuais em bin/cli/commands/, registrados via Commander.
- stop.mjs: SIGTERM/SIGKILL via process.kill(); fallback por porta usa execFile
com array de args (evita injeção de shell / Semgrep CWE-78)
- restart.mjs: delega para runStopCommand + runServe
- dashboard.mjs: alias "open", fallback nativo por plataforma via execFile
- keys.mjs: server-first (POST /api/v1/providers/keys) → DB fallback; valida
provider via loadAvailableProviders(); suporte a --stdin
- models.mjs: GET /api/models → fallback /api/v1/models; filtro por provider e --search
- combo.mjs: list/switch/create/delete; switch server-first → DB fallback via key_value;
TODO(1.5) marcados para substituir SQL cru por src/lib/db/combos.ts
- serve.mjs: escreve PID via writePidFile() no spawn; limpa no shutdown; suporte daemon
utils/pid.mjs e i18n keys (en.json + pt-BR.json) já criados em iteração anterior.
Testes: cli-keys-command.test.ts atualizado para novos runners;
cli-serve-stop-command.test.ts, cli-combo-command.test.ts,
cli-models-command.test.ts adicionados (23 testes, 0 falhas).
89 lines
2.1 KiB
JavaScript
89 lines
2.1 KiB
JavaScript
import { execFile } from "node:child_process";
|
|
import { promisify } from "node:util";
|
|
import { readPidFile, isPidRunning, cleanupPidFile, sleep } from "../utils/pid.mjs";
|
|
import { t } from "../i18n.mjs";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
|
|
export function registerStop(program) {
|
|
program
|
|
.command("stop")
|
|
.description(t("stop.description"))
|
|
.action(async (opts) => {
|
|
const exitCode = await runStopCommand(opts);
|
|
if (exitCode !== 0) process.exit(exitCode);
|
|
});
|
|
}
|
|
|
|
export async function runStopCommand(opts = {}) {
|
|
const pid = readPidFile();
|
|
|
|
if (pid && isPidRunning(pid)) {
|
|
console.log(t("stop.stopping", { pid }));
|
|
try {
|
|
process.kill(pid, "SIGTERM");
|
|
|
|
let waited = 0;
|
|
while (waited < 5000 && isPidRunning(pid)) {
|
|
await sleep(100);
|
|
waited += 100;
|
|
}
|
|
|
|
if (isPidRunning(pid)) {
|
|
process.kill(pid, "SIGKILL");
|
|
await sleep(500);
|
|
}
|
|
|
|
cleanupPidFile();
|
|
console.log(t("stop.stopped"));
|
|
return 0;
|
|
} catch (err) {
|
|
console.error(
|
|
t("common.error", { message: err instanceof Error ? err.message : String(err) })
|
|
);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
const port = opts.port ? parseInt(String(opts.port), 10) : 20128;
|
|
if (pid === null) {
|
|
console.log(t("stop.portFallback"));
|
|
await killByPort(port);
|
|
cleanupPidFile();
|
|
console.log(t("stop.stopped"));
|
|
return 0;
|
|
}
|
|
|
|
console.log(t("stop.notRunning"));
|
|
return 0;
|
|
}
|
|
|
|
async function killByPort(port) {
|
|
if (process.platform === "win32") return;
|
|
try {
|
|
const { stdout } = await execFileAsync("lsof", ["-ti", `:${port}`]);
|
|
const pids = stdout
|
|
.trim()
|
|
.split("\n")
|
|
.map((p) => parseInt(p, 10))
|
|
.filter((p) => Number.isFinite(p) && p > 0);
|
|
|
|
for (const p of pids) {
|
|
try {
|
|
process.kill(p, "SIGTERM");
|
|
} catch {}
|
|
}
|
|
|
|
if (pids.length > 0) {
|
|
await sleep(1000);
|
|
for (const p of pids) {
|
|
try {
|
|
if (isPidRunning(p)) process.kill(p, "SIGKILL");
|
|
} catch {}
|
|
}
|
|
}
|
|
} catch {
|
|
// lsof not available or no process on port
|
|
}
|
|
}
|