diff --git a/src/extensions/__tests__/is-compatible-extension.test.ts b/src/extensions/__tests__/is-compatible-extension.test.ts index 49de17b2cc..13eb7bee29 100644 --- a/src/extensions/__tests__/is-compatible-extension.test.ts +++ b/src/extensions/__tests__/is-compatible-extension.test.ts @@ -12,42 +12,78 @@ import type { LensExtensionManifest } from "../lens-extension"; describe("Extension/App versions compatibility check", () => { it("is compatible with exact version matching", () => { expect(isCompatibleExtension({ - appSemVer: semver.coerce("5.5.0"), // current app version - })(getExtensionManifestMock({ - lensEngine: "5.5.0", // requested app version by extension ("semver"-format) - }))).toBeTruthy(); - }); - - it("is compatible with higher patch-versions of main app", () => { - expect(isCompatibleExtension({ - appSemVer: semver.coerce("5.5.5"), // for patch-versions - })(getExtensionManifestMock({ - lensEngine: "^5.5.0", - }))).toBeTruthy(); - }); - - it("supports short versions format for engines.lens", () => { - const isCompatible = isCompatibleExtension({ appSemVer: semver.coerce("5.5.0"), })(getExtensionManifestMock({ - lensEngine: "5.5", - })); - - expect(isCompatible).toBeTruthy(); + lensEngine: "5.5.0", + }))).toBeTruthy(); }); - it("supporting `manifest.engines.lens='*'` to match any base-app version", () => { + it("is compatible with upper %PATCH versions of base app", () => { expect(isCompatibleExtension({ - appSemVer: semver.coerce("1.0.0"), + appSemVer: semver.coerce("5.5.5"), })(getExtensionManifestMock({ - lensEngine: "*", + lensEngine: "5.5.0", }))).toBeTruthy(); + }); + + it("is compatible with upper %MINOR version of base app", () => { + expect(isCompatibleExtension({ + appSemVer: semver.coerce("5.6.0"), + })(getExtensionManifestMock({ + lensEngine: "5.5.0", + }))).toBeTruthy(); + }); + + it("is not compatible with upper %MAJOR version of base app", () => { + expect(isCompatibleExtension({ + appSemVer: semver.coerce("5.5.0"), // current lens-version + })(getExtensionManifestMock({ + lensEngine: "6.0.0", + }))).toBeFalsy(); // extension with lens@6.0 is not compatible with app@5.5 expect(isCompatibleExtension({ - appSemVer: semver.coerce("2.0.0"), + appSemVer: semver.coerce("6.0.0"), // current lens-version })(getExtensionManifestMock({ - lensEngine: "*", - }))).toBeTruthy(); + lensEngine: "5.5.0", + }))).toBeFalsy(); // extension with lens@5.5 is not compatible with app@6.0 + }); + + describe("supported formats for manifest.engines.lens", () => { + it("short version format for engines.lens", () => { + expect(isCompatibleExtension({ + appSemVer: semver.coerce("5.5.0"), + })(getExtensionManifestMock({ + lensEngine: "5.5", + }))).toBeTruthy(); + }); + + it("validates version and throws if incorrect format", () => { + expect(() => isCompatibleExtension({ + appSemVer: semver.coerce("1.0.0"), + })(getExtensionManifestMock({ + lensEngine: "1.0", + }))).not.toThrow(); + + expect(() => isCompatibleExtension({ + appSemVer: semver.coerce("1.0.0"), + })(getExtensionManifestMock({ + lensEngine: "^1.0", + }))).not.toThrow(); + + expect(() => isCompatibleExtension({ + appSemVer: semver.coerce("1.0.0"), + })(getExtensionManifestMock({ + lensEngine: ">=2.0", + }))).toThrow(/Invalid format/i); + }); + + it("'*' cannot be used for any version matching (at least in the prefix)", () => { + expect(() => isCompatibleExtension({ + appSemVer: semver.coerce("1.0.0"), + })(getExtensionManifestMock({ + lensEngine: "*", + }))).toThrowError(/Invalid format/i); + }); }); }); diff --git a/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.ts b/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.ts index 545b3c89e3..52d77cb452 100644 --- a/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.ts +++ b/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.ts @@ -12,7 +12,24 @@ interface Dependencies { export const isCompatibleExtension = ({ appSemVer }: Dependencies): ((manifest: LensExtensionManifest) => boolean) => { return (manifest: LensExtensionManifest): boolean => { const { raw: appVersion } = appSemVer; - const supportedVersionsByExtension: string = semver.validRange(manifest.engines.lens); + const manifestLensEngine = manifest.engines.lens; + const validVersion = manifestLensEngine.match(/^[\^0-9]\d*\.\d+\b/); // must start from ^ or number + + if (!validVersion) { + const errorInfo = [ + `Invalid format for "manifest.engines.lens"="${manifestLensEngine}"`, + `Range versions could be specified only starting with '^'.`, + `Otherwise it's recommended to use plain %MAJOR.%MINOR to match with supported Lens version.`, + ].join("\n"); + + throw new Error(errorInfo); + } + + const { major: extMajor, minor: extMinor } = semver.coerce(manifestLensEngine, { + loose: true, + includePrerelease: false, + }); + const supportedVersionsByExtension: string = semver.validRange(`^${extMajor}.${extMinor}`); return semver.satisfies(appVersion, supportedVersionsByExtension, { loose: true,