1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Initial support for validating extension engines.lens version (#2884)

This commit is contained in:
Jari Kolehmainen 2021-06-09 16:24:58 +03:00 committed by GitHub
parent d6e72ddc1c
commit 3847a91758
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 70 additions and 43 deletions

View File

@ -21,6 +21,7 @@
// App's common configuration for any process (main, renderer, build pipeline, etc.)
import path from "path";
import { SemVer } from "semver";
import packageInfo from "../../package.json";
import { defineGlobal } from "./utils/defineGlobal";
@ -66,4 +67,6 @@ export const apiKubePrefix = "/api-kube" as string; // k8s cluster apis
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues" as string;
export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI" as string;
export const supportUrl = "https://docs.k8slens.dev/latest/support/" as string;
export const appSemVer = new SemVer(packageInfo.version);
export const docsUrl = `https://docs.k8slens.dev/main/` as string;

View File

@ -93,6 +93,7 @@ describe("ExtensionDiscovery", () => {
id: path.normalize("node_modules/my-extension/package.json"),
isBundled: false,
isEnabled: false,
isCompatible: false,
manifest: {
name: "my-extension",
},

View File

@ -38,7 +38,8 @@ describe("lens extension", () => {
absolutePath: "/absolute/fake/",
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true
isEnabled: true,
isCompatible: true
});
});

View File

@ -30,10 +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";
import { isProduction } from "../common/vars";
export interface InstalledExtension {
@ -48,6 +51,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 isCompatible: boolean;
isEnabled: boolean;
}
@ -349,12 +353,17 @@ export class ExtensionDiscovery extends Singleton {
*/
protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise<InstalledExtension | null> {
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 = (isProduction && await fse.pathExists(npmPackage)) ? npmPackage : extensionDir;
let isCompatible = isBundled;
if (manifest.engines?.lens) {
isCompatible = semver.satisfies(appSemVer, manifest.engines.lens);
}
return {
id: installedManifestPath,
@ -362,7 +371,8 @@ export class ExtensionDiscovery extends Singleton {
manifestPath: installedManifestPath,
manifest,
isBundled,
isEnabled
isEnabled,
isCompatible
};
} catch (error) {
if (error.code === "ENOTDIR") {

View File

@ -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

View File

@ -301,7 +301,7 @@ export class ExtensionLoader extends Singleton {
for (const [extId, extension] of installedExtensions) {
const alreadyInit = this.instances.has(extId);
if (extension.isEnabled && !alreadyInit) {
if (extension.isCompatible && extension.isEnabled && !alreadyInit) {
try {
const LensExtensionClass = this.requireExtension(extension);

View File

@ -24,18 +24,17 @@ import { action, observable, makeObservable } from "mobx";
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
import logger from "../main/logger";
import type { ProtocolHandlerRegistration } from "./registries";
import type { PackageJson } from "type-fest";
import { Disposer, disposer } from "../common/utils";
export type LensExtensionId = string; // path to manifest (package.json)
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => 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();

View File

@ -40,7 +40,8 @@ describe("getPageUrl", () => {
absolutePath: "/absolute/fake/",
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true
isEnabled: true,
isCompatible: 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,
isCompatible: true
});
globalPageRegistry.add([
{

View File

@ -87,6 +87,7 @@ describe("protocol router tests", () => {
},
isBundled: false,
isEnabled: true,
isCompatible: true,
absolutePath: "/foo/bar",
});
const lpr = LensProtocolRouterMain.getInstance();
@ -165,6 +166,7 @@ describe("protocol router tests", () => {
},
isBundled: false,
isEnabled: true,
isCompatible: true,
absolutePath: "/foo/bar",
});
@ -206,6 +208,7 @@ describe("protocol router tests", () => {
},
isBundled: false,
isEnabled: true,
isCompatible: true,
absolutePath: "/foo/bar",
});
@ -230,6 +233,7 @@ describe("protocol router tests", () => {
},
isBundled: false,
isEnabled: true,
isCompatible: true,
absolutePath: "/foo/bar",
});

View File

@ -76,7 +76,8 @@ describe("Extensions", () => {
absolutePath: "/absolute/path",
manifestPath: "/symlinked/path/package.json",
isBundled: false,
isEnabled: true
isEnabled: true,
isCompatible: true
});
});

View File

@ -11,6 +11,10 @@
color: var(--colorOk);
}
.invalid {
color: var(--colorWarning);
}
.title {
margin-bottom: 0!important;
}

View File

@ -39,14 +39,18 @@ interface Props {
uninstall: (extension: InstalledExtension) => void;
}
function getStatus(isEnabled: boolean) {
return isEnabled ? "Enabled" : "Disabled";
function getStatus(extension: InstalledExtension) {
if (!extension.isCompatible) {
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, isCompatible, manifest } = extension;
const { name, description, version } = manifest;
const isUninstalling = ExtensionInstallationStateStore.isExtensionUninstalling(id);
@ -102,12 +106,14 @@ export const InstalledExtensions = observer(({ extensions, uninstall, enable, di
),
version,
status: (
<div className={cssNames({[styles.enabled]: getStatus(isEnabled) == "Enabled"})}>
{getStatus(isEnabled)}
<div className={cssNames({[styles.enabled]: isEnabled, [styles.invalid]: !isCompatible})}>
{getStatus(extension)}
</div>
),
actions: (
<MenuActions usePortal toolbar={false}>
{ isCompatible && (
<>
{isEnabled ? (
<MenuItem
disabled={isUninstalling}
@ -125,6 +131,9 @@ export const InstalledExtensions = observer(({ extensions, uninstall, enable, di
<span className="title" aria-disabled={isUninstalling}>Enable</span>
</MenuItem>
)}
</>
)}
<MenuItem
disabled={isUninstalling}
onClick={() => uninstall(extension)}