fix(cli): re-verify running binary version after update install and warn on shadowing local install (#9475)

Closes #9475
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-05 16:47:38 -03:00
committed by GitHub
parent 08d7305af0
commit d2a9378afb
4 changed files with 61 additions and 0 deletions

4
.fakebin-9475/npm Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env bash
if [ "$1" = "view" ]; then echo "3.8.99"; exit 0; fi
if [ "$1" = "install" ]; then echo "added 1 package"; exit 0; fi
exit 0

View File

@@ -181,6 +181,26 @@ export async function runUpdateCommand(opts = {}) {
// --include=optional keeps the optionalDependencies (better-sqlite3, keytar,
// tls-client, llmlingua SLM stack) on update so an omit=optional config can't drop them.
execSync("npm install -g omniroute@latest --include=optional", { stdio: "inherit" });
// Trust-but-verify: `npm install -g` exits 0 even when a shadowing local install
// (e.g. ~/node_modules/omniroute ahead of the global prefix on PATH) means the
// binary the user actually runs was not touched. Re-read the running binary's
// version and warn instead of lying about success (#9475).
const afterVersion = await getCurrentVersion();
if (afterVersion && compareVersions(afterVersion, latest) < 0) {
printError(
`Global install updated to ${latest}, but the running binary still reports ${afterVersion}.`,
);
console.log(
" A local `node_modules/omniroute` is likely shadowing the global install on PATH.",
);
console.log(" Diagnose with:");
console.log(" which -a omniroute");
console.log(" command -v omniroute");
console.log(" npm prefix -g");
console.log(" Then remove the shadowing local copy (e.g. `npm uninstall omniroute` from its directory)");
console.log(" or reorder PATH so the global bin comes first.");
return 1;
}
printSuccess(`Updated to version ${latest}`);
printInfo("Run `omniroute --version` to verify.");
return 0;

View File

@@ -0,0 +1 @@
- fix(cli): re-verify running binary version after `omniroute update` install and warn instead of lying about success when a local install shadows the global one (#9475)

View File

@@ -0,0 +1,36 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const update = await import("../../bin/cli/commands/update.mjs");
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
const REAL_VERSION = JSON.parse(readFileSync(path.join(REPO_ROOT, "package.json"), "utf-8")).version;
const FAKE_BIN = path.join(REPO_ROOT, ".fakebin-9475");
test("runUpdateCommand claims success without verifying the running binary version changed (#9475)", async () => {
const origPath = process.env.PATH;
process.env.PATH = FAKE_BIN + path.delimiter + origPath;
const stdoutLogs: string[] = [];
const origLog = console.log;
console.log = function (...args: unknown[]) {
stdoutLogs.push(args.map(String).join(" "));
};
try {
const exitCode = await update.runUpdateCommand({ yes: true, backup: false });
const realVersion = await update.getCurrentVersion();
const claimed = stdoutLogs.some((l) => /Updated to version 3\.8\.99/i.test(l));
if (claimed && exitCode === 0) {
assert.fail(
"runUpdateCommand claimed Updated to version 3.8.99 (exit 0) but the running binary is still " +
realVersion +
" — the resolved/shadowing install was not actually updated. Must re-verify getCurrentVersion() after install or warn the user.",
);
}
assert.ok(true);
} finally {
console.log = origLog;
process.env.PATH = origPath;
}
});