Files
OmniRoute/tests/unit/version-compare-client-safe.test.ts
Diego Rodrigues de Sa e Souza effddc6a0e fix(build): split pure semver helpers into versionCompare so the Kimi client banner gate stops dragging child_process into the browser bundle
The KimiSponsorBanner (use client) version gate imported isNewer/normalizeVersion
from versionCheck.ts, whose top-level 'import { execFile } from child_process'
cannot be tree-shaken out of a client bundle — Turbopack next build failed with 33
'Module not found' errors (child_process, fs, net, dns, module). Move the pure
helpers to a dependency-free versionCompare.ts; versionCheck.ts re-exports them.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-22 00:07:19 -03:00

64 lines
3.0 KiB
TypeScript

// Regression guard for the base-red that broke the Turbopack `next build`:
// kimiSponsorBannerGate.ts (pulled into the "use client" KimiSponsorBanner
// bundle) imported the semver helpers from `versionCheck.ts`, whose top-level
// `import { execFile } from "child_process"` cannot be tree-shaken out of a
// client bundle → "Module not found: Can't resolve 'child_process'".
//
// The fix moved the pure helpers into `versionCompare.ts` (dependency-free) and
// pointed the client-reachable gate at it. These assertions lock that in so the
// server module can never sneak back into the client bundle via this path.
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
const here = dirname(fileURLToPath(import.meta.url));
const src = (p: string) => readFileSync(resolve(here, "../../", p), "utf8");
const COMPARE = "src/lib/system/versionCompare.ts";
const GATE = "src/app/(dashboard)/dashboard/kimiSponsorBannerGate.ts";
test("versionCompare.ts is dependency-free (no server-only imports)", () => {
// Match actual import/require statements, not the word appearing in the
// module's own docstring (which explains WHY it avoids these).
const importLines = src(COMPARE)
.split("\n")
.filter((l) => /^\s*import\b/.test(l) || /\brequire\s*\(/.test(l));
const joined = importLines.join("\n");
for (const forbidden of ["child_process", "@/lib/services/installers", "@/shared/utils/logger", '"util"']) {
assert.ok(
!joined.includes(forbidden),
`versionCompare.ts must stay client-safe — found forbidden import ${forbidden}`
);
}
// The file must in fact have no import statements at all (fully self-contained).
assert.equal(importLines.length, 0, "versionCompare.ts should have zero imports");
});
test("the client-reachable Kimi banner gate imports helpers from versionCompare, not versionCheck", () => {
const code = src(GATE);
assert.match(code, /from "@\/lib\/system\/versionCompare"/);
assert.ok(
!/from "@\/lib\/system\/versionCheck"/.test(code),
"kimiSponsorBannerGate.ts must NOT import from versionCheck (drags child_process into the client bundle)"
);
});
test("versionCompare exports working isNewer/normalizeVersion", async () => {
const m = await import("../../src/lib/system/versionCompare.ts");
assert.deepEqual(m.normalizeVersion("v3.8.60"), [3, 8, 60]);
assert.equal(m.normalizeVersion("garbage"), null);
assert.equal(m.isNewer("3.8.61", "3.8.60"), true);
assert.equal(m.isNewer("3.8.60", "3.8.60"), false);
assert.equal(m.isNewer(null, "3.8.60"), false);
});
test("versionCheck still re-exports the helpers (back-compat for server importers)", async () => {
const m = await import("../../src/lib/system/versionCheck.ts");
assert.equal(typeof m.isNewer, "function");
assert.equal(typeof m.normalizeVersion, "function");
assert.equal(m.isNewer("3.9.0", "3.8.60"), true);
});