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/"; export const supportUrl = "https://docs.k8slens.dev/latest/support/";
// This explicitly ignores the prerelease info on the package version // 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 mmpVersion = [major, minor, patch].join(".");
const docsVersion = isProduction ? `v${mmpVersion}` : "latest"; const docsVersion = isProduction ? `v${mmpVersion}` : "latest";

View File

@ -38,7 +38,8 @@ describe("lens extension", () => {
absolutePath: "/absolute/fake/", absolutePath: "/absolute/fake/",
manifestPath: "/this/is/fake/package.json", manifestPath: "/this/is/fake/package.json",
isBundled: false, 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 { Singleton, toJS } from "../common/utils";
import logger from "../main/logger"; import logger from "../main/logger";
import { ExtensionInstallationStateStore } from "../renderer/components/+extensions/extension-install.store"; 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 { ExtensionsStore } from "./extensions-store";
import { ExtensionLoader } from "./extension-loader"; import { ExtensionLoader } from "./extension-loader";
import type { LensExtensionId, LensExtensionManifest } from "./lens-extension"; 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 { export interface InstalledExtension {
id: LensExtensionId; id: LensExtensionId;
@ -47,6 +49,7 @@ export interface InstalledExtension {
// Absolute to the symlinked package.json file // Absolute to the symlinked package.json file
readonly manifestPath: string; readonly manifestPath: string;
readonly isBundled: boolean; // defined in project root's package.json readonly isBundled: boolean; // defined in project root's package.json
readonly isValid: boolean;
isEnabled: boolean; isEnabled: boolean;
} }
@ -348,12 +351,17 @@ export class ExtensionDiscovery extends Singleton {
*/ */
protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise<InstalledExtension | null> { protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise<InstalledExtension | null> {
try { try {
const manifest = await fse.readJson(manifestPath); const manifest = await fse.readJson(manifestPath) as LensExtensionManifest;
const installedManifestPath = this.getInstalledManifestPath(manifest.name); const installedManifestPath = this.getInstalledManifestPath(manifest.name);
const isEnabled = isBundled || ExtensionsStore.getInstance().isEnabled(installedManifestPath); const isEnabled = isBundled || ExtensionsStore.getInstance().isEnabled(installedManifestPath);
const extensionDir = path.dirname(manifestPath); const extensionDir = path.dirname(manifestPath);
const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`); const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`);
const absolutePath = (await fse.pathExists(npmPackage)) ? npmPackage : extensionDir; const absolutePath = (await fse.pathExists(npmPackage)) ? npmPackage : extensionDir;
let isValid = false;
if (manifest.engines?.lens) {
isValid = semver.satisfies(appSemVer, manifest.engines.lens);
}
return { return {
id: installedManifestPath, id: installedManifestPath,
@ -361,7 +369,8 @@ export class ExtensionDiscovery extends Singleton {
manifestPath: installedManifestPath, manifestPath: installedManifestPath,
manifest, manifest,
isBundled, isBundled,
isEnabled isEnabled,
isValid
}; };
} catch (error) { } catch (error) {
if (error.code === "ENOTDIR") { if (error.code === "ENOTDIR") {

View File

@ -25,17 +25,10 @@ import fs from "fs-extra";
import path from "path"; import path from "path";
import logger from "../main/logger"; import logger from "../main/logger";
import { extensionPackagesRoot } from "./extension-loader"; import { extensionPackagesRoot } from "./extension-loader";
import type { PackageJson } from "type-fest";
const logModule = "[EXTENSION-INSTALLER]"; 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 * Installs dependencies for extensions

View File

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

View File

@ -25,17 +25,16 @@ import { FilesystemProvisionerStore } from "../main/extension-filesystem";
import logger from "../main/logger"; import logger from "../main/logger";
import type { ProtocolHandlerRegistration } from "./registries"; import type { ProtocolHandlerRegistration } from "./registries";
import { disposer } from "../common/utils"; import { disposer } from "../common/utils";
import type { PackageJson } from "type-fest";
export type LensExtensionId = string; // path to manifest (package.json) export type LensExtensionId = string; // path to manifest (package.json)
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension; export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
export interface LensExtensionManifest { export interface LensExtensionManifest extends PackageJson {
name: string; name: string;
version: string; version: string;
description?: string;
main?: string; // path to %ext/dist/main.js main?: string; // path to %ext/dist/main.js
renderer?: string; // path to %ext/dist/renderer.js renderer?: string; // path to %ext/dist/renderer.js
lens?: object; // fixme: add more required fields for validation
} }
export const Disposers = Symbol(); export const Disposers = Symbol();

View File

@ -40,7 +40,8 @@ describe("getPageUrl", () => {
absolutePath: "/absolute/fake/", absolutePath: "/absolute/fake/",
manifestPath: "/this/is/fake/package.json", manifestPath: "/this/is/fake/package.json",
isBundled: false, isBundled: false,
isEnabled: true isEnabled: true,
isValid: true
}); });
globalPageRegistry.add({ globalPageRegistry.add({
id: "page-with-params", id: "page-with-params",
@ -107,7 +108,8 @@ describe("globalPageRegistry", () => {
absolutePath: "/absolute/fake/", absolutePath: "/absolute/fake/",
manifestPath: "/this/is/fake/package.json", manifestPath: "/this/is/fake/package.json",
isBundled: false, isBundled: false,
isEnabled: true isEnabled: true,
isValid: true
}); });
globalPageRegistry.add([ globalPageRegistry.add([
{ {

View File

@ -87,6 +87,7 @@ describe("protocol router tests", () => {
}, },
isBundled: false, isBundled: false,
isEnabled: true, isEnabled: true,
isValid: true,
absolutePath: "/foo/bar", absolutePath: "/foo/bar",
}); });
const lpr = LensProtocolRouterMain.getInstance(); const lpr = LensProtocolRouterMain.getInstance();
@ -164,6 +165,7 @@ describe("protocol router tests", () => {
}, },
isBundled: false, isBundled: false,
isEnabled: true, isEnabled: true,
isValid: true,
absolutePath: "/foo/bar", absolutePath: "/foo/bar",
}); });
@ -204,6 +206,7 @@ describe("protocol router tests", () => {
}, },
isBundled: false, isBundled: false,
isEnabled: true, isEnabled: true,
isValid: true,
absolutePath: "/foo/bar", absolutePath: "/foo/bar",
}); });
@ -228,6 +231,7 @@ describe("protocol router tests", () => {
}, },
isBundled: false, isBundled: false,
isEnabled: true, isEnabled: true,
isValid: true,
absolutePath: "/foo/bar", absolutePath: "/foo/bar",
}); });

