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

initial support for checking engines.lens version

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2021-05-27 12:48:48 +03:00
parent 57c87a2e71
commit ba22372c64
11 changed files with 67 additions and 44 deletions

View File

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

View File

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

View File

@ -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<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 = (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") {

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

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

View File

@ -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<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,
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([
{

View File

@ -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",
});

View File

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

View File

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

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.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: (
<div className={cssNames({[styles.enabled]: getStatus(isEnabled) == "Enabled"})}>
{getStatus(isEnabled)}
<div className={cssNames({[styles.enabled]: isEnabled, [styles.invalid]: !isValid})}>
{getStatus(extension)}
</div>
),
actions: (
<MenuActions usePortal toolbar={false}>
{isEnabled ? (
<MenuItem
disabled={isUninstalling}
onClick={() => disable(id)}
>
<Icon material="unpublished"/>
<span className="title" aria-disabled={isUninstalling}>Disable</span>
</MenuItem>
) : (
<MenuItem
disabled={isUninstalling}
onClick={() => enable(id)}
>
<Icon material="check_circle"/>
<span className="title" aria-disabled={isUninstalling}>Enable</span>
</MenuItem>
{ isValid && (
<>
{isEnabled ? (
<MenuItem
disabled={isUninstalling}
onClick={() => disable(id)}
>
<Icon material="unpublished"/>
<span className="title" aria-disabled={isUninstalling}>Disable</span>
</MenuItem>
) : (
<MenuItem
disabled={isUninstalling}
onClick={() => enable(id)}
>
<Icon material="check_circle"/>
<span className="title" aria-disabled={isUninstalling}>Enable</span>
</MenuItem>
)}
</>
)}
<MenuItem
disabled={isUninstalling}
onClick={() => uninstall(extension)}