mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
chore: Improve linting within @k8slens/core
- Turning on @typescript-eslint/recommended-requiring-type-checking - Turning off @typescript-eslint/no-unnecessary-type-assertion (due too many false positives) - Making @typescript-eslint/no-explicit-any an error (except in tests) Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
6184c2f03b
commit
fa44b795d4
734 files changed
+32216
-4566
No files matched your search
@@ -15,7 +15,7 @@ describe("configurable directories for extension files", () => {
|
||||
beforeEach(async () => {
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
builder.beforeApplicationStart(({ mainDi }) => {
|
||||
await builder.beforeApplicationStart(({ mainDi }) => {
|
||||
runInAction(() => {
|
||||
mainDi.override(getHashInjectable, () => x => x);
|
||||
});
|
||||
@@ -50,7 +50,7 @@ describe("configurable directories for extension files", () => {
|
||||
},
|
||||
};
|
||||
|
||||
builder.extensions.enable(testExtensionOptions);
|
||||
await builder.extensions.enable(testExtensionOptions);
|
||||
});
|
||||
|
||||
it("creates extension directory for specific store name", async () => {
|
||||
@@ -85,7 +85,7 @@ describe("configurable directories for extension files", () => {
|
||||
},
|
||||
};
|
||||
|
||||
builder.extensions.enable(testExtensionOptions);
|
||||
await builder.extensions.enable(testExtensionOptions);
|
||||
});
|
||||
|
||||
it("creates extension directory for package name", async () => {
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("ExtensionLoader", () => {
|
||||
di.override(currentlyInClusterFrameInjectable, () => false);
|
||||
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
invoke: jest.fn(async (channel: string) => {
|
||||
invoke: jest.fn((channel: string) => {
|
||||
if (channel === "extension-loader:main:state") {
|
||||
return [
|
||||
[
|
||||
@@ -68,7 +68,7 @@ describe("ExtensionLoader", () => {
|
||||
return [];
|
||||
}),
|
||||
|
||||
on: (channel: string, listener: (event: any, ...args: any[]) => void) => {
|
||||
on: (channel: string, listener: (event: unknown, ...args: unknown[]) => void) => {
|
||||
if (channel === "extension-loader:main:state") {
|
||||
// First initialize with extensions 1 and 2
|
||||
// and then broadcast event to remove extension 2 and add extension number 3
|
||||
|
||||
@@ -21,21 +21,21 @@ export interface ExtensionStoreParams<T extends object> extends Omit<PersistentS
|
||||
}
|
||||
|
||||
export abstract class BaseExtensionStore<T extends object> {
|
||||
private static readonly instances = new WeakMap<object, any>();
|
||||
private static readonly instances = new WeakMap<object, unknown>();
|
||||
|
||||
/**
|
||||
* @deprecated This is a form of global shared state. Just call `new Store(...)`
|
||||
*/
|
||||
static createInstance<T, R extends any[]>(this: StaticThis<T, R>, ...args: R): T {
|
||||
static createInstance<T, R extends unknown[]>(this: StaticThis<T, R>, ...args: R): T {
|
||||
return getOrInsertWith(BaseExtensionStore.instances, this, () => new this(...args)) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This is a form of global shared state. Just call `new Store(...)`
|
||||
*/
|
||||
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict?: true): T;
|
||||
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict: false): T | undefined;
|
||||
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict = true): T | undefined {
|
||||
static getInstance<T, R extends unknown[]>(this: StaticThis<T, R>, strict?: true): T;
|
||||
static getInstance<T, R extends unknown[]>(this: StaticThis<T, R>, strict: false): T | undefined;
|
||||
static getInstance<T, R extends unknown[]>(this: StaticThis<T, R>, strict = true): T | undefined {
|
||||
if (!BaseExtensionStore.instances.has(this) && strict) {
|
||||
throw new TypeError(`instance of ${this.name} is not created`);
|
||||
}
|
||||
@@ -73,6 +73,8 @@ export abstract class BaseExtensionStore<T extends object> {
|
||||
...params
|
||||
} = this.rawParams;
|
||||
|
||||
void _cwd;
|
||||
|
||||
this.persistentStorage = this.dependencies.createPersistentStorage({
|
||||
...params,
|
||||
cwd: this.cwd(),
|
||||
|
||||
@@ -59,9 +59,9 @@ export interface ExternalKubeApiOptions {
|
||||
|
||||
// NOTE: this is done to preserve `instanceOf` behaviour
|
||||
function KubeApiCstr<
|
||||
Object extends KubeObject = KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Object> = KubeJsonApiDataFor<Object>,
|
||||
>({ autoRegister = true, ...opts }: KubeApiOptions<Object, Data> & ExternalKubeApiOptions) {
|
||||
Kube extends KubeObject = KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Kube> = KubeJsonApiDataFor<Kube>,
|
||||
>({ autoRegister = true, ...opts }: KubeApiOptions<Kube, Data> & ExternalKubeApiOptions) {
|
||||
const api = new InternalKubeApi(getKubeApiDeps(), opts);
|
||||
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
@@ -75,14 +75,14 @@ function KubeApiCstr<
|
||||
}
|
||||
|
||||
export type KubeApi<
|
||||
Object extends KubeObject = KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Object> = KubeJsonApiDataFor<Object>,
|
||||
> = InternalKubeApi<Object, Data>;
|
||||
Kube extends KubeObject = KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Kube> = KubeJsonApiDataFor<Kube>,
|
||||
> = InternalKubeApi<Kube, Data>;
|
||||
|
||||
export const KubeApi = KubeApiCstr as unknown as new<
|
||||
Object extends KubeObject = KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Object> = KubeJsonApiDataFor<Object>,
|
||||
>(opts: KubeApiOptions<Object, Data> & ExternalKubeApiOptions) => InternalKubeApi<Object, Data>;
|
||||
Kube extends KubeObject = KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Kube> = KubeJsonApiDataFor<Kube>,
|
||||
>(opts: KubeApiOptions<Kube, Data> & ExternalKubeApiOptions) => InternalKubeApi<Kube, Data>;
|
||||
|
||||
/**
|
||||
* @deprecated Switch to using `Common.createResourceStack` instead
|
||||
@@ -94,11 +94,11 @@ export class ResourceStack implements ResourceApplyingStack {
|
||||
this.impl = createResourceStack(cluster, name);
|
||||
}
|
||||
|
||||
kubectlApplyFolder(folderPath: string, templateContext?: any, extraArgs?: string[] | undefined): Promise<string> {
|
||||
kubectlApplyFolder(folderPath: string, templateContext?: unknown, extraArgs?: string[] | undefined): Promise<string> {
|
||||
return this.impl.kubectlApplyFolder(folderPath, templateContext, extraArgs);
|
||||
}
|
||||
|
||||
kubectlDeleteFolder(folderPath: string, templateContext?: any, extraArgs?: string[] | undefined): Promise<string> {
|
||||
kubectlDeleteFolder(folderPath: string, templateContext?: unknown, extraArgs?: string[] | undefined): Promise<string> {
|
||||
return this.impl.kubectlDeleteFolder(folderPath, templateContext, extraArgs);
|
||||
}
|
||||
}
|
||||
@@ -211,19 +211,19 @@ export interface IgnoredKubeApiOptions {
|
||||
/**
|
||||
* @deprecated this option is overridden and should not be used
|
||||
*/
|
||||
objectConstructor?: any;
|
||||
objectConstructor?: unknown;
|
||||
/**
|
||||
* @deprecated this option is overridden and should not be used
|
||||
*/
|
||||
kind?: any;
|
||||
kind?: unknown;
|
||||
/**
|
||||
* @deprecated this option is overridden and should not be used
|
||||
*/
|
||||
isNamespaces?: any;
|
||||
isNamespaces?: unknown;
|
||||
/**
|
||||
* @deprecated this option is overridden and should not be used
|
||||
*/
|
||||
apiBase?: any;
|
||||
apiBase?: unknown;
|
||||
}
|
||||
|
||||
// NOTE: these *Constructor functions MUST be `function` to work with `new X()`
|
||||
|
||||
@@ -87,9 +87,9 @@ describe("ExtensionDiscovery", () => {
|
||||
watchMock.mockImplementationOnce(() => mockWatchInstance);
|
||||
|
||||
// Need to force isLoaded to be true so that the file watching is started
|
||||
extensionDiscovery.isLoaded = true;
|
||||
extensionDiscovery.isLoaded.set(true);
|
||||
|
||||
await extensionDiscovery.watchExtensions();
|
||||
extensionDiscovery.watchExtensions();
|
||||
|
||||
extensionDiscovery.events.on("add", extension => {
|
||||
expect(extension).toEqual({
|
||||
@@ -130,9 +130,8 @@ describe("ExtensionDiscovery", () => {
|
||||
watchMock.mockImplementationOnce(() => mockWatchInstance);
|
||||
|
||||
// Need to force isLoaded to be true so that the file watching is started
|
||||
extensionDiscovery.isLoaded = true;
|
||||
|
||||
await extensionDiscovery.watchExtensions();
|
||||
extensionDiscovery.isLoaded.set(true);
|
||||
extensionDiscovery.watchExtensions();
|
||||
|
||||
const onAdd = jest.fn();
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
|
||||
import { ipcRenderer } from "electron";
|
||||
import { EventEmitter } from "events";
|
||||
import { makeObservable, observable, reaction, when } from "mobx";
|
||||
import { action, observable, reaction } from "mobx";
|
||||
import { broadcastMessage, ipcMainHandle, ipcRendererOn } from "../../common/ipc";
|
||||
import { toJS } from "../../common/utils";
|
||||
import { isErrnoException } from "@k8slens/utilities";
|
||||
import type { ExtensionLoader } from "../extension-loader";
|
||||
import type { InstalledExtension, LensExtensionId, LensExtensionManifest, ExternalInstalledExtension } from "@k8slens/legacy-extensions";
|
||||
@@ -31,6 +30,7 @@ import type { GetRelativePath } from "../../common/path/get-relative-path.inject
|
||||
import type { RemovePath } from "../../common/fs/remove.injectable";
|
||||
import type TypedEventEmitter from "typed-emitter";
|
||||
import type { IsExtensionEnabled } from "../../features/extensions/enabled/common/is-enabled.injectable";
|
||||
import assert from "assert";
|
||||
|
||||
interface Dependencies {
|
||||
readonly extensionLoader: ExtensionLoader;
|
||||
@@ -95,53 +95,39 @@ export class ExtensionDiscovery {
|
||||
private extensions: Map<string, InstalledExtension> = new Map();
|
||||
|
||||
// True if extensions have been loaded from the disk after app startup
|
||||
@observable isLoaded = false;
|
||||
|
||||
get whenLoaded() {
|
||||
return when(() => this.isLoaded);
|
||||
}
|
||||
readonly isLoaded = observable.box(false);
|
||||
|
||||
public readonly events: TypedEventEmitter<ExtensionDiscoveryEvents> = new EventEmitter();
|
||||
|
||||
constructor(protected readonly dependencies: Dependencies) {
|
||||
makeObservable(this);
|
||||
}
|
||||
constructor(protected readonly dependencies: Dependencies) {}
|
||||
|
||||
get localFolderPath(): string {
|
||||
return this.dependencies.joinPaths(this.dependencies.homeDirectoryPath, ".k8slens", "extensions");
|
||||
}
|
||||
|
||||
get packageJsonPath(): string {
|
||||
return this.dependencies.joinPaths(this.dependencies.extensionPackageRootDirectory, manifestFilename);
|
||||
}
|
||||
|
||||
get nodeModulesPath(): string {
|
||||
return this.dependencies.joinPaths(this.dependencies.extensionPackageRootDirectory, "node_modules");
|
||||
}
|
||||
readonly localFolderPath = this.dependencies.joinPaths(this.dependencies.homeDirectoryPath, ".k8slens", "extensions");
|
||||
readonly packageJsonPath = this.dependencies.joinPaths(this.dependencies.extensionPackageRootDirectory, manifestFilename);
|
||||
readonly nodeModulesPath = this.dependencies.joinPaths(this.dependencies.extensionPackageRootDirectory, "node_modules");
|
||||
|
||||
/**
|
||||
* Initializes the class and setups the file watcher for added/removed local extensions.
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
async init() {
|
||||
if (ipcRenderer) {
|
||||
await this.initRenderer();
|
||||
} else {
|
||||
await this.initMain();
|
||||
this.initMain();
|
||||
}
|
||||
}
|
||||
|
||||
async initRenderer(): Promise<void> {
|
||||
const onMessage = ({ isLoaded }: ExtensionDiscoveryChannelMessage) => {
|
||||
this.isLoaded = isLoaded;
|
||||
};
|
||||
async initRenderer() {
|
||||
const onMessage = action(({ isLoaded }: ExtensionDiscoveryChannelMessage) => {
|
||||
this.isLoaded.set(isLoaded);
|
||||
});
|
||||
|
||||
requestInitialExtensionDiscovery().then(onMessage);
|
||||
ipcRendererOn(extensionDiscoveryStateChannel, (_event, message: ExtensionDiscoveryChannelMessage) => {
|
||||
onMessage(message);
|
||||
await requestInitialExtensionDiscovery().then(onMessage);
|
||||
ipcRendererOn(extensionDiscoveryStateChannel, (_event, message) => {
|
||||
onMessage(message as ExtensionDiscoveryChannelMessage);
|
||||
});
|
||||
}
|
||||
|
||||
async initMain(): Promise<void> {
|
||||
initMain() {
|
||||
ipcMainHandle(extensionDiscoveryStateChannel, () => this.toJSON());
|
||||
|
||||
reaction(() => this.toJSON(), () => {
|
||||
@@ -155,12 +141,10 @@ export class ExtensionDiscovery {
|
||||
* Watches for added/removed local extensions.
|
||||
* Dependencies are installed automatically after an extension folder is copied.
|
||||
*/
|
||||
async watchExtensions(): Promise<void> {
|
||||
watchExtensions() {
|
||||
assert(this.isLoaded.get(), "Can only watch extensions after ExtensionDiscovery is loaded");
|
||||
|
||||
this.dependencies.logger.info(`${logModule} watching extension add/remove in ${this.localFolderPath}`);
|
||||
|
||||
// Wait until .load() has been called and has been resolved
|
||||
await this.whenLoaded;
|
||||
|
||||
this._watch = this.dependencies.watch(this.localFolderPath, {
|
||||
// For adding and removing symlinks to work, the depth has to be 1.
|
||||
depth: 1,
|
||||
@@ -174,11 +158,11 @@ export class ExtensionDiscovery {
|
||||
},
|
||||
})
|
||||
// Extension add is detected by watching "<extensionDir>/package.json" add
|
||||
.on("add", this.handleWatchFileAdd)
|
||||
.on("add", (path) => void this.handleWatchFileAdd(path))
|
||||
// Extension remove is detected by watching "<extensionDir>" unlink
|
||||
.on("unlinkDir", this.handleWatchUnlinkEvent)
|
||||
.on("unlinkDir", (path) => void this.handleWatchUnlinkEvent(path))
|
||||
// Extension remove is detected by watching "<extensionSymLink>" unlink
|
||||
.on("unlink", this.handleWatchUnlinkEvent);
|
||||
.on("unlink", (path) => void this.handleWatchUnlinkEvent(path));
|
||||
}
|
||||
|
||||
async stopWatchingExtensions() {
|
||||
@@ -213,7 +197,7 @@ export class ExtensionDiscovery {
|
||||
this.events.emit("add", extension);
|
||||
}
|
||||
} catch (error) {
|
||||
this.dependencies.logger.error(`${logModule}: failed to add extension: ${error}`, { error });
|
||||
this.dependencies.logger.error(`${logModule}: failed to add extension: ${String(error)}`, { error });
|
||||
} finally {
|
||||
this.dependencies.extensionInstallationStateStore.clearInstallingFromMain(manifestPath);
|
||||
}
|
||||
@@ -309,7 +293,7 @@ export class ExtensionDiscovery {
|
||||
|
||||
const extensions = await this.ensureExtensions();
|
||||
|
||||
this.isLoaded = true;
|
||||
this.isLoaded.set(true);
|
||||
|
||||
return extensions;
|
||||
}
|
||||
@@ -362,7 +346,7 @@ export class ExtensionDiscovery {
|
||||
// ignore this error, probably from .DS_Store file
|
||||
this.dependencies.logger.debug(`${logModule}: failed to load extension manifest through a not-dir-like at ${manifestPath}`);
|
||||
} else {
|
||||
this.dependencies.logger.error(`${logModule}: can't load extension manifest at ${manifestPath}: ${error}`);
|
||||
this.dependencies.logger.error(`${logModule}: can't load extension manifest at ${manifestPath}: ${String(error)}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -410,12 +394,12 @@ export class ExtensionDiscovery {
|
||||
}
|
||||
|
||||
toJSON(): ExtensionDiscoveryChannelMessage {
|
||||
return toJS({
|
||||
isLoaded: this.isLoaded,
|
||||
});
|
||||
return {
|
||||
isLoaded: this.isLoaded.get(),
|
||||
};
|
||||
}
|
||||
|
||||
broadcast(): void {
|
||||
broadcastMessage(extensionDiscoveryStateChannel, this.toJSON());
|
||||
void broadcastMessage(extensionDiscoveryStateChannel, this.toJSON());
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -35,11 +35,11 @@ export class ExtensionInstallationStateStore {
|
||||
|
||||
bindIpcListeners = () => {
|
||||
ipcRenderer
|
||||
.on(installingFromMainChannel, (event, extId) => {
|
||||
.on(installingFromMainChannel, (event, extId: string) => {
|
||||
this.setInstalling(extId);
|
||||
})
|
||||
|
||||
.on(clearInstallingFromMainChannel, (event, extId) => {
|
||||
.on(clearInstallingFromMainChannel, (event, extId: string) => {
|
||||
this.clearInstalling(extId);
|
||||
});
|
||||
};
|
||||
@@ -68,7 +68,7 @@ export class ExtensionInstallationStateStore {
|
||||
* @param extId the ID of the extension
|
||||
*/
|
||||
setInstallingFromMain = (extId: string): void => {
|
||||
broadcastMessage(installingFromMainChannel, extId);
|
||||
void broadcastMessage(installingFromMainChannel, extId);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -76,7 +76,7 @@ export class ExtensionInstallationStateStore {
|
||||
* @param extId the ID of the extension
|
||||
*/
|
||||
clearInstallingFromMain = (extId: string): void => {
|
||||
broadcastMessage(clearInstallingFromMainChannel, extId);
|
||||
void broadcastMessage(clearInstallingFromMainChannel, extId);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -89,13 +89,13 @@ export class ExtensionInstallationStateStore {
|
||||
const preInstallStepId = uuid.v4();
|
||||
|
||||
this.dependencies.logger.debug(
|
||||
`${Prefix}: starting a new preinstall phase: ${preInstallStepId}`,
|
||||
`${Prefix}: starting a new pre-install phase: ${preInstallStepId}`,
|
||||
);
|
||||
this.preInstallIds.add(preInstallStepId);
|
||||
|
||||
return disposer(() => {
|
||||
this.preInstallIds.delete(preInstallStepId);
|
||||
this.dependencies.logger.debug(`${Prefix}: ending a preinstall phase: ${preInstallStepId}`);
|
||||
this.dependencies.logger.debug(`${Prefix}: ending a pre-install phase: ${preInstallStepId}`);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -225,23 +225,23 @@ export class ExtensionInstallationStateStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* The current number of extensions preinstalling
|
||||
* The current number of extensions pre-installing
|
||||
*/
|
||||
get preinstalling(): number {
|
||||
get preInstalling(): number {
|
||||
return this.preInstallIds.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is at least one extension currently downloading
|
||||
*/
|
||||
get anyPreinstalling(): boolean {
|
||||
return this.preinstalling > 0;
|
||||
get anyPreInstalling(): boolean {
|
||||
return this.preInstalling > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is at least one installing or preinstalling step taking place
|
||||
* If there is at least one installing or pre-installing step taking place
|
||||
*/
|
||||
get anyPreInstallingOrInstalling(): boolean {
|
||||
return this.anyInstalling || this.anyPreinstalling;
|
||||
return this.anyInstalling || this.anyPreInstalling;
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,10 @@
|
||||
import { ipcMain, ipcRenderer } from "electron";
|
||||
import { isEqual } from "lodash";
|
||||
import type { ObservableMap } from "mobx";
|
||||
import { runInAction, action, computed, toJS, observable, reaction, when } from "mobx";
|
||||
import { runInAction, computed, toJS, observable, reaction, when } from "mobx";
|
||||
import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../../common/ipc";
|
||||
import { isDefined, iter } from "@k8slens/utilities";
|
||||
import type { ExternalInstalledExtension, InstalledExtension, LensExtensionConstructor, LensExtensionId, BundledExtension, BundledInstalledExtension, LegacyLensExtension } from "@k8slens/legacy-extensions";
|
||||
import type { LensExtensionId, ExternalInstalledExtension, InstalledExtension, LensExtensionConstructor, BundledExtension, BundledInstalledExtension, LegacyLensExtension } from "@k8slens/legacy-extensions";
|
||||
import type { LensExtension } from "../lens-extension";
|
||||
import { extensionLoaderFromMainChannel, extensionLoaderFromRendererChannel } from "../../common/ipc/extension-handling";
|
||||
import { requestExtensionLoaderInitialState } from "../../renderer/ipc";
|
||||
@@ -104,7 +104,7 @@ export class ExtensionLoader {
|
||||
if (ipcMain) {
|
||||
await this.initMain();
|
||||
} else {
|
||||
await this.initRenderer();
|
||||
this.initRenderer();
|
||||
}
|
||||
|
||||
await when(() => this.isLoaded.get());
|
||||
@@ -130,8 +130,7 @@ export class ExtensionLoader {
|
||||
this.extensions.set(extension.id, extension);
|
||||
}
|
||||
|
||||
@action
|
||||
removeInstance(lensExtensionId: LensExtensionId) {
|
||||
async removeInstance(lensExtensionId: LensExtensionId) {
|
||||
this.dependencies.logger.info(`${logModule} deleting extension instance ${lensExtensionId}`);
|
||||
const instance = this.dependencies.extensionInstances.get(lensExtensionId);
|
||||
|
||||
@@ -140,22 +139,25 @@ export class ExtensionLoader {
|
||||
}
|
||||
|
||||
try {
|
||||
instance.disable();
|
||||
await instance.disable();
|
||||
|
||||
const extension = this.dependencies.getExtension(instance);
|
||||
|
||||
extension.deregister();
|
||||
|
||||
this.onRemoveExtensionId.emit(instance.id);
|
||||
this.dependencies.extensionInstances.delete(lensExtensionId);
|
||||
this.nonInstancesByName.delete(instance.name);
|
||||
|
||||
runInAction(() => {
|
||||
this.dependencies.extensionInstances.delete(lensExtensionId);
|
||||
this.nonInstancesByName.delete(instance.name);
|
||||
});
|
||||
} catch (error) {
|
||||
this.dependencies.logger.error(`${logModule}: deactivation extension error`, { lensExtensionId, error });
|
||||
}
|
||||
}
|
||||
|
||||
removeExtension(lensExtensionId: LensExtensionId) {
|
||||
this.removeInstance(lensExtensionId);
|
||||
async removeExtension(lensExtensionId: LensExtensionId) {
|
||||
await this.removeInstance(lensExtensionId);
|
||||
|
||||
if (!this.extensions.delete(lensExtensionId)) {
|
||||
throw new Error(`Can't remove extension ${lensExtensionId}, doesn't exist.`);
|
||||
@@ -185,7 +187,7 @@ export class ExtensionLoader {
|
||||
});
|
||||
}
|
||||
|
||||
protected async initRenderer() {
|
||||
protected initRenderer() {
|
||||
const extensionListHandler = (extensions: [LensExtensionId, InstalledExtension][]) => {
|
||||
runInAction(() => {
|
||||
this.isLoaded.set(true);
|
||||
@@ -197,12 +199,12 @@ export class ExtensionLoader {
|
||||
// Remove deleted extensions in renderer side only
|
||||
this.extensions.forEach((_, lensExtensionId) => {
|
||||
if (!receivedExtensionIds.includes(lensExtensionId)) {
|
||||
this.removeExtension(lensExtensionId);
|
||||
void this.removeExtension(lensExtensionId);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
requestExtensionLoaderInitialState().then(extensionListHandler);
|
||||
void requestExtensionLoaderInitialState().then(extensionListHandler);
|
||||
ipcRendererOn(extensionLoaderFromMainChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => {
|
||||
extensionListHandler(extensions);
|
||||
});
|
||||
@@ -213,7 +215,7 @@ export class ExtensionLoader {
|
||||
? extensionLoaderFromRendererChannel
|
||||
: extensionLoaderFromMainChannel;
|
||||
|
||||
broadcastMessage(channel, Array.from(this.extensions));
|
||||
void broadcastMessage(channel, Array.from(this.extensions));
|
||||
}
|
||||
|
||||
syncExtensions(extensions: [LensExtensionId, InstalledExtension][]) {
|
||||
@@ -269,9 +271,10 @@ export class ExtensionLoader {
|
||||
await Promise.all(
|
||||
extensions.map(extension =>
|
||||
// If extension activation fails, log error
|
||||
extension.activated.catch((error) => {
|
||||
this.dependencies.logger.error(`${logModule}: activation extension error`, { ext: extension.installedExtension, error });
|
||||
}),
|
||||
extension.activated
|
||||
.catch((error: unknown) => {
|
||||
this.dependencies.logger.error(`${logModule}: activation extension error`, { ext: extension.installedExtension, error });
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -282,9 +285,10 @@ export class ExtensionLoader {
|
||||
});
|
||||
|
||||
return extensions.map(extension => {
|
||||
const loaded = extension.instance.enable().catch((err) => {
|
||||
this.dependencies.logger.error(`${logModule}: failed to enable`, { ext: extension, err });
|
||||
});
|
||||
const loaded = extension.instance.enable()
|
||||
.catch((error: unknown) => {
|
||||
this.dependencies.logger.error(`${logModule}: failed to enable`, { ext: extension, error });
|
||||
});
|
||||
|
||||
return {
|
||||
isBundled: extension.installedExtension.isBundled,
|
||||
@@ -300,9 +304,9 @@ export class ExtensionLoader {
|
||||
// 3. Call .enable for each extension
|
||||
// 4. Return ExtensionLoading[]
|
||||
|
||||
return [...installedExtensions.entries()]
|
||||
const promises = [...installedExtensions.entries()]
|
||||
.filter((entry): entry is [string, ExternalInstalledExtension] => !entry[1].isBundled)
|
||||
.map(([extId, installedExtension]) => {
|
||||
.map(async ([extId, installedExtension]) => {
|
||||
const alreadyInit = this.dependencies.extensionInstances.has(extId) || this.nonInstancesByName.has(installedExtension.manifest.name);
|
||||
|
||||
if (installedExtension.isCompatible && installedExtension.isEnabled && !alreadyInit) {
|
||||
@@ -328,11 +332,13 @@ export class ExtensionLoader {
|
||||
this.dependencies.logger.error(`${logModule}: error loading extension`, { ext: installedExtension, err });
|
||||
}
|
||||
} else if (!installedExtension.isEnabled && alreadyInit) {
|
||||
this.removeInstance(extId);
|
||||
await this.removeInstance(extId);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
});
|
||||
|
||||
return (await Promise.all(promises))
|
||||
.filter(isDefined);
|
||||
}
|
||||
|
||||
@@ -368,9 +374,10 @@ export class ExtensionLoader {
|
||||
const extAbsolutePath = this.dependencies.joinPaths(this.dependencies.getDirnameOfPath(extension.manifestPath), extRelativePath);
|
||||
|
||||
try {
|
||||
return require(/* webpackIgnore: true */ extAbsolutePath).default;
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
return (require(/* webpackIgnore: true */ extAbsolutePath) as unknown as { default: LensExtensionConstructor | null }).default;
|
||||
} catch (error) {
|
||||
const message = (error instanceof Error ? error.stack : undefined) || error;
|
||||
const message = (error instanceof Error ? error.stack : undefined) || String(error);
|
||||
|
||||
this.dependencies.logger.error(`${logModule}: can't load ${this.dependencies.extensionEntryPointName} for "${extension.manifest.name}": ${message}`, { extension });
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { Injectable } from "@ogre-tools/injectable";
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type Injectables = Injectable<any, any, any>[];
|
||||
export type Registration = Injectables | IComputedValue<Injectables>;
|
||||
export type ExtensionRegistrator = (extension: LegacyLensExtension) => Registration;
|
||||
|
||||
@@ -39,6 +39,7 @@ export abstract class IpcMain extends IpcRegistrar {
|
||||
* @param listener The function that will be called with the arguments of the broadcast
|
||||
* @returns An optional disposer, Lens will cleanup when the extension is disabled or uninstalled even if this is not called
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
listen(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): Disposer {
|
||||
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||
const cleanup = once(() => {
|
||||
@@ -59,6 +60,7 @@ export abstract class IpcMain extends IpcRegistrar {
|
||||
* @param channel The name of the RPC
|
||||
* @param handler The remote procedure that is called
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
handle(channel: string, handler: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any): void {
|
||||
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export abstract class IpcRegistrar extends Singleton {
|
||||
* @param channel The channel to broadcast to your whole extension, both `main` and `renderer`
|
||||
* @param args The arguments passed to all listeners
|
||||
*/
|
||||
broadcast(channel: string, ...args: any[]): void {
|
||||
broadcastMessage(`extensions@${this[IpcPrefix]}:${channel}`, ...args);
|
||||
broadcast(channel: string, ...args: unknown[]): void {
|
||||
void broadcastMessage(`extensions@${this[IpcPrefix]}:${channel}`, ...args);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ export abstract class IpcRenderer extends IpcRegistrar {
|
||||
* @param listener The function that will be called with the arguments of the broadcast
|
||||
* @returns An optional disposer, Lens will cleanup even if this is not called
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
listen(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): Disposer {
|
||||
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||
const cleanup = once(() => {
|
||||
@@ -48,7 +49,8 @@ export abstract class IpcRenderer extends IpcRegistrar {
|
||||
* @param args The arguments to pass to the RPC
|
||||
* @returns A promise of the resulting value
|
||||
*/
|
||||
invoke(channel: string, ...args: any[]): Promise<any> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
invoke(channel: string, ...args: unknown[]): Promise<any> {
|
||||
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||
|
||||
return ipcRenderer.invoke(prefixedChannel, ...args);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { action, computed, makeObservable, observable } from "mobx";
|
||||
import { computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import { disposer } from "@k8slens/utilities";
|
||||
import type { ProtocolHandlerRegistration } from "../common/protocol-handler/registration";
|
||||
import type { InstalledExtension, LensExtensionId, LensExtensionManifest } from "@k8slens/legacy-extensions";
|
||||
@@ -83,30 +83,34 @@ export class LensExtension {
|
||||
return this.dependencies.ensureHashedDirectoryForExtension(this.storeName);
|
||||
}
|
||||
|
||||
@action
|
||||
async enable() {
|
||||
if (this._isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isEnabled = true;
|
||||
runInAction(() => {
|
||||
this._isEnabled = true;
|
||||
});
|
||||
this.dependencies.logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@action
|
||||
async disable() {
|
||||
if (!this._isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isEnabled = false;
|
||||
runInAction(() => {
|
||||
this._isEnabled = false;
|
||||
});
|
||||
|
||||
try {
|
||||
await this.onDeactivate();
|
||||
this[Disposers]();
|
||||
this.dependencies.logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
||||
} catch (error) {
|
||||
this.dependencies.logger.error(`[EXTENSION]: disabling ${this.name}@${this.version} threw an error: ${error}`);
|
||||
this.dependencies.logger.error(`[EXTENSION]: disabling ${this.name}@${this.version} threw an error: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ export class LensMainExtension extends LensExtension {
|
||||
*/
|
||||
terminalShellEnvModifier?: ShellEnvModifier;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async navigate(pageId?: string, params?: Record<string, any>, frameId?: number) {
|
||||
await this.dependencies.navigate(this.id, pageId, params, frameId);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import getExtensionPageParametersInjectable from "../renderer/routes/get-extensi
|
||||
import navigateToRouteInjectable from "../renderer/routes/navigate-to-route.injectable";
|
||||
import routesInjectable from "../renderer/routes/routes.injectable";
|
||||
import ensureHashedDirectoryForExtensionInjectable from "./extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable";
|
||||
import type { PageParamInit } from "./renderer-api/navigation";
|
||||
|
||||
interface LensRendererExtensionDependencies extends LensExtensionDependencies {
|
||||
navigateToRoute: NavigateToRoute;
|
||||
@@ -53,6 +54,10 @@ interface LensRendererExtensionDependencies extends LensExtensionDependencies {
|
||||
readonly categoryRegistry: CatalogCategoryRegistry;
|
||||
}
|
||||
|
||||
export const getPageRegistration = <Params>(reg: PageRegistration<Params>): PageRegistration<Params> => reg;
|
||||
export const getParamInitRegistration = <T>(reg: Omit<PageParamInit<T>, "name">) => reg;
|
||||
export const getParamsRegistration = <P>(reg: P): P => reg;
|
||||
|
||||
export class LensRendererExtension extends LensExtension {
|
||||
globalPages: PageRegistration[] = [];
|
||||
clusterPages: PageRegistration[] = [];
|
||||
@@ -95,7 +100,7 @@ export class LensRendererExtension extends LensExtension {
|
||||
super(deps, extension);
|
||||
}
|
||||
|
||||
async navigate(pageId?: string, params: object = {}) {
|
||||
navigate(pageId?: string, params: object = {}) {
|
||||
const routes = this.dependencies.routes.get();
|
||||
const targetRegistration = [...this.globalPages, ...this.clusterPages]
|
||||
.find(registration => registration.id === (pageId || undefined));
|
||||
@@ -127,6 +132,7 @@ export class LensRendererExtension extends LensExtension {
|
||||
|
||||
this.dependencies.navigateToRoute(targetRoute, {
|
||||
query,
|
||||
parameters: {},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -138,8 +144,9 @@ export class LensRendererExtension extends LensExtension {
|
||||
*
|
||||
* @deprecated Switch to using "enabled" or "visible" properties in each registration together with `activeCluster`
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
async isEnabledForCluster(cluster: KubernetesCluster): Promise<Boolean> {
|
||||
return (void cluster) || true;
|
||||
return Promise.resolve((void cluster) || true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @deprecated This function never works
|
||||
* @returns false
|
||||
*/
|
||||
export function isAllowedResource(...args: any[]) {
|
||||
export function isAllowedResource(...args: unknown[]) {
|
||||
return Boolean(void args);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user