From 3776aca28b4e27883f7694ecf603d3593d3a8170 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 2 Nov 2020 17:35:17 +0200 Subject: [PATCH] refactoring base lens-extension.ts, added `isBundled` flag Signed-off-by: Roman --- src/extensions/extension-loader.ts | 73 +++++++---------- src/extensions/extension-manager.ts | 20 +++-- src/extensions/lens-extension.ts | 80 ++++++------------- .../components/+extensions/extensions.tsx | 22 +++-- 4 files changed, 77 insertions(+), 118 deletions(-) diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index 9567e4d5c2..435b2fb00e 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -1,4 +1,4 @@ -import type { ExtensionId, ExtensionManifest, ExtensionModel, LensExtension } from "./lens-extension" +import type { LensExtension, LensExtensionConstructor, LensExtensionManifest } from "./lens-extension" import type { LensMainExtension } from "./lens-main-extension" import type { LensRendererExtension } from "./lens-renderer-extension" import path from "path" @@ -6,15 +6,14 @@ import { broadcastIpc } from "../common/ipc" import { computed, observable, reaction, toJS, } from "mobx" import logger from "../main/logger" import { app, ipcRenderer, remote } from "electron" -import { - appPreferenceRegistry, clusterFeatureRegistry, clusterPageRegistry, globalPageRegistry, - kubeObjectDetailRegistry, kubeObjectMenuRegistry, menuRegistry, statusBarRegistry -} from "./registries"; -import { getBundledExtensions } from "../common/utils" +import * as registries from "./registries"; -export interface InstalledExtension extends ExtensionModel { +type ExtensionManifestPath = string; // path to package.json + +export interface InstalledExtension { manifestPath: string; - manifest: ExtensionManifest; + manifest: LensExtensionManifest; + isBundled?: boolean; // defined in package.json } // lazy load so that we get correct userData @@ -23,14 +22,14 @@ export function extensionPackagesRoot() { } export class ExtensionLoader { - @observable extensions = observable.map([], { deep: false }); - @observable instances = observable.map([], { deep: false }) + @observable extensions = observable.map([], { deep: false }); + @observable instances = observable.map([], { deep: false }) constructor() { if (ipcRenderer) { ipcRenderer.on("extensions:loaded", (event, extensions: InstalledExtension[]) => { extensions.forEach((ext) => { - if (!this.getById(ext.manifestPath)) { + if (!this.getByManifest(ext.manifestPath)) { this.extensions.set(ext.manifestPath, ext) } }) @@ -39,57 +38,52 @@ export class ExtensionLoader { } @computed get userExtensions(): LensExtension[] { - const builtIn = getBundledExtensions().map(ext => `lens-${ext}`) - const extensions: LensExtension[] = [] - this.instances.forEach(instance => { - if (builtIn.includes(instance.name)) return - extensions.push(instance) - }) - return extensions + return [...this.instances.values()].filter(ext => !ext.isBundled) } loadOnMain() { logger.info('[EXTENSIONS-LOADER]: load on main') this.autoloadExtensions((extension: LensMainExtension) => { - extension.registerTo(menuRegistry, extension.appMenus) + extension.registerTo(registries.menuRegistry, extension.appMenus) }) } loadOnClusterManagerRenderer() { logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)') this.autoloadExtensions((extension: LensRendererExtension) => { - extension.registerTo(globalPageRegistry, extension.globalPages) - extension.registerTo(appPreferenceRegistry, extension.appPreferences) - extension.registerTo(clusterFeatureRegistry, extension.clusterFeatures) - extension.registerTo(statusBarRegistry, extension.statusBarItems) + extension.registerTo(registries.globalPageRegistry, extension.globalPages) + extension.registerTo(registries.appPreferenceRegistry, extension.appPreferences) + extension.registerTo(registries.clusterFeatureRegistry, extension.clusterFeatures) + extension.registerTo(registries.statusBarRegistry, extension.statusBarItems) }) } loadOnClusterRenderer() { logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)') this.autoloadExtensions((extension: LensRendererExtension) => { - extension.registerTo(clusterPageRegistry, extension.clusterPages) - extension.registerTo(kubeObjectMenuRegistry, extension.kubeObjectMenuItems) - extension.registerTo(kubeObjectDetailRegistry, extension.kubeObjectDetailItems) + extension.registerTo(registries.clusterPageRegistry, extension.clusterPages) + extension.registerTo(registries.kubeObjectMenuRegistry, extension.kubeObjectMenuItems) + extension.registerTo(registries.kubeObjectDetailRegistry, extension.kubeObjectDetailItems) }) } protected autoloadExtensions(callback: (instance: LensExtension) => void) { return reaction(() => this.extensions.toJS(), (installedExtensions) => { - for(const [id, ext] of installedExtensions) { - let instance = this.instances.get(ext.id) + for (const [id, ext] of installedExtensions) { + let instance = this.instances.get(ext.manifestPath) if (!instance) { const extensionModule = this.requireExtension(ext) if (!extensionModule) { continue } - const LensExtensionClass = extensionModule.default; - instance = new LensExtensionClass({ ...ext.manifest, manifestPath: ext.manifestPath, id: ext.manifestPath }, ext.manifest); try { + const LensExtensionClass: LensExtensionConstructor = extensionModule.default; + instance = new LensExtensionClass(ext); instance.enable() callback(instance) - } finally { - this.instances.set(ext.id, instance) + this.instances.set(ext.manifestPath, instance) + } catch (err) { + logger.error(`[EXTENSIONS-LOADER]: activating extension error`, { ext, err }) } } } @@ -116,19 +110,8 @@ export class ExtensionLoader { } } - getById(id: ExtensionId): InstalledExtension { - return this.extensions.get(id); - } - - async removeById(id: ExtensionId) { - const extension = this.getById(id); - if (extension) { - const instance = this.instances.get(extension.id) - if (instance) { - await instance.disable() - } - this.extensions.delete(id); - } + getByManifest(manifestPath: ExtensionManifestPath): InstalledExtension { + return this.extensions.get(manifestPath); } broadcastExtensions(frameId?: number) { diff --git a/src/extensions/extension-manager.ts b/src/extensions/extension-manager.ts index 89879be602..599e42f54b 100644 --- a/src/extensions/extension-manager.ts +++ b/src/extensions/extension-manager.ts @@ -1,4 +1,4 @@ -import type { ExtensionManifest } from "./lens-extension" +import type { LensExtensionManifest } from "./lens-extension" import path from "path" import os from "os" import fs from "fs-extra" @@ -51,7 +51,7 @@ export class ExtensionManager { return path.join(this.extensionPackagesRoot, "package.json") } - async load() { + async load(): Promise> { logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot) if (fs.existsSync(path.join(this.extensionPackagesRoot, "package-lock.json"))) { await fs.remove(path.join(this.extensionPackagesRoot, "package-lock.json")) @@ -71,8 +71,8 @@ export class ExtensionManager { return await this.loadExtensions(); } - async getExtensionByManifest(manifestPath: string): Promise { - let manifestJson: ExtensionManifest; + async getByManifest(manifestPath: string): Promise { + let manifestJson: LensExtensionManifest; try { fs.accessSync(manifestPath, fs.constants.F_OK); // check manifest file for existence manifestJson = __non_webpack_require__(manifestPath) @@ -80,9 +80,6 @@ export class ExtensionManager { logger.info("[EXTENSION-MANAGER] installed extension " + manifestJson.name) return { - id: manifestJson.name, - version: manifestJson.version, - name: manifestJson.name, manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"), manifest: manifestJson } @@ -109,10 +106,10 @@ export class ExtensionManager { async loadExtensions() { const bundledExtensions = await this.loadBundledExtensions() const localExtensions = await this.loadFromFolder(this.localFolderPath) - await fs.writeFile(path.join(this.packageJsonPath), JSON.stringify(this.packagesJson, null, 2), {mode: 0o600}) + await fs.writeFile(path.join(this.packageJsonPath), JSON.stringify(this.packagesJson, null, 2), { mode: 0o600 }) await this.installPackages() const extensions = bundledExtensions.concat(localExtensions) - return new Map(extensions.map(ext => [ext.id, ext])); + return new Map(extensions.map(ext => [ext.manifestPath, ext])); } async loadBundledExtensions() { @@ -126,8 +123,9 @@ export class ExtensionManager { } const absPath = path.resolve(folderPath, fileName); const manifestPath = path.resolve(absPath, "package.json"); - const ext = await this.getExtensionByManifest(manifestPath).catch(() => null) + const ext = await this.getByManifest(manifestPath).catch(() => null) if (ext) { + ext.isBundled = true; extensions.push(ext) } } @@ -148,7 +146,7 @@ export class ExtensionManager { continue; } const manifestPath = path.resolve(absPath, "package.json"); - const ext = await this.getExtensionByManifest(manifestPath).catch(() => null) + const ext = await this.getByManifest(manifestPath).catch(() => null) if (ext) { extensions.push(ext) } diff --git a/src/extensions/lens-extension.ts b/src/extensions/lens-extension.ts index 0921dc066a..0a8766a019 100644 --- a/src/extensions/lens-extension.ts +++ b/src/extensions/lens-extension.ts @@ -1,58 +1,42 @@ -import { readJsonSync } from "fs-extra"; -import { action, observable, toJS } from "mobx"; +import { observable, toJS } from "mobx"; import logger from "../main/logger"; import { BaseRegistry } from "./registries/base-registry"; +import type { InstalledExtension } from "./extension-loader"; -export type ExtensionId = string | ExtensionPackageJsonPath; -export type ExtensionPackageJsonPath = string; -export type ExtensionVersion = string | number; +export type LensExtensionConstructor = new (init: InstalledExtension) => LensExtension; -export interface ExtensionModel { - id: ExtensionId; - version: ExtensionVersion; +export interface LensExtensionManifest { name: string; - manifestPath: string; + version: string; description?: string; - enabled?: boolean; - updateUrl?: string; + main?: string; // path to %ext/dist/main.js + renderer?: string; // path to %ext/dist/renderer.js } -export interface ExtensionManifest extends ExtensionModel { - main?: string; - renderer?: string; - description?: string; // todo: add more fields similar to package.json + some extra -} - -export class LensExtension implements ExtensionModel { - public id: ExtensionId; - public updateUrl: string; +export class LensExtension { + public manifest: LensExtensionManifest; + public manifestPath: string; + public isBundled: boolean; protected disposers: (() => void)[] = []; - @observable name = ""; - @observable description = ""; - @observable version: ExtensionVersion = "0.0.0"; - @observable manifest: ExtensionManifest; - @observable manifestPath: string; @observable isEnabled = false; - constructor(model: ExtensionModel, manifest: ExtensionManifest) { - this.importModel(model, manifest); + constructor({ manifest, manifestPath, isBundled }: InstalledExtension) { + this.manifest = manifest + this.manifestPath = manifestPath + this.isBundled = !!isBundled } - @action - async importModel({ enabled, manifestPath, ...model }: ExtensionModel, manifest?: ExtensionManifest) { - try { - this.manifest = manifest || await readJsonSync(manifestPath, { throws: true }) - this.manifestPath = manifestPath; - Object.assign(this, model); - } catch (err) { - logger.error(`[EXTENSION]: cannot read manifest at ${manifestPath}`, { ...model, err: String(err) }) - this.disable(); - } + get name() { + return this.manifest.name } - async migrate(appVersion: string) { - // mock + get version() { + return this.manifest.version + } + + get description() { + return this.manifest.description } async enable() { @@ -69,7 +53,6 @@ export class LensExtension implements ExtensionModel { logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`); } - // todo: add more hooks protected onActivate() { // mock } @@ -86,26 +69,13 @@ export class LensExtension implements ExtensionModel { }; } - getMeta() { + toJSON() { return toJS({ - id: this.id, - manifest: this.manifest, - manifestPath: this.manifestPath, - enabled: this.isEnabled - }, { - recurseEverything: true - }) - } - - toJSON(): ExtensionModel { - return toJS({ - id: this.id, name: this.name, version: this.version, description: this.description, manifestPath: this.manifestPath, - enabled: this.isEnabled, - updateUrl: this.updateUrl, + isEnabled: this.isEnabled, }, { recurseEverything: true, }) diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index 88a3bc2662..814be3d87d 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -1,5 +1,4 @@ import "./extensions.scss"; - import { shell } from "electron"; import React from "react"; import { computed, observable } from "mobx"; @@ -18,8 +17,13 @@ export class Extensions extends React.Component { @observable search = "" @computed get extensions() { - const extensions = extensionLoader.userExtensions - return extensions.filter(ext => ext.name.includes(this.search)) + const searchText = this.search.toLowerCase(); + return extensionLoader.userExtensions.filter(({ name, description }) => { + return [ + name.toLowerCase().includes(searchText), + description.toLowerCase().includes(searchText), + ].some(v => v) + }) } get extensionsPath() { @@ -62,12 +66,16 @@ export class Extensions extends React.Component { ) } - return extensions.map(({ id, name, description }) => { + return extensions.map(({ manifestPath, name, description }) => { return ( -
+
-
Package: {name}
-
Description: {description}
+
+ Name: {name} +
+
+ Description: {description} +