@khanglvm/llm-router 2.3.6 → 2.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,14 +2,10 @@ import { execSync } from "node:child_process";
2
2
  import { readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import {
6
- getActiveRuntimeState,
7
- stopProcessByPid,
8
- clearRuntimeState,
9
- spawnDetachedStart
10
- } from "./instance-state.js";
5
+ import { getActiveRuntimeState } from "./instance-state.js";
11
6
 
12
7
  const PKG_NAME = "@khanglvm/llm-router";
8
+ const GRACEFUL_RESTART_SIGNAL = "SIGUSR2";
13
9
 
14
10
  function readInstalledVersion() {
15
11
  try {
@@ -21,41 +17,77 @@ function readInstalledVersion() {
21
17
  }
22
18
  }
23
19
 
24
- function fetchLatestVersion() {
20
+ function fetchLatestVersion(exec = execSync) {
25
21
  try {
26
- return execSync(`npm view ${PKG_NAME} version`, { encoding: "utf8" }).trim();
22
+ return exec(`npm view ${PKG_NAME} version`, { encoding: "utf8" }).trim();
27
23
  } catch {
28
24
  return null;
29
25
  }
30
26
  }
31
27
 
32
- function detectPackageManager() {
28
+ function detectPackageManager(exec = execSync) {
33
29
  try {
34
- const npmGlobalRoot = execSync("npm root -g", { encoding: "utf8" }).trim();
30
+ const npmGlobalRoot = exec("npm root -g", { encoding: "utf8" }).trim();
35
31
  const entryReal = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
36
32
  if (entryReal.startsWith(npmGlobalRoot)) return "npm";
37
- } catch { /* ignore */ }
33
+ } catch {
34
+ // ignore
35
+ }
38
36
 
39
37
  try {
40
- const out = execSync("pnpm list -g --json 2>/dev/null", { encoding: "utf8" });
38
+ const out = exec("pnpm list -g --json 2>/dev/null", { encoding: "utf8" });
41
39
  if (out.includes(PKG_NAME)) return "pnpm";
42
- } catch { /* ignore */ }
40
+ } catch {
41
+ // ignore
42
+ }
43
43
 
44
44
  return "npm";
45
45
  }
46
46
 
47
- export async function runUpgradeCommand({ onLine, onError } = {}) {
47
+ function requestGracefulRuntimeRestart(runtime, signalProcess = process.kill) {
48
+ const pid = Number(runtime?.pid);
49
+ if (!Number.isInteger(pid) || pid <= 0) {
50
+ return { ok: false, reason: "Invalid runtime pid." };
51
+ }
52
+
53
+ try {
54
+ signalProcess(pid, GRACEFUL_RESTART_SIGNAL);
55
+ return { ok: true, signal: GRACEFUL_RESTART_SIGNAL };
56
+ } catch (restartError) {
57
+ return {
58
+ ok: false,
59
+ reason: restartError instanceof Error ? restartError.message : String(restartError)
60
+ };
61
+ }
62
+ }
63
+
64
+ export async function runUpgradeCommand({ onLine, onError } = {}, deps = {}) {
48
65
  const line = typeof onLine === "function" ? onLine : (msg) => console.log(msg);
49
66
  const error = typeof onError === "function" ? onError : (msg) => console.error(msg);
50
-
51
- const currentVersion = readInstalledVersion();
67
+ const exec = typeof deps.exec === "function" ? deps.exec : execSync;
68
+ const readInstalledVersionFn = typeof deps.readInstalledVersion === "function"
69
+ ? deps.readInstalledVersion
70
+ : readInstalledVersion;
71
+ const fetchLatestVersionFn = typeof deps.fetchLatestVersion === "function"
72
+ ? deps.fetchLatestVersion
73
+ : () => fetchLatestVersion(exec);
74
+ const detectPackageManagerFn = typeof deps.detectPackageManager === "function"
75
+ ? deps.detectPackageManager
76
+ : () => detectPackageManager(exec);
77
+ const getActiveRuntimeStateFn = typeof deps.getActiveRuntimeState === "function"
78
+ ? deps.getActiveRuntimeState
79
+ : getActiveRuntimeState;
80
+ const signalProcess = typeof deps.signalProcess === "function"
81
+ ? deps.signalProcess
82
+ : process.kill;
83
+
84
+ const currentVersion = readInstalledVersionFn();
52
85
  line(`Current version: ${currentVersion}`);
53
86
 
54
- // Check latest
55
87
  line("Checking for updates...");
56
- const latestVersion = fetchLatestVersion();
88
+ const latestVersion = fetchLatestVersionFn();
57
89
  if (!latestVersion) {
58
- error("Could not fetch latest version from npm registry.");
90
+ error("Could not fetch the latest version from the npm registry.");
59
91
  return { ok: false, exitCode: 1 };
60
92
  }
61
93
 
@@ -64,65 +96,61 @@ export async function runUpgradeCommand({ onLine, onError } = {}) {
64
96
  return { ok: true, exitCode: 0 };
65
97
  }
66
98
 
67
- line(`New version available: ${currentVersion} ${latestVersion}`);
99
+ line(`New version available: ${currentVersion} -> ${latestVersion}`);
68
100
 
69
- // Stop running instance
70
- let wasRunning = false;
71
- let savedState = null;
101
+ let runtime = null;
72
102
  try {
73
- const runtime = await getActiveRuntimeState();
74
- if (runtime) {
75
- wasRunning = true;
76
- savedState = { ...runtime };
77
- line(`Stopping running server (pid ${runtime.pid})...`);
78
- const stopResult = await stopProcessByPid(runtime.pid);
79
- if (stopResult.ok) {
80
- await clearRuntimeState({ pid: runtime.pid });
81
- line("Server stopped.");
82
- } else {
83
- error(`Warning: could not stop server cleanly — ${stopResult.reason || "unknown"}`);
84
- }
85
- }
103
+ runtime = await getActiveRuntimeStateFn();
86
104
  } catch {
87
- // instance-state not available, skip
105
+ runtime = null;
88
106
  }
89
107
 
90
- // Install latest
91
- const pm = detectPackageManager();
108
+ if (runtime) {
109
+ line(`Running router detected (pid ${runtime.pid}) at http://${runtime.host}:${runtime.port}.`);
110
+ line("The router will keep serving current requests while the package upgrade runs.");
111
+ } else {
112
+ line("No running local router detected. The new version will be used on the next start.");
113
+ }
114
+
115
+ const pm = detectPackageManagerFn();
92
116
  const installCmd = pm === "pnpm"
93
117
  ? `pnpm add -g ${PKG_NAME}@latest`
94
118
  : `npm install -g ${PKG_NAME}@latest`;
95
119
 
96
120
  line(`Upgrading via: ${installCmd}`);
97
121
  try {
98
- execSync(installCmd, { stdio: "inherit" });
122
+ exec(installCmd, { stdio: "inherit" });
99
123
  } catch {
100
- error("Upgrade failed. You may need to run with sudo or fix npm permissions.");
124
+ error("Upgrade failed. The running router was left untouched. You may need sudo or corrected npm permissions.");
101
125
  return { ok: false, exitCode: 1 };
102
126
  }
103
127
 
104
- const newVersion = fetchLatestVersion() || latestVersion;
105
- line(`Upgraded to ${newVersion}.`);
106
-
107
- // Restart server if it was running
108
- if (wasRunning && savedState) {
109
- line("Restarting server...");
110
- try {
111
- spawnDetachedStart({
112
- cliPath: savedState.cliPath || "",
113
- configPath: savedState.configPath || "",
114
- host: savedState.host || "127.0.0.1",
115
- port: savedState.port || 18080,
116
- watchConfig: savedState.watchConfig ?? true,
117
- watchBinary: savedState.watchBinary ?? true,
118
- requireAuth: savedState.requireAuth ?? false,
119
- });
120
- line("Server restarted.");
121
- } catch (err) {
122
- error(`Could not restart server: ${err instanceof Error ? err.message : String(err)}`);
123
- line("Start manually with: llr start");
124
- }
128
+ line(`Upgraded package to ${latestVersion}.`);
129
+
130
+ if (!runtime) {
131
+ return { ok: true, exitCode: 0 };
132
+ }
133
+
134
+ const restartResult = requestGracefulRuntimeRestart(runtime, signalProcess);
135
+ if (restartResult.ok) {
136
+ line(`Requested a graceful router reload (pid ${runtime.pid}).`);
137
+ line("Existing requests will drain before the new version takes over.");
138
+ return { ok: true, exitCode: 0 };
139
+ }
140
+
141
+ error(`Could not trigger an immediate graceful router reload: ${restartResult.reason || "unknown error"}`);
142
+ if (runtime.watchBinary) {
143
+ line("The running router is still serving traffic and should self-reload when its binary watcher detects the new version.");
144
+ } else {
145
+ line("Binary update watch is disabled for the running router. Restart it manually with `llr start` to activate the new version.");
125
146
  }
126
147
 
127
148
  return { ok: true, exitCode: 0 };
128
149
  }
150
+
151
+ export {
152
+ detectPackageManager,
153
+ fetchLatestVersion,
154
+ readInstalledVersion,
155
+ requestGracefulRuntimeRestart
156
+ };