diff --git a/src/common/vars.ts b/src/common/vars.ts index 94ded7028e..aa965e4721 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -69,7 +69,8 @@ export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/enQtOTc5 export const supportUrl = "https://docs.k8slens.dev/latest/support/"; // This explicitly ignores the prerelease info on the package version -const { major, minor, patch } = new SemVer(packageInfo.version); +export const appSemVer = new SemVer(packageInfo.version); +const { major, minor, patch } = appSemVer; const mmpVersion = [major, minor, patch].join("."); const docsVersion = isProduction ? `v${mmpVersion}` : "latest"; diff --git a/src/extensions/__tests__/lens-extension.test.ts b/src/extensions/__tests__/lens-extension.test.ts index 57ae8b8a68..c415c1c37a 100644 --- a/src/extensions/__tests__/lens-extension.test.ts +++ b/src/extensions/__tests__/lens-extension.test.ts @@ -38,7 +38,8 @@ describe("lens extension", () => { absolutePath: "/absolute/fake/", manifestPath: "/this/is/fake/package.json", isBundled: false, - isEnabled: true + isEnabled: true, + isValid: true }); }); diff --git a/src/extensions/extension-discovery.ts b/src/extensions/extension-discovery.ts index 8a0929d016..99c9847e44 100644 --- a/src/extensions/extension-discovery.ts +++ b/src/extensions/extension-discovery.ts @@ -30,11 +30,13 @@ import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } fr import { Singleton, toJS } from "../common/utils"; import logger from "../main/logger"; import { ExtensionInstallationStateStore } from "../renderer/components/+extensions/extension-install.store"; -import { extensionInstaller, PackageJson } from "./extension-installer"; +import { extensionInstaller } from "./extension-installer"; import { ExtensionsStore } from "./extensions-store"; import { ExtensionLoader } from "./extension-loader"; import type { LensExtensionId, LensExtensionManifest } from "./lens-extension"; - +import type { PackageJson } from "type-fest"; +import semver from "semver"; +import { appSemVer } from "../common/vars"; export interface InstalledExtension { id: LensExtensionId; @@ -47,6 +49,7 @@ export interface InstalledExtension { // Absolute to the symlinked package.json file readonly manifestPath: string; readonly isBundled: boolean; // defined in project root's package.json + readonly isValid: boolean; isEnabled: boolean; } @@ -348,12 +351,17 @@ export class ExtensionDiscovery extends Singleton { */ protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise { try { - const manifest = await fse.readJson(manifestPath); + const manifest = await fse.readJson(manifestPath) as LensExtensionManifest; const installedManifestPath = this.getInstalledManifestPath(manifest.name); const isEnabled = isBundled || ExtensionsStore.getInstance().isEnabled(installedManifestPath); const extensionDir = path.dirname(manifestPath); const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`); const absolutePath = (await fse.pathExists(npmPackage)) ? npmPackage : extensionDir; + let isValid = false; + + if (manifest.engines?.lens) { + isValid = semver.satisfies(appSemVer, manifest.engines.lens); + } return { id: installedManifestPath, @@ -361,7 +369,8 @@ export class ExtensionDiscovery extends Singleton { manifestPath: installedManifestPath, manifest, isBundled, - isEnabled + isEnabled, + isValid }; } catch (error) { if (error.code === "ENOTDIR") { diff --git a/src/extensions/extension-installer.ts b/src/extensions/extension-installer.ts index 797da91c5f..57e67183bf 100644 --- a/src/extensions/extension-installer.ts +++ b/src/extensions/extension-installer.ts @@ -25,17 +25,10 @@ import fs from "fs-extra"; import path from "path"; import logger from "../main/logger"; import { extensionPackagesRoot } from "./extension-loader"; +import type { PackageJson } from "type-fest"; const logModule = "[EXTENSION-INSTALLER]"; -type Dependencies = { - [name: string]: string; -}; - -// Type for the package.json file that is written by ExtensionInstaller -export type PackageJson = { - dependencies: Dependencies; -}; /** * Installs dependencies for extensions diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index b3bec33179..bffc762c3b 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -299,7 +299,7 @@ export class ExtensionLoader extends Singleton { for (const [extId, extension] of installedExtensions) { const alreadyInit = this.instances.has(extId); - if (extension.isEnabled && !alreadyInit) { + if (extension.isValid && extension.isEnabled && !alreadyInit) { try { const LensExtensionClass = this.requireExtension(extension); diff --git a/src/extensions/lens-extension.ts b/src/extensions/lens-extension.ts index 6f03829121..7b927def01 100644 --- a/src/extensions/lens-extension.ts +++ b/src/extensions/lens-extension.ts @@ -25,17 +25,16 @@ import { FilesystemProvisionerStore } from "../main/extension-filesystem"; import logger from "../main/logger"; import type { ProtocolHandlerRegistration } from "./registries"; import { disposer } from "../common/utils"; +import type { PackageJson } from "type-fest"; export type LensExtensionId = string; // path to manifest (package.json) export type LensExtensionConstructor = new (...args: ConstructorParameters) => LensExtension; -export interface LensExtensionManifest { +export interface LensExtensionManifest extends PackageJson { name: string; version: string; - description?: string; main?: string; // path to %ext/dist/main.js renderer?: string; // path to %ext/dist/renderer.js - lens?: object; // fixme: add more required fields for validation } export const Disposers = Symbol(); diff --git a/src/extensions/registries/__tests__/page-registry.test.ts b/src/extensions/registries/__tests__/page-registry.test.ts index 8c6582b437..78a7bbe1da 100644 --- a/src/extensions/registries/__tests__/page-registry.test.ts +++ b/src/extensions/registries/__tests__/page-registry.test.ts @@ -40,7 +40,8 @@ describe("getPageUrl", () => { absolutePath: "/absolute/fake/", manifestPath: "/this/is/fake/package.json", isBundled: false, - isEnabled: true + isEnabled: true, + isValid: true }); globalPageRegistry.add({ id: "page-with-params", @@ -107,7 +108,8 @@ describe("globalPageRegistry", () => { absolutePath: "/absolute/fake/", manifestPath: "/this/is/fake/package.json", isBundled: false, - isEnabled: true + isEnabled: true, + isValid: true }); globalPageRegistry.add([ { diff --git a/src/main/protocol-handler/__test__/router.test.ts b/src/main/protocol-handler/__test__/router.test.ts index 414881dec1..c5a95f4a79 100644 --- a/src/main/protocol-handler/__test__/router.test.ts +++ b/src/main/protocol-handler/__test__/router.test.ts @@ -87,6 +87,7 @@ describe("protocol router tests", () => { }, isBundled: false, isEnabled: true, + isValid: true, absolutePath: "/foo/bar", }); const lpr = LensProtocolRouterMain.getInstance(); @@ -164,6 +165,7 @@ describe("protocol router tests", () => { }, isBundled: false, isEnabled: true, + isValid: true, absolutePath: "/foo/bar", }); @@ -204,6 +206,7 @@ describe("protocol router tests", () => { }, isBundled: false, isEnabled: true, + isValid: true, absolutePath: "/foo/bar", }); @@ -228,6 +231,7 @@ describe("protocol router tests", () => { }, isBundled: false, isEnabled: true, + isValid: true, absolutePath: "/foo/bar", }); diff --git a/src/renderer/components/+extensions/__tests__/extensions.test.tsx b/src/renderer/components/+extensions/__tests__/extensions.test.tsx index 40a523d8a3..537d8d4a99 100644 --- a/src/renderer/components/+extensions/__tests__/extensions.test.tsx +++ b/src/renderer/components/+extensions/__tests__/extensions.test.tsx @@ -76,7 +76,8 @@ describe("Extensions", () => { absolutePath: "/absolute/path", manifestPath: "/symlinked/path/package.json", isBundled: false, - isEnabled: true + isEnabled: true, + isValid: true }); }); diff --git a/src/renderer/components/+extensions/installed-extensions.module.css b/src/renderer/components/+extensions/installed-extensions.module.css index 831ece9024..b685f029fe 100644 --- a/src/renderer/components/+extensions/installed-extensions.module.css +++ b/src/renderer/components/+extensions/installed-extensions.module.css @@ -11,6 +11,10 @@ color: var(--colorOk); } +.invalid { + color: var(--colorWarning); +} + .title { margin-bottom: 0!important; } @@ -22,4 +26,4 @@ .frozenRow { @apply opacity-30 pointer-events-none; -} \ No newline at end of file +} diff --git a/src/renderer/components/+extensions/installed-extensions.tsx b/src/renderer/components/+extensions/installed-extensions.tsx index f896f410fd..b336e7160a 100644 --- a/src/renderer/components/+extensions/installed-extensions.tsx +++ b/src/renderer/components/+extensions/installed-extensions.tsx @@ -39,14 +39,18 @@ interface Props { uninstall: (extension: InstalledExtension) => void; } -function getStatus(isEnabled: boolean) { - return isEnabled ? "Enabled" : "Disabled"; +function getStatus(extension: InstalledExtension) { + if (!extension.isValid) { + return "Incompatible"; + } + + return extension.isEnabled ? "Enabled" : "Disabled"; } export const InstalledExtensions = observer(({ extensions, uninstall, enable, disable }: Props) => { const filters = [ (extension: InstalledExtension) => extension.manifest.name, - (extension: InstalledExtension) => getStatus(extension.isEnabled), + (extension: InstalledExtension) => getStatus(extension), (extension: InstalledExtension) => extension.manifest.version, ]; @@ -87,7 +91,7 @@ export const InstalledExtensions = observer(({ extensions, uninstall, enable, di const data = useMemo( () => { return extensions.map(extension => { - const { id, isEnabled, manifest } = extension; + const { id, isEnabled, isValid, manifest } = extension; const { name, description, version } = manifest; const isUninstalling = ExtensionInstallationStateStore.isExtensionUninstalling(id); @@ -102,29 +106,34 @@ export const InstalledExtensions = observer(({ extensions, uninstall, enable, di ), version, status: ( -
- {getStatus(isEnabled)} +
+ {getStatus(extension)}
), actions: ( - {isEnabled ? ( - disable(id)} - > - - Disable - - ) : ( - enable(id)} - > - - Enable - + { isValid && ( + <> + {isEnabled ? ( + disable(id)} + > + + Disable + + ) : ( + enable(id)} + > + + Enable + + )} + )} + uninstall(extension)}