View File

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

View File

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

View File

@ -39,14 +39,18 @@ interface Props {
uninstall: (extension: InstalledExtension) => void; uninstall: (extension: InstalledExtension) => void;
} }
function getStatus(isEnabled: boolean) { function getStatus(extension: InstalledExtension) {
return isEnabled ? "Enabled" : "Disabled"; if (!extension.isValid) {
return "Incompatible";
}
return extension.isEnabled ? "Enabled" : "Disabled";
} }
export const InstalledExtensions = observer(({ extensions, uninstall, enable, disable }: Props) => { export const InstalledExtensions = observer(({ extensions, uninstall, enable, disable }: Props) => {
const filters = [ const filters = [
(extension: InstalledExtension) => extension.manifest.name, (extension: InstalledExtension) => extension.manifest.name,
(extension: InstalledExtension) => getStatus(extension.isEnabled), (extension: InstalledExtension) => getStatus(extension),
(extension: InstalledExtension) => extension.manifest.version, (extension: InstalledExtension) => extension.manifest.version,
]; ];
@ -87,7 +91,7 @@ export const InstalledExtensions = observer(({ extensions, uninstall, enable, di
const data = useMemo( const data = useMemo(
() => { () => {
return extensions.map(extension => { return extensions.map(extension => {
const { id, isEnabled, manifest } = extension; const { id, isEnabled, isValid, manifest } = extension;
const { name, description, version } = manifest; const { name, description, version } = manifest;
const isUninstalling = ExtensionInstallationStateStore.isExtensionUninstalling(id); const isUninstalling = ExtensionInstallationStateStore.isExtensionUninstalling(id);
@ -102,29 +106,34 @@ export const InstalledExtensions = observer(({ extensions, uninstall, enable, di
), ),
version, version,
status: ( status: (
<div className={cssNames({[styles.enabled]: getStatus(isEnabled) == "Enabled"})}> <div className={cssNames({[styles.enabled]: isEnabled, [styles.invalid]: !isValid})}>
{getStatus(isEnabled)} {getStatus(extension)}
</div> </div>
), ),
actions: ( actions: (
<MenuActions usePortal toolbar={false}> <MenuActions usePortal toolbar={false}>
{isEnabled ? ( { isValid && (
<MenuItem <>
disabled={isUninstalling} {isEnabled ? (
onClick={() => disable(id)} <MenuItem
> disabled={isUninstalling}
<Icon material="unpublished"/> onClick={() => disable(id)}
<span className="title" aria-disabled={isUninstalling}>Disable</span> >
</MenuItem> <Icon material="unpublished"/>
) : ( <span className="title" aria-disabled={isUninstalling}>Disable</span>
<MenuItem </MenuItem>
disabled={isUninstalling} ) : (
onClick={() => enable(id)} <MenuItem
> disabled={isUninstalling}
<Icon material="check_circle"/> onClick={() => enable(id)}
<span className="title" aria-disabled={isUninstalling}>Enable</span> >
</MenuItem> <Icon material="check_circle"/>
<span className="title" aria-disabled={isUninstalling}>Enable</span>
</MenuItem>
)}
</>
)} )}
<MenuItem <MenuItem
disabled={isUninstalling} disabled={isUninstalling}
onClick={() => uninstall(extension)} onClick={() => uninstall(extension)}