From ed123e2819d1ca35664cb5534616f07432d9daa7 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 1 Sep 2020 13:20:49 +0300 Subject: [PATCH] extensions-api -- in-progress Signed-off-by: Roman --- src/extensions/example-extension/README.md | 3 ++ .../example-extension/example-extension.ts | 2 +- src/extensions/extension-store.ts | 25 ++++++++--- src/extensions/extension.ts | 45 ++++++++++++++++--- src/renderer/bootstrap.tsx | 2 + 5 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 src/extensions/example-extension/README.md diff --git a/src/extensions/example-extension/README.md b/src/extensions/example-extension/README.md new file mode 100644 index 0000000000..eb8d01ac38 --- /dev/null +++ b/src/extensions/example-extension/README.md @@ -0,0 +1,3 @@ +# Lens Example Extension + +*TODO*: add more info \ No newline at end of file diff --git a/src/extensions/example-extension/example-extension.ts b/src/extensions/example-extension/example-extension.ts index 37eaed07b0..9d5f2eab5e 100644 --- a/src/extensions/example-extension/example-extension.ts +++ b/src/extensions/example-extension/example-extension.ts @@ -1,6 +1,6 @@ import { LensExtension } from "@lens"; // todo: handle runtime import -export class ExampleExtension extends LensExtension { +export default class ExampleExtension extends LensExtension { async init(): Promise { console.log('Example extension: init') return super.init(); diff --git a/src/extensions/extension-store.ts b/src/extensions/extension-store.ts index 3150b135fb..069dca540b 100644 --- a/src/extensions/extension-store.ts +++ b/src/extensions/extension-store.ts @@ -1,11 +1,10 @@ import path from "path"; -import { action, observable, toJS } from "mobx"; +import fs from "fs-extra"; +import { action, comparer, observable, toJS } from "mobx"; import { BaseStore } from "../common/base-store"; -import { LensExtension } from "./extension"; +import { ExtensionId, ExtensionVersion, LensExtension } from "./extension"; import { isDevelopment } from "../common/vars"; - -export type ExtensionId = string; -export type ExtensionVersion = string | number; +import logger from "../main/logger"; export interface ExtensionStoreModel { version: ExtensionVersion; @@ -16,6 +15,7 @@ export interface ExtensionModel { id: ExtensionId; version: ExtensionVersion; name: string; + manifestPath: string; description?: string; enabled?: boolean; updateUrl?: string; @@ -31,6 +31,21 @@ export class ExtensionStore extends BaseStore { @observable version: ExtensionVersion = "0.0.0"; @observable extensions = observable.map(); @observable removed = observable.map(); + @observable installed = observable.set([], { equals: comparer.shallow }); + + async load() { + await this.loadExtensions(); + return super.load(); + } + + async loadExtensions() { + const localExtensions = await fs.readdir(this.builtInExtensionsPath); + logger.info(`[EXTENSIONS]: scanning installed extensions`, { paths: localExtensions }); + this.installed.replace(localExtensions as any[]); + // return localExtensions + // .filter(path => ![".", ".."].includes(path)) + // .map(path => import(`${path}/package.json`)); + } get builtInExtensionsPath(): string { if (isDevelopment) { diff --git a/src/extensions/extension.ts b/src/extensions/extension.ts index dedff68e22..c30753025f 100644 --- a/src/extensions/extension.ts +++ b/src/extensions/extension.ts @@ -1,5 +1,8 @@ -import { observable } from "mobx"; +import { readJsonSync } from "fs-extra"; +import { action, observable, when } from "mobx"; import { ExtensionModel } from "./extension-store"; +import extensionManifest from "./example-extension/package.json" +import logger from "../main/logger"; // TODO: extensions api // * Lazy load/unload extension (js/ts?) (from sources: local folder, npm_modules/@lens/some_plugin, etc.) @@ -9,27 +12,41 @@ import { ExtensionModel } from "./extension-store"; export type ExtensionId = string; export type ExtensionVersion = string | number; +export type ExtensionManifest = typeof extensionManifest; export class LensExtension implements ExtensionModel { public id: ExtensionId; - public version: string | number; public updateUrl: string; @observable name = ""; @observable description = ""; + @observable version: ExtensionVersion = "0.0.0"; + @observable manifest: ExtensionManifest; + @observable manifestPath: string; + @observable isReady = false; @observable isEnabled = false; - @observable isInstalled = false; + + whenReady = when(() => this.isReady); constructor(model: ExtensionModel) { this.importModel(model); } - importModel({ enabled, ...model }: ExtensionModel) { - Object.assign(this, model); - this.isEnabled = enabled; + @action + async importModel({ enabled, manifestPath, ...model }: ExtensionModel) { + try { + this.manifest = await readJsonSync(manifestPath, { throws: true }) + this.manifestPath = manifestPath; + this.isEnabled = enabled; + Object.assign(this, model); + this.isReady = true; + } catch (err) { + logger.error(`[EXTENSION]: cannot read manifest at ${manifestPath}`, { ...model, err: String(err) }) + this.disable(); + } } - async init(){ + async init() { // todo? } @@ -46,10 +63,24 @@ export class LensExtension implements ExtensionModel { } async enable() { + this.isEnabled = true; // todo } async disable() { + this.isEnabled = false; // todo } + + toJSON(): ExtensionModel { + return { + id: this.id, + name: this.name, + version: this.version, + description: this.description, + manifestPath: this.manifestPath, + enabled: this.isEnabled, + updateUrl: this.updateUrl, + } + } } diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 0fcb216cd9..e4b24c3035 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -4,6 +4,7 @@ import { render } from "react-dom"; import { isMac } from "../common/vars"; import { userStore } from "../common/user-store"; import { workspaceStore } from "../common/workspace-store"; +import { extensionStore } from "../extensions/extension-store"; import { clusterStore, getHostedClusterId } from "../common/cluster-store"; import { i18nStore } from "./i18n"; import { themeStore } from "./theme.store"; @@ -23,6 +24,7 @@ export async function bootstrap(App: AppComponent) { userStore.load(), workspaceStore.load(), clusterStore.load(), + extensionStore.load(), i18nStore.init(), themeStore.init(), ]);