mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
refactoring base lens-extension.ts, added isBundled flag
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
1b3251ae2e
commit
3776aca28b
@ -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 { LensMainExtension } from "./lens-main-extension"
|
||||||
import type { LensRendererExtension } from "./lens-renderer-extension"
|
import type { LensRendererExtension } from "./lens-renderer-extension"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
@ -6,15 +6,14 @@ import { broadcastIpc } from "../common/ipc"
|
|||||||
import { computed, observable, reaction, toJS, } from "mobx"
|
import { computed, observable, reaction, toJS, } from "mobx"
|
||||||
import logger from "../main/logger"
|
import logger from "../main/logger"
|
||||||
import { app, ipcRenderer, remote } from "electron"
|
import { app, ipcRenderer, remote } from "electron"
|
||||||
import {
|
import * as registries from "./registries";
|
||||||
appPreferenceRegistry, clusterFeatureRegistry, clusterPageRegistry, globalPageRegistry,
|
|
||||||
kubeObjectDetailRegistry, kubeObjectMenuRegistry, menuRegistry, statusBarRegistry
|
|
||||||
} from "./registries";
|
|
||||||
import { getBundledExtensions } from "../common/utils"
|
|
||||||
|
|
||||||
export interface InstalledExtension extends ExtensionModel {
|
type ExtensionManifestPath = string; // path to package.json
|
||||||
|
|
||||||
|
export interface InstalledExtension {
|
||||||
manifestPath: string;
|
manifestPath: string;
|
||||||
manifest: ExtensionManifest;
|
manifest: LensExtensionManifest;
|
||||||
|
isBundled?: boolean; // defined in package.json
|
||||||
}
|
}
|
||||||
|
|
||||||
// lazy load so that we get correct userData
|
// lazy load so that we get correct userData
|
||||||
@ -23,14 +22,14 @@ export function extensionPackagesRoot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ExtensionLoader {
|
export class ExtensionLoader {
|
||||||
@observable extensions = observable.map<ExtensionId, InstalledExtension>([], { deep: false });
|
@observable extensions = observable.map<ExtensionManifestPath, InstalledExtension>([], { deep: false });
|
||||||
@observable instances = observable.map<ExtensionId, LensExtension>([], { deep: false })
|
@observable instances = observable.map<ExtensionManifestPath, LensExtension>([], { deep: false })
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
ipcRenderer.on("extensions:loaded", (event, extensions: InstalledExtension[]) => {
|
ipcRenderer.on("extensions:loaded", (event, extensions: InstalledExtension[]) => {
|
||||||
extensions.forEach((ext) => {
|
extensions.forEach((ext) => {
|
||||||
if (!this.getById(ext.manifestPath)) {
|
if (!this.getByManifest(ext.manifestPath)) {
|
||||||
this.extensions.set(ext.manifestPath, ext)
|
this.extensions.set(ext.manifestPath, ext)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -39,57 +38,52 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get userExtensions(): LensExtension[] {
|
@computed get userExtensions(): LensExtension[] {
|
||||||
const builtIn = getBundledExtensions().map(ext => `lens-${ext}`)
|
return [...this.instances.values()].filter(ext => !ext.isBundled)
|
||||||
const extensions: LensExtension[] = []
|
|
||||||
this.instances.forEach(instance => {
|
|
||||||
if (builtIn.includes(instance.name)) return
|
|
||||||
extensions.push(instance)
|
|
||||||
})
|
|
||||||
return extensions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnMain() {
|
loadOnMain() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main')
|
logger.info('[EXTENSIONS-LOADER]: load on main')
|
||||||
this.autoloadExtensions((extension: LensMainExtension) => {
|
this.autoloadExtensions((extension: LensMainExtension) => {
|
||||||
extension.registerTo(menuRegistry, extension.appMenus)
|
extension.registerTo(registries.menuRegistry, extension.appMenus)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterManagerRenderer() {
|
loadOnClusterManagerRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
||||||
this.autoloadExtensions((extension: LensRendererExtension) => {
|
this.autoloadExtensions((extension: LensRendererExtension) => {
|
||||||
extension.registerTo(globalPageRegistry, extension.globalPages)
|
extension.registerTo(registries.globalPageRegistry, extension.globalPages)
|
||||||
extension.registerTo(appPreferenceRegistry, extension.appPreferences)
|
extension.registerTo(registries.appPreferenceRegistry, extension.appPreferences)
|
||||||
extension.registerTo(clusterFeatureRegistry, extension.clusterFeatures)
|
extension.registerTo(registries.clusterFeatureRegistry, extension.clusterFeatures)
|
||||||
extension.registerTo(statusBarRegistry, extension.statusBarItems)
|
extension.registerTo(registries.statusBarRegistry, extension.statusBarItems)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterRenderer() {
|
loadOnClusterRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
||||||
this.autoloadExtensions((extension: LensRendererExtension) => {
|
this.autoloadExtensions((extension: LensRendererExtension) => {
|
||||||
extension.registerTo(clusterPageRegistry, extension.clusterPages)
|
extension.registerTo(registries.clusterPageRegistry, extension.clusterPages)
|
||||||
extension.registerTo(kubeObjectMenuRegistry, extension.kubeObjectMenuItems)
|
extension.registerTo(registries.kubeObjectMenuRegistry, extension.kubeObjectMenuItems)
|
||||||
extension.registerTo(kubeObjectDetailRegistry, extension.kubeObjectDetailItems)
|
extension.registerTo(registries.kubeObjectDetailRegistry, extension.kubeObjectDetailItems)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected autoloadExtensions(callback: (instance: LensExtension) => void) {
|
protected autoloadExtensions(callback: (instance: LensExtension) => void) {
|
||||||
return reaction(() => this.extensions.toJS(), (installedExtensions) => {
|
return reaction(() => this.extensions.toJS(), (installedExtensions) => {
|
||||||
for(const [id, ext] of installedExtensions) {
|
for (const [id, ext] of installedExtensions) {
|
||||||
let instance = this.instances.get(ext.id)
|
let instance = this.instances.get(ext.manifestPath)
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
const extensionModule = this.requireExtension(ext)
|
const extensionModule = this.requireExtension(ext)
|
||||||
if (!extensionModule) {
|
if (!extensionModule) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const LensExtensionClass = extensionModule.default;
|
|
||||||
instance = new LensExtensionClass({ ...ext.manifest, manifestPath: ext.manifestPath, id: ext.manifestPath }, ext.manifest);
|
|
||||||
try {
|
try {
|
||||||
|
const LensExtensionClass: LensExtensionConstructor = extensionModule.default;
|
||||||
|
instance = new LensExtensionClass(ext);
|
||||||
instance.enable()
|
instance.enable()
|
||||||
callback(instance)
|
callback(instance)
|
||||||
} finally {
|
this.instances.set(ext.manifestPath, instance)
|
||||||
this.instances.set(ext.id, instance)
|
} catch (err) {
|
||||||
|
logger.error(`[EXTENSIONS-LOADER]: activating extension error`, { ext, err })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,19 +110,8 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getById(id: ExtensionId): InstalledExtension {
|
getByManifest(manifestPath: ExtensionManifestPath): InstalledExtension {
|
||||||
return this.extensions.get(id);
|
return this.extensions.get(manifestPath);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcastExtensions(frameId?: number) {
|
broadcastExtensions(frameId?: number) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ExtensionManifest } from "./lens-extension"
|
import type { LensExtensionManifest } from "./lens-extension"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import os from "os"
|
import os from "os"
|
||||||
import fs from "fs-extra"
|
import fs from "fs-extra"
|
||||||
@ -51,7 +51,7 @@ export class ExtensionManager {
|
|||||||
return path.join(this.extensionPackagesRoot, "package.json")
|
return path.join(this.extensionPackagesRoot, "package.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load(): Promise<Map<string, InstalledExtension>> {
|
||||||
logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot)
|
logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot)
|
||||||
if (fs.existsSync(path.join(this.extensionPackagesRoot, "package-lock.json"))) {
|
if (fs.existsSync(path.join(this.extensionPackagesRoot, "package-lock.json"))) {
|
||||||
await fs.remove(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();
|
return await this.loadExtensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getExtensionByManifest(manifestPath: string): Promise<InstalledExtension> {
|
async getByManifest(manifestPath: string): Promise<InstalledExtension> {
|
||||||
let manifestJson: ExtensionManifest;
|
let manifestJson: LensExtensionManifest;
|
||||||
try {
|
try {
|
||||||
fs.accessSync(manifestPath, fs.constants.F_OK); // check manifest file for existence
|
fs.accessSync(manifestPath, fs.constants.F_OK); // check manifest file for existence
|
||||||
manifestJson = __non_webpack_require__(manifestPath)
|
manifestJson = __non_webpack_require__(manifestPath)
|
||||||
@ -80,9 +80,6 @@ export class ExtensionManager {
|
|||||||
|
|
||||||
logger.info("[EXTENSION-MANAGER] installed extension " + manifestJson.name)
|
logger.info("[EXTENSION-MANAGER] installed extension " + manifestJson.name)
|
||||||
return {
|
return {
|
||||||
id: manifestJson.name,
|
|
||||||
version: manifestJson.version,
|
|
||||||
name: manifestJson.name,
|
|
||||||
manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"),
|
manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"),
|
||||||
manifest: manifestJson
|
manifest: manifestJson
|
||||||
}
|
}
|
||||||
@ -109,10 +106,10 @@ export class ExtensionManager {
|
|||||||
async loadExtensions() {
|
async loadExtensions() {
|
||||||
const bundledExtensions = await this.loadBundledExtensions()
|
const bundledExtensions = await this.loadBundledExtensions()
|
||||||
const localExtensions = await this.loadFromFolder(this.localFolderPath)
|
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()
|
await this.installPackages()
|
||||||
const extensions = bundledExtensions.concat(localExtensions)
|
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() {
|
async loadBundledExtensions() {
|
||||||
@ -126,8 +123,9 @@ export class ExtensionManager {
|
|||||||
}
|
}
|
||||||
const absPath = path.resolve(folderPath, fileName);
|
const absPath = path.resolve(folderPath, fileName);
|
||||||
const manifestPath = path.resolve(absPath, "package.json");
|
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) {
|
if (ext) {
|
||||||
|
ext.isBundled = true;
|
||||||
extensions.push(ext)
|
extensions.push(ext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,7 +146,7 @@ export class ExtensionManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const manifestPath = path.resolve(absPath, "package.json");
|
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) {
|
if (ext) {
|
||||||
extensions.push(ext)
|
extensions.push(ext)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,58 +1,42 @@
|
|||||||
import { readJsonSync } from "fs-extra";
|
import { observable, toJS } from "mobx";
|
||||||
import { action, observable, toJS } from "mobx";
|
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { BaseRegistry } from "./registries/base-registry";
|
import { BaseRegistry } from "./registries/base-registry";
|
||||||
|
import type { InstalledExtension } from "./extension-loader";
|
||||||
|
|
||||||
export type ExtensionId = string | ExtensionPackageJsonPath;
|
export type LensExtensionConstructor = new (init: InstalledExtension) => LensExtension;
|
||||||
export type ExtensionPackageJsonPath = string;
|
|
||||||
export type ExtensionVersion = string | number;
|
|
||||||
|
|
||||||
export interface ExtensionModel {
|
export interface LensExtensionManifest {
|
||||||
id: ExtensionId;
|
|
||||||
version: ExtensionVersion;
|
|
||||||
name: string;
|
name: string;
|
||||||
manifestPath: string;
|
version: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
enabled?: boolean;
|
main?: string; // path to %ext/dist/main.js
|
||||||
updateUrl?: string;
|
renderer?: string; // path to %ext/dist/renderer.js
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtensionManifest extends ExtensionModel {
|
export class LensExtension {
|
||||||
main?: string;
|
public manifest: LensExtensionManifest;
|
||||||
renderer?: string;
|
public manifestPath: string;
|
||||||
description?: string; // todo: add more fields similar to package.json + some extra
|
public isBundled: boolean;
|
||||||
}
|
|
||||||
|
|
||||||
export class LensExtension implements ExtensionModel {
|
|
||||||
public id: ExtensionId;
|
|
||||||
public updateUrl: string;
|
|
||||||
protected disposers: (() => void)[] = [];
|
protected disposers: (() => void)[] = [];
|
||||||
|
|
||||||
@observable name = "";
|
|
||||||
@observable description = "";
|
|
||||||
@observable version: ExtensionVersion = "0.0.0";
|
|
||||||
@observable manifest: ExtensionManifest;
|
|
||||||
@observable manifestPath: string;
|
|
||||||
@observable isEnabled = false;
|
@observable isEnabled = false;
|
||||||
|
|
||||||
constructor(model: ExtensionModel, manifest: ExtensionManifest) {
|
constructor({ manifest, manifestPath, isBundled }: InstalledExtension) {
|
||||||
this.importModel(model, manifest);
|
this.manifest = manifest
|
||||||
|
this.manifestPath = manifestPath
|
||||||
|
this.isBundled = !!isBundled
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
get name() {
|
||||||
async importModel({ enabled, manifestPath, ...model }: ExtensionModel, manifest?: ExtensionManifest) {
|
return this.manifest.name
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async migrate(appVersion: string) {
|
get version() {
|
||||||
// mock
|
return this.manifest.version
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
return this.manifest.description
|
||||||
}
|
}
|
||||||
|
|
||||||
async enable() {
|
async enable() {
|
||||||
@ -69,7 +53,6 @@ export class LensExtension implements ExtensionModel {
|
|||||||
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: add more hooks
|
|
||||||
protected onActivate() {
|
protected onActivate() {
|
||||||
// mock
|
// mock
|
||||||
}
|
}
|
||||||
@ -86,26 +69,13 @@ export class LensExtension implements ExtensionModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getMeta() {
|
toJSON() {
|
||||||
return toJS({
|
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,
|
name: this.name,
|
||||||
version: this.version,
|
version: this.version,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
manifestPath: this.manifestPath,
|
manifestPath: this.manifestPath,
|
||||||
enabled: this.isEnabled,
|
isEnabled: this.isEnabled,
|
||||||
updateUrl: this.updateUrl,
|
|
||||||
}, {
|
}, {
|
||||||
recurseEverything: true,
|
recurseEverything: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import "./extensions.scss";
|
import "./extensions.scss";
|
||||||
|
|
||||||
import { shell } from "electron";
|
import { shell } from "electron";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { computed, observable } from "mobx";
|
import { computed, observable } from "mobx";
|
||||||
@ -18,8 +17,13 @@ export class Extensions extends React.Component {
|
|||||||
@observable search = ""
|
@observable search = ""
|
||||||
|
|
||||||
@computed get extensions() {
|
@computed get extensions() {
|
||||||
const extensions = extensionLoader.userExtensions
|
const searchText = this.search.toLowerCase();
|
||||||
return extensions.filter(ext => ext.name.includes(this.search))
|
return extensionLoader.userExtensions.filter(({ name, description }) => {
|
||||||
|
return [
|
||||||
|
name.toLowerCase().includes(searchText),
|
||||||
|
description.toLowerCase().includes(searchText),
|
||||||
|
].some(v => v)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get extensionsPath() {
|
get extensionsPath() {
|
||||||
@ -62,12 +66,16 @@ export class Extensions extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return extensions.map(({ id, name, description }) => {
|
return extensions.map(({ manifestPath, name, description }) => {
|
||||||
return (
|
return (
|
||||||
<div key={id} className="extension flex gaps align-center">
|
<div key={manifestPath} className="extension flex gaps align-center">
|
||||||
<div className="box grow flex column gaps">
|
<div className="box grow flex column gaps">
|
||||||
<div className="package">Package: <code className="name">{name}</code></div>
|
<div className="package">
|
||||||
<div>Description: <span className="text-secondary">{description}</span></div>
|
Name: <code className="name">{name}</code>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Description: <span className="text-secondary">{description}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button plain active onClick={() => console.log(`//todo: disable ${name}`)}>
|
<Button plain active onClick={() => console.log(`//todo: disable ${name}`)}>
|
||||||
Disable
|
Disable
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user