From 54f31f79d801179dbdaec9675229dd4f285cba6d Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Tue, 14 Jul 2026 23:09:44 -0300 Subject: [PATCH] fix(cli): fast-path --version to skip full CLI bootstrap `omniroute --version` ran the entire CLI bootstrap before printing the version: the tsx/esm + polyfill imports, env-file loading, and Commander's ~70-command registration (importing DB, providers, OAuth, and other heavy modules). That took ~1.5s just to print a version string. Add isVersionFastPath() (bin/cli/utils/versionFastPath.mjs) and check it at the very top of bin/omniroute.mjs, before any of that work runs. It only trips for an unambiguous bare `--version`/`-V` invocation (no other args), so it never changes behavior for real commands or for `--help` (whose output is generated dynamically from every registered subcommand, so it still needs full registration and is deliberately not fast-pathed). `--version` now returns in ~0.3s instead of ~1.5s locally. Co-authored-by: Sutarto Jordan Chrisfivo Inspired-by: https://github.com/decolua/9router/pull/2414 --- bin/cli/utils/versionFastPath.mjs | 25 +++++++++++ bin/omniroute.mjs | 27 ++++++++++-- tests/unit/cli-version-fastpath.test.ts | 56 +++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 bin/cli/utils/versionFastPath.mjs create mode 100644 tests/unit/cli-version-fastpath.test.ts diff --git a/bin/cli/utils/versionFastPath.mjs b/bin/cli/utils/versionFastPath.mjs new file mode 100644 index 0000000000..13103b6309 --- /dev/null +++ b/bin/cli/utils/versionFastPath.mjs @@ -0,0 +1,25 @@ +/** + * Decide whether a CLI invocation is a bare `--version`/`-V` query that should + * short-circuit BEFORE the runtime polyfill import, env-file loading, and + * Commander's command registration (~70 command modules) are loaded. + * + * Scope is intentionally narrow — only a single, unambiguous `--version`/`-V` + * argument fast-paths. Anything else (extra args, a subcommand, `--help`, + * global options like `--lang`/`--output` alongside it) falls through to the + * normal Commander flow. Unlike `--version`, OmniRoute's `--help` output is + * generated dynamically from every registered subcommand, so skipping + * registration would change (truncate) the help text — that flag is + * deliberately NOT fast-pathed here. + * + * Mirrors the intent of upstream 9router PR #2414 (fast-path help/version + * before expensive self-heal hooks), adapted to OmniRoute's Commander-based + * CLI where the equivalent expensive work is eager command registration + * rather than npm-install-based runtime self-healing. + * + * @param {string[]} argv - process.argv (node + script + args). + * @returns {boolean} + */ +export function isVersionFastPath(argv) { + const args = Array.isArray(argv) ? argv.slice(2) : []; + return args.length === 1 && (args[0] === "--version" || args[0] === "-V"); +} diff --git a/bin/omniroute.mjs b/bin/omniroute.mjs index e1ef7b0e9a..4d6720e097 100755 --- a/bin/omniroute.mjs +++ b/bin/omniroute.mjs @@ -4,6 +4,9 @@ * OmniRoute CLI entry point. * * Special bypasses (handled before Commander): + * --version / -V (alone) Fast-path: print the version and exit, skipping the + * tsx/esm + polyfill imports, env-file loading, and + * Commander's ~70-command registration entirely. * --mcp Start MCP server over stdio * reset-encrypted-columns Recovery tool for broken encrypted credentials * reset-password Reset the admin/management password @@ -19,6 +22,26 @@ import { isNativeBinaryCompatible } from "../scripts/build/native-binary-compat. import { getNodeRuntimeSupport, getNodeRuntimeWarning } from "./nodeRuntimeSupport.mjs"; import { getDefaultDataDir } from "./cli/data-dir.mjs"; import { shouldProvisionStorageKey } from "./cli/utils/storageKeyProvision.mjs"; +import { isVersionFastPath } from "./cli/utils/versionFastPath.mjs"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const ROOT = join(__dirname, ".."); + +// Fast-path a bare `--version`/`-V` query BEFORE the tsx/esm registration, the +// polyfill import, env-file loading, or Commander's command registration (~70 +// modules — DB, providers, OAuth, etc.) run. None of that work is needed to answer +// "what version is this" — mirrors upstream 9router PR #2414 (fast-path help/version +// ahead of expensive self-heal hooks), adapted to OmniRoute's Commander CLI where the +// equivalent expensive work is eager command registration rather than npm-install-based +// runtime self-healing. `--help` is intentionally NOT fast-pathed here: its output is +// generated dynamically from every registered subcommand, so skipping registration +// would truncate the help text instead of just speeding it up. +if (isVersionFastPath(process.argv)) { + const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")); + console.log(pkg.version); + process.exit(0); +} // Register tsx so dynamic imports of .ts source files (referenced as .js per // TypeScript conventions) resolve correctly. The build never emits .js for @@ -26,10 +49,6 @@ import { shouldProvisionStorageKey } from "./cli/utils/storageKeyProvision.mjs"; await import("tsx/esm"); await import("../open-sse/utils/setupPolyfill.ts"); -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const ROOT = join(__dirname, ".."); - // MCP stdio transport uses stdout exclusively for JSON-RPC messages. // Redirect console.log/warn to stderr early (before loadEnvFile and DB init) // so no startup output corrupts the protocol. diff --git a/tests/unit/cli-version-fastpath.test.ts b/tests/unit/cli-version-fastpath.test.ts new file mode 100644 index 0000000000..0ad5cd9f9a --- /dev/null +++ b/tests/unit/cli-version-fastpath.test.ts @@ -0,0 +1,56 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { isVersionFastPath } from "../../bin/cli/utils/versionFastPath.mjs"; + +const execFileAsync = promisify(execFile); + +// argv shape is [node, script, ...args] +const argv = (...args: string[]) => ["node", "omniroute", ...args]; + +test("fast-path selector: bare --version/-V select the fast path", () => { + assert.equal(isVersionFastPath(argv("--version")), true); + assert.equal(isVersionFastPath(argv("-V")), true); +}); + +test("fast-path selector: --help does NOT select the fast path (help text is dynamic)", () => { + assert.equal(isVersionFastPath(argv("--help")), false); + assert.equal(isVersionFastPath(argv("-h")), false); +}); + +test("fast-path selector: extra args or a subcommand alongside --version fall through", () => { + assert.equal(isVersionFastPath(argv("serve", "--version")), false); + assert.equal(isVersionFastPath(argv("--version", "extra")), false); + assert.equal(isVersionFastPath(argv("--lang", "en", "--version")), false); +}); + +test("fast-path selector: no args or a real command do not select the fast path", () => { + assert.equal(isVersionFastPath(argv()), false); + assert.equal(isVersionFastPath(argv("serve")), false); +}); + +test("fast-path selector: defensive on non-array input", () => { + // @ts-expect-error intentional bad input + assert.equal(isVersionFastPath(undefined), false); +}); + +test("omniroute CLI --version fast-path prints ONLY the version, skipping bootstrap output", async () => { + const pkg = JSON.parse( + readFileSync(join(process.cwd(), "package.json"), "utf8") + ) as { version: string }; + + const { stdout } = await execFileAsync(process.execPath, ["bin/omniroute.mjs", "--version"], { + cwd: process.cwd(), + env: { ...process.env, DATA_DIR: "" }, + }); + + // Before the fast-path, env-file loading (loadEnvFile) runs ahead of Commander and + // prints "Loaded env from ..." lines interleaved with the version — proving the full + // bootstrap (tsx/esm polyfill, env loading, ~70-command Commander registration) ran + // for a plain --version query. The fast-path must short-circuit before any of that, + // so stdout is EXACTLY the version string and nothing else. + assert.equal(stdout.trim(), pkg.version); +});