diff --git a/.changeset/gentle-points-live.md b/.changeset/gentle-points-live.md new file mode 100644 index 000000000..963e8120d --- /dev/null +++ b/.changeset/gentle-points-live.md @@ -0,0 +1,7 @@ +--- +"@opennextjs/aws": patch +--- + +refactor `compareSemver`. + +It now takes a comparison operator and return a boolean, i.e. `compareSemver("1.0", "<=", "1.2.3")` diff --git a/packages/open-next/src/build/compileCache.ts b/packages/open-next/src/build/compileCache.ts index 0959ccb47..fa30f2e3a 100644 --- a/packages/open-next/src/build/compileCache.ts +++ b/packages/open-next/src/build/compileCache.ts @@ -17,8 +17,11 @@ export function compileCache( const ext = format === "cjs" ? "cjs" : "mjs"; const outFile = path.join(options.buildDir, `cache.${ext}`); - const isAfter15 = - buildHelper.compareSemver(options.nextVersion, "15.0.0") >= 0; + const isAfter15 = buildHelper.compareSemver( + options.nextVersion, + ">=", + "15.0.0", + ); buildHelper.esbuildSync( { diff --git a/packages/open-next/src/build/createImageOptimizationBundle.ts b/packages/open-next/src/build/createImageOptimizationBundle.ts index 5686d4bda..6dc787802 100644 --- a/packages/open-next/src/build/createImageOptimizationBundle.ts +++ b/packages/open-next/src/build/createImageOptimizationBundle.ts @@ -36,7 +36,7 @@ export async function createImageOptimizationBundle( }), ]; - if (buildHelper.compareSemver(options.nextVersion, "14.1.1") >= 0) { + if (buildHelper.compareSemver(options.nextVersion, ">=", "14.1.1")) { plugins.push( openNextReplacementPlugin({ name: "opennext-14.1.1-image-optimization", diff --git a/packages/open-next/src/build/createServerBundle.ts b/packages/open-next/src/build/createServerBundle.ts index dc0d0a88f..770a8aa01 100644 --- a/packages/open-next/src/build/createServerBundle.ts +++ b/packages/open-next/src/build/createServerBundle.ts @@ -190,15 +190,21 @@ async function generateBundle( // Next.js app. const disableNextPrebundledReact = - buildHelper.compareSemver(options.nextVersion, "13.5.1") >= 0 || - buildHelper.compareSemver(options.nextVersion, "13.4.1") <= 0; + buildHelper.compareSemver(options.nextVersion, ">=", "13.5.1") || + buildHelper.compareSemver(options.nextVersion, "<=", "13.4.1"); const overrides = fnOptions.override ?? {}; - const isBefore13413 = - buildHelper.compareSemver(options.nextVersion, "13.4.13") <= 0; - const isAfter141 = - buildHelper.compareSemver(options.nextVersion, "14.1") >= 0; + const isBefore13413 = buildHelper.compareSemver( + options.nextVersion, + "<=", + "13.4.13", + ); + const isAfter141 = buildHelper.compareSemver( + options.nextVersion, + ">=", + "14.1", + ); const disableRouting = isBefore13413 || config.middleware?.external; diff --git a/packages/open-next/src/build/helper.ts b/packages/open-next/src/build/helper.ts index ec334de93..57f845112 100644 --- a/packages/open-next/src/build/helper.ts +++ b/packages/open-next/src/build/helper.ts @@ -288,33 +288,65 @@ export function getNextVersion(appPath: string): string { return version.split("-")[0]; } +export type SemverOp = "=" | ">=" | "<=" | ">" | "<"; + /** * Compare two semver versions. * * @param v1 - First version. Can be "latest", otherwise it should be a valid semver version in the format of `major.minor.patch`. Usually is the next version from the package.json without canary suffix. If minor or patch are missing, they are considered 0. * @param v2 - Second version. Should not be "latest", it should be a valid semver version in the format of `major.minor.patch`. If minor or patch are missing, they are considered 0. * @example - * compareSemver("1.0.0", "1.0.0") // 0 + * compareSemver("2.0.0", ">=", "1.0.0") === true */ -export function compareSemver(v1: string, v2: string): number { - if (v1 === "latest") return 1; - if (/^[^\d]/.test(v1)) { - // biome-ignore lint/style/noParameterAssign: - v1 = v1.substring(1); - } - if (/^[^\d]/.test(v2)) { - // biome-ignore lint/style/noParameterAssign: - v2 = v2.substring(1); - } - const [major1, minor1 = 0, patch1 = 0] = v1.split(".").map(Number); - const [major2, minor2 = 0, patch2 = 0] = v2.split(".").map(Number); - if (Number.isNaN(major1) || Number.isNaN(major2)) { - throw new Error("The major version is required."); +export function compareSemver( + v1: string, + operator: SemverOp, + v2: string, +): boolean { + // - = 0 when versions are equal + // - > 0 if v1 > v2 + // - < 0 if v2 > v1 + let versionDiff = 0; + if (v1 === "latest") { + versionDiff = 1; + } else { + if (/^[^\d]/.test(v1)) { + // biome-ignore lint/style/noParameterAssign: + v1 = v1.substring(1); + } + if (/^[^\d]/.test(v2)) { + // biome-ignore lint/style/noParameterAssign: + v2 = v2.substring(1); + } + const [major1, minor1 = 0, patch1 = 0] = v1.split(".").map(Number); + const [major2, minor2 = 0, patch2 = 0] = v2.split(".").map(Number); + if (Number.isNaN(major1) || Number.isNaN(major2)) { + throw new Error("The major version is required."); + } + + if (major1 !== major2) { + versionDiff = major1 - major2; + } else if (minor1 !== minor2) { + versionDiff = minor1 - minor2; + } else if (patch1 !== patch2) { + versionDiff = patch1 - patch2; + } } - if (major1 !== major2) return major1 - major2; - if (minor1 !== minor2) return minor1 - minor2; - return patch1 - patch2; + switch (operator) { + case "=": + return versionDiff === 0; + case ">=": + return versionDiff >= 0; + case "<=": + return versionDiff <= 0; + case ">": + return versionDiff > 0; + case "<": + return versionDiff < 0; + default: + throw new Error(`Unsupported operator: ${operator}`); + } } export function copyOpenNextConfig( diff --git a/packages/open-next/src/build/patch/codePatcher.ts b/packages/open-next/src/build/patch/codePatcher.ts index 7802bdbd3..ad93ca0d4 100644 --- a/packages/open-next/src/build/patch/codePatcher.ts +++ b/packages/open-next/src/build/patch/codePatcher.ts @@ -103,20 +103,20 @@ export function extractVersionedField( if ( before && after && - buildHelper.compareSemver(version, before) <= 0 && - buildHelper.compareSemver(version, after) >= 0 + buildHelper.compareSemver(version, "<=", before) && + buildHelper.compareSemver(version, ">=", after) ) { result.push(field.field); } else if ( before && !after && - buildHelper.compareSemver(version, before) <= 0 + buildHelper.compareSemver(version, "<=", before) ) { result.push(field.field); } else if ( after && !before && - buildHelper.compareSemver(version, after) >= 0 + buildHelper.compareSemver(version, ">=", after) ) { result.push(field.field); } diff --git a/packages/tests-unit/tests/build/helper.test.ts b/packages/tests-unit/tests/build/helper.test.ts index 87a51de84..3c5d4717e 100644 --- a/packages/tests-unit/tests/build/helper.test.ts +++ b/packages/tests-unit/tests/build/helper.test.ts @@ -2,33 +2,66 @@ import { compareSemver } from "@opennextjs/aws/build/helper.js"; // We don't need to test canary versions, they are stripped out describe("compareSemver", () => { - it("should return 0 when versions are equal", () => { - expect(compareSemver("1.0.0", "1.0.0")).toBe(0); + test("=", () => { + expect(compareSemver("1.0.0", "=", "1.0.0")).toBe(true); + expect(compareSemver("1.1.0", "=", "1.0.0")).toBe(false); + expect(compareSemver("1.0.1", "=", "1.0.0")).toBe(false); }); - it("should return 1 when first version is greater", () => { - expect(compareSemver("1.0.1", "1.0.0")).toBe(1); + it(">", () => { + expect(compareSemver("1.0.1", ">", "1.0.0")).toBe(true); + expect(compareSemver("1.0.0", ">", "1.0.0")).toBe(false); + expect(compareSemver("1.0.0", ">", "1.0.1")).toBe(false); }); - it("should return -1 when first version is smaller", () => { - expect(compareSemver("1.0.0", "1.0.1")).toBe(-1); + it(">=", () => { + expect(compareSemver("1.0.1", ">=", "1.0.0")).toBe(true); + expect(compareSemver("1.0.0", ">=", "1.0.0")).toBe(true); + expect(compareSemver("1.0.0", ">=", "1.0.1")).toBe(false); }); - it("should work with latest", () => { - expect(compareSemver("latest", "1.0.0")).toBe(1); + it("<", () => { + expect(compareSemver("1.0.0", "<", "1.0.1")).toBe(true); + expect(compareSemver("1.0.0", "<", "1.0.0")).toBe(false); + expect(compareSemver("1.0.0", "<", "0.0.1")).toBe(false); }); - it("should work with incomplete version for patch", () => { - expect(compareSemver("14.1.0", "14.1")).toBe(0); - expect(compareSemver("14.1", "14.1.0")).toBe(0); + it("<=", () => { + expect(compareSemver("1.0.0", "<=", "1.0.1")).toBe(true); + expect(compareSemver("1.0.0", "<=", "1.0.0")).toBe(true); + expect(compareSemver("1.0.0", "<=", "0.0.1")).toBe(false); }); - it("should work with incomplete version for minor", () => { - expect(compareSemver("14.0.0", "14")).toBe(0); + test("latest", () => { + expect(compareSemver("latest", "=", "1.0.0")).toBe(false); + expect(compareSemver("latest", ">=", "1.0.0")).toBe(true); + expect(compareSemver("latest", ">", "1.0.0")).toBe(true); + expect(compareSemver("latest", "<=", "1.0.0")).toBe(false); + expect(compareSemver("latest", "<", "1.0.0")).toBe(false); }); - it("should throw if the major version is missing", () => { - expect(() => compareSemver("incorrect", "14.0.0")).toThrow(); - expect(() => compareSemver("14.0.0", "latest")).toThrow(); + test("incomplete version for patch", () => { + expect(compareSemver("14.1.0", "=", "14.1")).toBe(true); + expect(compareSemver("14.1", "=", "14.1.0")).toBe(true); + }); + + test("incomplete version for minor", () => { + expect(compareSemver("14.0.0", "=", "14")).toBe(true); + expect(compareSemver("14", "=", "14.0.0")).toBe(true); + }); + + test("throw if the major version is missing", () => { + expect(() => compareSemver("incorrect", "=", "14.0.0")).toThrow(); + expect(() => compareSemver("14.0.0", "=", "latest")).toThrow(); + }); + + test("throw if the major version is missing", () => { + expect(() => compareSemver("incorrect", "=", "14.0.0")).toThrow(); + expect(() => compareSemver("14.0.0", "=", "latest")).toThrow(); + }); + + test("throw if the operator is not supported", () => { + expect(() => compareSemver("14.0.0", "==" as any, "14.0.0")).toThrow(); + expect(() => compareSemver("14.0.0", "!=" as any, "14.0.0")).toThrow(); }); });