mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-14 11:12:17 +03:00
fix(deps): pin next to an exact version so a fresh upstream release cannot break installs
The published package ships a PREBUILT .next directory, and next start reads build manifests whose shape changes between minors — so the runtime version must be the one that produced the build. With "next": "^16.2.11", every `npm i -g omniroute` resolved whatever Next was latest at INSTALL time. Next 16.3.1 was published 2026-08-13T22:45Z and added `validationLevel` to its server config schema (0 occurrences in 16.2.12, 74 in 16.3.1). Any install after that timestamp boots a 16.2.12-built .next on the 16.3.1 runtime and crashes immediately: TypeError: Cannot read properties of undefined (reading 'validationLevel') Reproduced on the 192.168.0.17 VPS: a fresh global install of the 3.8.50 tarball crashed in a restart loop; the previous install (next 16.3.0) is healthy, and nothing in this repo changed between them. Published 3.8.49 carries the same range, so new user installs are affected too. react/react-dom were already pinned exactly for this reason; this extends the invariant to next, syncs the lockfile range, and adds tests/unit/next-version-pinned.test.ts as the regression guard (asserts the build-coupled deps are exact and that package.json matches the lockfile version the build actually uses).
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -56,7 +56,7 @@
|
||||
"material-symbols": "^0.45.2",
|
||||
"mermaid": "^11.15.0",
|
||||
"monaco-editor": "^0.56.0",
|
||||
"next": "^16.2.11",
|
||||
"next": "16.2.12",
|
||||
"next-intl": "^4.12.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"node-machine-id": "^1.1.12",
|
||||
|
||||
@@ -298,7 +298,7 @@
|
||||
"material-symbols": "^0.45.2",
|
||||
"mermaid": "^11.15.0",
|
||||
"monaco-editor": "^0.56.0",
|
||||
"next": "^16.2.11",
|
||||
"next": "16.2.12",
|
||||
"next-intl": "^4.12.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"node-machine-id": "^1.1.12",
|
||||
|
||||
64
tests/unit/next-version-pinned.test.ts
Normal file
64
tests/unit/next-version-pinned.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* next-version-pinned.test.ts — `next` must be pinned to an exact version.
|
||||
*
|
||||
* The published package ships a PREBUILT `.next` directory. That build output is
|
||||
* tightly coupled to the exact Next.js runtime that produced it: `next start`
|
||||
* reads build manifests whose shape changes between minors. With a caret range,
|
||||
* `npm i -g omniroute` resolves whatever Next is latest at INSTALL time, so a
|
||||
* fresh upstream release silently breaks every new install even though nothing
|
||||
* in this repo changed.
|
||||
*
|
||||
* That is not hypothetical: Next 16.3.1 (published 2026-08-13T22:45Z) added
|
||||
* `validationLevel` to its server config schema, and a `.next` built by 16.2.12
|
||||
* crashes at boot with "Cannot read properties of undefined (reading
|
||||
* 'validationLevel')" — the symbol has 0 occurrences in 16.2.12 and 74 in
|
||||
* 16.3.1. Range `^16.2.11` picked it up on the VPS install (2026-08-14).
|
||||
*
|
||||
* `react`/`react-dom` are already pinned exactly for the same reason; this test
|
||||
* extends that invariant to `next` and keeps package.json in sync with the
|
||||
* lockfile version the build actually uses.
|
||||
*/
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const ROOT = new URL("../../", import.meta.url);
|
||||
const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[\w.]+)?$/;
|
||||
|
||||
/** Deps whose published artifact is coupled to the exact installed runtime. */
|
||||
const MUST_BE_EXACT = ["next", "react", "react-dom"];
|
||||
|
||||
async function readJson(relative: string): Promise<Record<string, unknown>> {
|
||||
return JSON.parse(await readFile(fileURLToPath(new URL(relative, ROOT)), "utf8"));
|
||||
}
|
||||
|
||||
test("build-coupled dependencies are pinned to exact versions", async () => {
|
||||
const pkg = await readJson("package.json");
|
||||
const deps = (pkg.dependencies ?? {}) as Record<string, string>;
|
||||
|
||||
const ranged = MUST_BE_EXACT.filter((name) => deps[name] && !EXACT_VERSION.test(deps[name]));
|
||||
|
||||
assert.deepEqual(
|
||||
ranged,
|
||||
[],
|
||||
"these ship a prebuilt artifact and must be pinned exactly (a range lets a fresh " +
|
||||
`upstream release break new installs): ${ranged.map((n) => `${n}@${deps[n]}`).join(", ")}`
|
||||
);
|
||||
});
|
||||
|
||||
test("pinned next version matches the lockfile version the build uses", async () => {
|
||||
const pkg = await readJson("package.json");
|
||||
const lock = await readJson("package-lock.json");
|
||||
|
||||
const declared = ((pkg.dependencies ?? {}) as Record<string, string>).next;
|
||||
const packages = (lock.packages ?? {}) as Record<string, { version?: string }>;
|
||||
const locked = packages["node_modules/next"]?.version;
|
||||
|
||||
assert.ok(locked, "next missing from package-lock.json");
|
||||
assert.equal(
|
||||
declared,
|
||||
locked,
|
||||
`package.json declares next@${declared} but the lockfile builds with next@${locked}`
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user