mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix extensions not being able to be installed in some cases
- Specifically, when an empty folder exists with the name that would be used to install it - Make extensions and IPC more injected, so that ExtensionInstallationStateStore can be removed - Add test to cover bug Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
c46d0036cc
commit
c2a359295b
@ -78,7 +78,7 @@ describe("BaseStore", () => {
|
|||||||
let store: TestStore;
|
let store: TestStore;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
const dis = await getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
dis.mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory");
|
dis.mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory");
|
||||||
|
|
||||||
|
|||||||
@ -80,7 +80,7 @@ describe("cluster-store", () => {
|
|||||||
let createCluster: (model: ClusterModel) => Cluster;
|
let createCluster: (model: ClusterModel) => Cluster;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
const dis = await getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
|
|
||||||
|
|||||||
@ -113,7 +113,7 @@ const awsCluster = getMockCatalogEntity({
|
|||||||
|
|
||||||
describe("HotbarStore", () => {
|
describe("HotbarStore", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
|
||||||
|
|||||||
@ -40,7 +40,7 @@ describe("user store tests", () => {
|
|||||||
let mainDi: DependencyInjectionContainer;
|
let mainDi: DependencyInjectionContainer;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
const dis = await getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
|
|
||||||
|
|||||||
12
src/common/app-paths/app-path-channel-injection-token.ts
Normal file
12
src/common/app-paths/app-path-channel-injection-token.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import type { AppPaths } from "./app-paths";
|
||||||
|
import type { Channel } from "../communication/channel";
|
||||||
|
|
||||||
|
export type AppPathsChannel = Channel<[], AppPaths>;
|
||||||
|
|
||||||
|
export const appPathsInjectionChannelToken = getInjectionToken<AppPathsChannel>();
|
||||||
|
export const appPathsIpcChannel = "app-paths";
|
||||||
@ -3,13 +3,6 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
import type { PathName } from "./app-path-names";
|
import type { AppPaths } from "./app-paths";
|
||||||
import { createChannel } from "../ipc-channel/create-channel/create-channel";
|
|
||||||
|
|
||||||
export type AppPaths = Record<PathName, string>;
|
|
||||||
|
|
||||||
export const appPathsInjectionToken = getInjectionToken<AppPaths>();
|
export const appPathsInjectionToken = getInjectionToken<AppPaths>();
|
||||||
|
|
||||||
export const appPathsIpcChannel = createChannel<AppPaths>("app-paths");
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,22 +3,22 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { DependencyInjectionContainer } from "@ogre-tools/injectable";
|
import type { DependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||||
import { AppPaths, appPathsInjectionToken } from "./app-path-injection-token";
|
|
||||||
import getElectronAppPathInjectable from "../../main/app-paths/get-electron-app-path/get-electron-app-path.injectable";
|
import getElectronAppPathInjectable from "../../main/app-paths/get-electron-app-path/get-electron-app-path.injectable";
|
||||||
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
||||||
import type { PathName } from "./app-path-names";
|
import type { AppPaths, PathName } from "./app-paths";
|
||||||
import setElectronAppPathInjectable from "../../main/app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
import setElectronAppPathInjectable from "../../main/app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
||||||
import appNameInjectable from "../../main/app-paths/app-name/app-name.injectable";
|
import appNameInjectable from "../../main/app-paths/app-name/app-name.injectable";
|
||||||
import directoryForIntegrationTestingInjectable from "../../main/app-paths/directory-for-integration-testing/directory-for-integration-testing.injectable";
|
import directoryForIntegrationTestingInjectable from "../../main/app-paths/directory-for-integration-testing/directory-for-integration-testing.injectable";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { appPathsInjectionToken } from "./app-path-injection-token";
|
||||||
|
|
||||||
describe("app-paths", () => {
|
describe("app-paths", () => {
|
||||||
let mainDi: DependencyInjectionContainer;
|
let mainDi: DependencyInjectionContainer;
|
||||||
let rendererDi: DependencyInjectionContainer;
|
let rendererDi: DependencyInjectionContainer;
|
||||||
let runSetups: () => Promise<void[]>;
|
let runSetups: () => Promise<void>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
const dis = await getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mainDi = dis.mainDi;
|
mainDi = dis.mainDi;
|
||||||
rendererDi = dis.rendererDi;
|
rendererDi = dis.rendererDi;
|
||||||
@ -45,15 +45,12 @@ describe("app-paths", () => {
|
|||||||
|
|
||||||
mainDi.override(
|
mainDi.override(
|
||||||
getElectronAppPathInjectable,
|
getElectronAppPathInjectable,
|
||||||
() =>
|
() => (key: PathName): string | null => defaultAppPathsStub[key],
|
||||||
(key: PathName): string | null =>
|
|
||||||
defaultAppPathsStub[key],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
mainDi.override(
|
mainDi.override(
|
||||||
setElectronAppPathInjectable,
|
setElectronAppPathInjectable,
|
||||||
() =>
|
() => (key: PathName, path: string): void => {
|
||||||
(key: PathName, path: string): void => {
|
|
||||||
defaultAppPathsStub[key] = path;
|
defaultAppPathsStub[key] = path;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -123,7 +120,7 @@ describe("app-paths", () => {
|
|||||||
await runSetups();
|
await runSetups();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("given in renderer, when injecting path for app data, has integration specific app data path", () => {
|
it("when in renderer, when injecting path for app data, has integration specific app data path", () => {
|
||||||
const { appData, userData } = rendererDi.inject(appPathsInjectionToken);
|
const { appData, userData } = rendererDi.inject(appPathsInjectionToken);
|
||||||
|
|
||||||
expect({ appData, userData }).toEqual({
|
expect({ appData, userData }).toEqual({
|
||||||
@ -132,8 +129,8 @@ describe("app-paths", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("given in main, when injecting path for app data, has integration specific app data path", () => {
|
it("when in main, when injecting path for app data, has integration specific app data path", () => {
|
||||||
const { appData, userData } = rendererDi.inject(appPathsInjectionToken);
|
const { appData, userData } = mainDi.inject(appPathsInjectionToken);
|
||||||
|
|
||||||
expect({ appData, userData }).toEqual({
|
expect({ appData, userData }).toEqual({
|
||||||
appData: "some-integration-testing-app-data",
|
appData: "some-integration-testing-app-data",
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import type { app as electronApp } from "electron";
|
import type { app as electronApp } from "electron";
|
||||||
|
|
||||||
|
export type AppPaths = Record<PathName, string>;
|
||||||
export type PathName = Parameters<typeof electronApp["getPath"]>[0];
|
export type PathName = Parameters<typeof electronApp["getPath"]>[0];
|
||||||
|
|
||||||
export const pathNames: PathName[] = [
|
export const pathNames: PathName[] = [
|
||||||
@ -7,8 +7,7 @@ import path from "path";
|
|||||||
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
|
||||||
const directoryForBinariesInjectable = getInjectable({
|
const directoryForBinariesInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) => path.join(di.inject(directoryForUserDataInjectable), "binaries"),
|
||||||
path.join(di.inject(directoryForUserDataInjectable), "binaries"),
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,8 +7,7 @@ import directoryForUserDataInjectable from "../directory-for-user-data/directory
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
const directoryForKubeConfigsInjectable = getInjectable({
|
const directoryForKubeConfigsInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) => path.resolve(di.inject(directoryForUserDataInjectable), "kubeconfigs"),
|
||||||
path.resolve(di.inject(directoryForUserDataInjectable), "kubeconfigs"),
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|||||||
13
src/common/communication/broadcast.injectable.ts
Normal file
13
src/common/communication/broadcast.injectable.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { broadcastMessage } from "../ipc";
|
||||||
|
|
||||||
|
const broadcastInjectable = getInjectable({
|
||||||
|
instantiate: () => broadcastMessage as (channel: string, ...args: any[]) => void,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default broadcastInjectable;
|
||||||
18
src/common/communication/channel.ts
Normal file
18
src/common/communication/channel.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Channel represent an link that renderer can request on, given some
|
||||||
|
* parameters, and get a value back
|
||||||
|
*/
|
||||||
|
export type Channel<Parameters extends any[], Value> = (...args: Parameters) => Promise<Value>;
|
||||||
|
|
||||||
|
export type ChannelValue<T> = T extends Channel<any, infer Value>
|
||||||
|
? Value
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type ChannelParameters<T> = T extends Channel<infer Parameters, any>
|
||||||
|
? Parameters
|
||||||
|
: never;
|
||||||
10
src/common/communication/emitter.ts
Normal file
10
src/common/communication/emitter.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An EmitterChannel represents a broadcast point where any side can emit data
|
||||||
|
* on
|
||||||
|
*/
|
||||||
|
export type EmitterChannel<Parameters extends any[]> = (...args: Parameters) => void;
|
||||||
10
src/common/communication/ipc-on-event-injection-token.ts
Normal file
10
src/common/communication/ipc-on-event-injection-token.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export type IpcOnEvent = (channel: string, ...args: any[]) => void;
|
||||||
|
|
||||||
|
export const ipcOnEventInjectionToken = getInjectionToken<IpcOnEvent>();
|
||||||
33
src/common/communication/register-emitter.injectable.ts
Normal file
33
src/common/communication/register-emitter.injectable.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { LensLogger } from "../logger";
|
||||||
|
import broadcastInjectable from "./broadcast.injectable";
|
||||||
|
import type { EmitterChannel } from "./emitter";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
broadcast: (name: string, ...args: any[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerEmitterChannel({ broadcast }: Dependencies) {
|
||||||
|
return function <Parameters extends any[]>(name: string, logger?: LensLogger): EmitterChannel<Parameters> {
|
||||||
|
return (...args) => {
|
||||||
|
logger?.info(`Broadcasting on ${name}`, { args });
|
||||||
|
broadcast(name, ...args);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This dependency is for registering the source of events
|
||||||
|
*/
|
||||||
|
const registerEmitterChannelInjectable = getInjectable({
|
||||||
|
instantiate: (di) => registerEmitterChannel({
|
||||||
|
broadcast: di.inject(broadcastInjectable),
|
||||||
|
}),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default registerEmitterChannelInjectable;
|
||||||
33
src/common/communication/register-event-sink.injectable.ts
Normal file
33
src/common/communication/register-event-sink.injectable.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { EmitterChannel } from "../../common/communication/emitter";
|
||||||
|
import type { LensLogger } from "../../common/logger";
|
||||||
|
import { ipcOnEventInjectionToken } from "./ipc-on-event-injection-token";
|
||||||
|
|
||||||
|
interface Depencencies {
|
||||||
|
onEvent: (channel: string, ...args: any[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerEventSink = ({ onEvent }: Depencencies) => (
|
||||||
|
function <Parameters extends any[]>(name: string, listener: (...args: Parameters) => void, logger?: LensLogger): EmitterChannel<Parameters> {
|
||||||
|
onEvent(name, (...args: Parameters) => {
|
||||||
|
logger?.info(`Received event on ${name}`, { args });
|
||||||
|
listener(...args);
|
||||||
|
});
|
||||||
|
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const registerEventSinkInjectable = getInjectable({
|
||||||
|
instantiate: (di) => registerEventSink({
|
||||||
|
onEvent: di.inject(ipcOnEventInjectionToken),
|
||||||
|
}),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default registerEventSinkInjectable;
|
||||||
15
src/common/fs/remove-dir.injectable.ts
Normal file
15
src/common/fs/remove-dir.injectable.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
|
export type RemoveDir = (dir: string) => Promise<void>;
|
||||||
|
|
||||||
|
const removeDirInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(fsInjectable).remove as RemoveDir,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default removeDirInjectable;
|
||||||
@ -1,8 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
export interface Channel<TInstance> {
|
|
||||||
name: string;
|
|
||||||
_template: TInstance;
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import type { Channel } from "../channel";
|
|
||||||
|
|
||||||
export const createChannel = <TInstance>(name: string): Channel<TInstance> => ({
|
|
||||||
name,
|
|
||||||
_template: null,
|
|
||||||
});
|
|
||||||
@ -10,6 +10,15 @@ import { consoleFormat } from "winston-console-format";
|
|||||||
import { isDebugging, isTestEnv } from "./vars";
|
import { isDebugging, isTestEnv } from "./vars";
|
||||||
import BrowserConsole from "winston-transport-browserconsole";
|
import BrowserConsole from "winston-transport-browserconsole";
|
||||||
|
|
||||||
|
export interface LensLogger {
|
||||||
|
error: (...args: any[]) => void;
|
||||||
|
warn: (...args: any[]) => void;
|
||||||
|
info: (...args: any[]) => void;
|
||||||
|
debug: (...args: any[]) => void;
|
||||||
|
verbose: (...args: any[]) => void;
|
||||||
|
silly: (...args: any[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
const logLevel = process.env.LOG_LEVEL
|
const logLevel = process.env.LOG_LEVEL
|
||||||
? process.env.LOG_LEVEL
|
? process.env.LOG_LEVEL
|
||||||
: isDebugging
|
: isDebugging
|
||||||
@ -67,4 +76,4 @@ if (ipcMain) {
|
|||||||
export default winston.createLogger({
|
export default winston.createLogger({
|
||||||
format: format.simple(),
|
format: format.simple(),
|
||||||
transports,
|
transports,
|
||||||
});
|
}) as LensLogger;
|
||||||
|
|||||||
13
src/common/logger/base-logger.injectable.ts
Normal file
13
src/common/logger/base-logger.injectable.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import logger from "../logger";
|
||||||
|
|
||||||
|
const baseLoggerInjectable = getInjectable({
|
||||||
|
instantiate: () => logger,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default baseLoggerInjectable;
|
||||||
33
src/common/logger/create-child-logger.injectable.ts
Normal file
33
src/common/logger/create-child-logger.injectable.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { LensLogger } from "../logger";
|
||||||
|
import baseLoggerInjectable from "./base-logger.injectable";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
baseLogger: LensLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createChildLogger = ({ baseLogger }: Dependencies) => (
|
||||||
|
(prefix: string): LensLogger => {
|
||||||
|
return {
|
||||||
|
debug: (message, info) => baseLogger.debug(`${prefix}: ${message}`, info),
|
||||||
|
warn: (message, info) => baseLogger.warn(`${prefix}: ${message}`, info),
|
||||||
|
error: (message, info) => baseLogger.error(`${prefix}: ${message}`, info),
|
||||||
|
verbose: (message, info) => baseLogger.verbose(`${prefix}: ${message}`, info),
|
||||||
|
info: (message, info) => baseLogger.info(`${prefix}: ${message}`, info),
|
||||||
|
silly: (message, info) => baseLogger.silly(`${prefix}: ${message}`, info),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const createChildLoggerInjectable = getInjectable({
|
||||||
|
instantiate: (di) => createChildLogger({
|
||||||
|
baseLogger: di.inject(baseLoggerInjectable),
|
||||||
|
}),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createChildLoggerInjectable;
|
||||||
13
src/common/utils/unique-id.injectable.ts
Normal file
13
src/common/utils/unique-id.injectable.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { v4 } from "uuid";
|
||||||
|
|
||||||
|
const uniqueIdInjectable = getInjectable({
|
||||||
|
instantiate: () => v4,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default uniqueIdInjectable;
|
||||||
@ -110,7 +110,7 @@ describe("ExtensionLoader", () => {
|
|||||||
let updateExtensionStateMock: jest.Mock;
|
let updateExtensionStateMock: jest.Mock;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
const dis = await getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
|
|
||||||
|
|||||||
@ -8,33 +8,22 @@ import extensionLoaderInjectable from "../extension-loader/extension-loader.inje
|
|||||||
import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable";
|
import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable";
|
||||||
import isCompatibleBundledExtensionInjectable from "./is-compatible-bundled-extension/is-compatible-bundled-extension.injectable";
|
import isCompatibleBundledExtensionInjectable from "./is-compatible-bundled-extension/is-compatible-bundled-extension.injectable";
|
||||||
import extensionsStoreInjectable from "../extensions-store/extensions-store.injectable";
|
import extensionsStoreInjectable from "../extensions-store/extensions-store.injectable";
|
||||||
import extensionInstallationStateStoreInjectable from "../extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable";
|
import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable";
|
||||||
import extensionPackageRootDirectoryInjectable from "../extension-installer/extension-package-root-directory/extension-package-root-directory.injectable";
|
import extensionPackageRootDirectoryInjectable from "../extension-installer/extension-package-root-directory/extension-package-root-directory.injectable";
|
||||||
import installExtensionsInjectable from "../extension-installer/install-extensions/install-extensions.injectable";
|
import installExtensionsInjectable from "../extension-installer/install-extensions/install-extensions.injectable";
|
||||||
|
import { clearInstallingChannelInjectionToken, setInstallingChannelInjectionToken } from "../installation-state/state-channels";
|
||||||
|
|
||||||
const extensionDiscoveryInjectable = getInjectable({
|
const extensionDiscoveryInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) => new ExtensionDiscovery({
|
||||||
new ExtensionDiscovery({
|
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||||
extensionsStore: di.inject(extensionsStoreInjectable),
|
extensionsStore: di.inject(extensionsStoreInjectable),
|
||||||
|
setInstalling: di.inject(setInstallingChannelInjectionToken),
|
||||||
extensionInstallationStateStore: di.inject(
|
clearInstalling: di.inject(clearInstallingChannelInjectionToken),
|
||||||
extensionInstallationStateStoreInjectable,
|
isCompatibleBundledExtension: di.inject(isCompatibleBundledExtensionInjectable),
|
||||||
),
|
|
||||||
|
|
||||||
isCompatibleBundledExtension: di.inject(
|
|
||||||
isCompatibleBundledExtensionInjectable,
|
|
||||||
),
|
|
||||||
|
|
||||||
isCompatibleExtension: di.inject(isCompatibleExtensionInjectable),
|
isCompatibleExtension: di.inject(isCompatibleExtensionInjectable),
|
||||||
|
|
||||||
installExtension: di.inject(installExtensionInjectable),
|
installExtension: di.inject(installExtensionInjectable),
|
||||||
installExtensions: di.inject(installExtensionsInjectable),
|
installExtensions: di.inject(installExtensionsInjectable),
|
||||||
|
extensionPackageRootDirectory: di.inject(extensionPackageRootDirectoryInjectable),
|
||||||
extensionPackageRootDirectory: di.inject(
|
|
||||||
extensionPackageRootDirectoryInjectable,
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
|||||||
@ -11,10 +11,8 @@ import * as fse from "fs-extra";
|
|||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import extensionDiscoveryInjectable from "../extension-discovery/extension-discovery.injectable";
|
import extensionDiscoveryInjectable from "../extension-discovery/extension-discovery.injectable";
|
||||||
import type { ExtensionDiscovery } from "../extension-discovery/extension-discovery";
|
import type { ExtensionDiscovery } from "../extension-discovery/extension-discovery";
|
||||||
import installExtensionInjectable
|
import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable";
|
||||||
from "../extension-installer/install-extension/install-extension.injectable";
|
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import directoryForUserDataInjectable
|
|
||||||
from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
|
|
||||||
jest.setTimeout(60_000);
|
jest.setTimeout(60_000);
|
||||||
@ -49,16 +47,15 @@ describe("ExtensionDiscovery", () => {
|
|||||||
let extensionDiscovery: ExtensionDiscovery;
|
let extensionDiscovery: ExtensionDiscovery;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
di.override(installExtensionInjectable, () => () => Promise.resolve());
|
di.override(installExtensionInjectable, () => () => Promise.resolve());
|
||||||
|
|
||||||
mockFs();
|
|
||||||
|
|
||||||
await di.runSetups();
|
await di.runSetups();
|
||||||
|
|
||||||
extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||||
|
mockFs();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import type { ExtensionsStore } from "../extensions-store/extensions-store";
|
|||||||
import type { ExtensionLoader } from "../extension-loader";
|
import type { ExtensionLoader } from "../extension-loader";
|
||||||
import type { LensExtensionId, LensExtensionManifest } from "../lens-extension";
|
import type { LensExtensionId, LensExtensionManifest } from "../lens-extension";
|
||||||
import { isProduction } from "../../common/vars";
|
import { isProduction } from "../../common/vars";
|
||||||
import type { ExtensionInstallationStateStore } from "../extension-installation-state-store/extension-installation-state-store";
|
|
||||||
import type { PackageJson } from "type-fest";
|
import type { PackageJson } from "type-fest";
|
||||||
import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling";
|
import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling";
|
||||||
import { requestInitialExtensionDiscovery } from "../../renderer/ipc";
|
import { requestInitialExtensionDiscovery } from "../../renderer/ipc";
|
||||||
@ -25,12 +24,10 @@ import { requestInitialExtensionDiscovery } from "../../renderer/ipc";
|
|||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
extensionLoader: ExtensionLoader;
|
extensionLoader: ExtensionLoader;
|
||||||
extensionsStore: ExtensionsStore;
|
extensionsStore: ExtensionsStore;
|
||||||
|
setInstalling: (extId: string) => void;
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
clearInstalling: (extId: string) => void;
|
||||||
|
|
||||||
isCompatibleBundledExtension: (manifest: LensExtensionManifest) => boolean;
|
isCompatibleBundledExtension: (manifest: LensExtensionManifest) => boolean;
|
||||||
isCompatibleExtension: (manifest: LensExtensionManifest) => boolean;
|
isCompatibleExtension: (manifest: LensExtensionManifest) => boolean;
|
||||||
|
|
||||||
installExtension: (name: string) => Promise<void>;
|
installExtension: (name: string) => Promise<void>;
|
||||||
installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>
|
installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>
|
||||||
extensionPackageRootDirectory: string;
|
extensionPackageRootDirectory: string;
|
||||||
@ -191,7 +188,7 @@ export class ExtensionDiscovery {
|
|||||||
|
|
||||||
if (path.basename(manifestPath) === manifestFilename && isUnderLocalFolderPath) {
|
if (path.basename(manifestPath) === manifestFilename && isUnderLocalFolderPath) {
|
||||||
try {
|
try {
|
||||||
this.dependencies.extensionInstallationStateStore.setInstallingFromMain(manifestPath);
|
this.dependencies.setInstalling(manifestPath);
|
||||||
const absPath = path.dirname(manifestPath);
|
const absPath = path.dirname(manifestPath);
|
||||||
|
|
||||||
// this.loadExtensionFromPath updates this.packagesJson
|
// this.loadExtensionFromPath updates this.packagesJson
|
||||||
@ -211,7 +208,7 @@ export class ExtensionDiscovery {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`${logModule}: failed to add extension: ${error}`, { error });
|
logger.error(`${logModule}: failed to add extension: ${error}`, { error });
|
||||||
} finally {
|
} finally {
|
||||||
this.dependencies.extensionInstallationStateStore.clearInstallingFromMain(manifestPath);
|
this.dependencies.clearInstalling(manifestPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import { ExtensionInstallationStateStore } from "./extension-installation-state-store";
|
|
||||||
|
|
||||||
const extensionInstallationStateStoreInjectable = getInjectable({
|
|
||||||
instantiate: () => new ExtensionInstallationStateStore(),
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default extensionInstallationStateStoreInjectable;
|
|
||||||
@ -1,244 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { action, computed, observable } from "mobx";
|
|
||||||
import logger from "../../main/logger";
|
|
||||||
import { disposer } from "../../renderer/utils";
|
|
||||||
import type { ExtendableDisposer } from "../../renderer/utils";
|
|
||||||
import * as uuid from "uuid";
|
|
||||||
import { broadcastMessage } from "../../common/ipc";
|
|
||||||
import { ipcRenderer } from "electron";
|
|
||||||
|
|
||||||
export enum ExtensionInstallationState {
|
|
||||||
INSTALLING = "installing",
|
|
||||||
UNINSTALLING = "uninstalling",
|
|
||||||
IDLE = "idle",
|
|
||||||
}
|
|
||||||
|
|
||||||
const Prefix = "[ExtensionInstallationStore]";
|
|
||||||
|
|
||||||
export class ExtensionInstallationStateStore {
|
|
||||||
private InstallingFromMainChannel =
|
|
||||||
"extension-installation-state-store:install";
|
|
||||||
|
|
||||||
private ClearInstallingFromMainChannel =
|
|
||||||
"extension-installation-state-store:clear-install";
|
|
||||||
|
|
||||||
private PreInstallIds = observable.set<string>();
|
|
||||||
private UninstallingExtensions = observable.set<string>();
|
|
||||||
private InstallingExtensions = observable.set<string>();
|
|
||||||
|
|
||||||
bindIpcListeners = () => {
|
|
||||||
ipcRenderer
|
|
||||||
.on(this.InstallingFromMainChannel, (event, extId) => {
|
|
||||||
this.setInstalling(extId);
|
|
||||||
})
|
|
||||||
|
|
||||||
.on(this.ClearInstallingFromMainChannel, (event, extId) => {
|
|
||||||
this.clearInstalling(extId);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strictly transitions an extension from not installing to installing
|
|
||||||
* @param extId the ID of the extension
|
|
||||||
* @throws if state is not IDLE
|
|
||||||
*/
|
|
||||||
@action setInstalling = (extId: string): void => {
|
|
||||||
logger.debug(`${Prefix}: trying to set ${extId} as installing`);
|
|
||||||
|
|
||||||
const curState = this.getInstallationState(extId);
|
|
||||||
|
|
||||||
if (curState !== ExtensionInstallationState.IDLE) {
|
|
||||||
throw new Error(
|
|
||||||
`${Prefix}: cannot set ${extId} as installing. Is currently ${curState}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InstallingExtensions.add(extId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcasts that an extension is being installed by the main process
|
|
||||||
* @param extId the ID of the extension
|
|
||||||
*/
|
|
||||||
setInstallingFromMain = (extId: string): void => {
|
|
||||||
broadcastMessage(this.InstallingFromMainChannel, extId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcasts that an extension is no longer being installed by the main process
|
|
||||||
* @param extId the ID of the extension
|
|
||||||
*/
|
|
||||||
clearInstallingFromMain = (extId: string): void => {
|
|
||||||
broadcastMessage(this.ClearInstallingFromMainChannel, extId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks the start of a pre-install phase of an extension installation. The
|
|
||||||
* part of the installation before the tarball has been unpacked and the ID
|
|
||||||
* determined.
|
|
||||||
* @returns a disposer which should be called to mark the end of the install phase
|
|
||||||
*/
|
|
||||||
@action startPreInstall = (): ExtendableDisposer => {
|
|
||||||
const preInstallStepId = uuid.v4();
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`${Prefix}: starting a new preinstall phase: ${preInstallStepId}`,
|
|
||||||
);
|
|
||||||
this.PreInstallIds.add(preInstallStepId);
|
|
||||||
|
|
||||||
return disposer(() => {
|
|
||||||
this.PreInstallIds.delete(preInstallStepId);
|
|
||||||
logger.debug(`${Prefix}: ending a preinstall phase: ${preInstallStepId}`);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strictly transitions an extension from not uninstalling to uninstalling
|
|
||||||
* @param extId the ID of the extension
|
|
||||||
* @throws if state is not IDLE
|
|
||||||
*/
|
|
||||||
@action setUninstalling = (extId: string): void => {
|
|
||||||
logger.debug(`${Prefix}: trying to set ${extId} as uninstalling`);
|
|
||||||
|
|
||||||
const curState = this.getInstallationState(extId);
|
|
||||||
|
|
||||||
if (curState !== ExtensionInstallationState.IDLE) {
|
|
||||||
throw new Error(
|
|
||||||
`${Prefix}: cannot set ${extId} as uninstalling. Is currently ${curState}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.UninstallingExtensions.add(extId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strictly clears the INSTALLING state of an extension
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
* @throws if state is not INSTALLING
|
|
||||||
*/
|
|
||||||
@action clearInstalling = (extId: string): void => {
|
|
||||||
logger.debug(`${Prefix}: trying to clear ${extId} as installing`);
|
|
||||||
|
|
||||||
const curState = this.getInstallationState(extId);
|
|
||||||
|
|
||||||
switch (curState) {
|
|
||||||
case ExtensionInstallationState.INSTALLING:
|
|
||||||
return void this.InstallingExtensions.delete(extId);
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`${Prefix}: cannot clear INSTALLING state for ${extId}, it is currently ${curState}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strictly clears the UNINSTALLING state of an extension
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
* @throws if state is not UNINSTALLING
|
|
||||||
*/
|
|
||||||
@action clearUninstalling = (extId: string): void => {
|
|
||||||
logger.debug(`${Prefix}: trying to clear ${extId} as uninstalling`);
|
|
||||||
|
|
||||||
const curState = this.getInstallationState(extId);
|
|
||||||
|
|
||||||
switch (curState) {
|
|
||||||
case ExtensionInstallationState.UNINSTALLING:
|
|
||||||
return void this.UninstallingExtensions.delete(extId);
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`${Prefix}: cannot clear UNINSTALLING state for ${extId}, it is currently ${curState}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current state of the extension. IDLE is default value.
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
*/
|
|
||||||
getInstallationState = (extId: string): ExtensionInstallationState => {
|
|
||||||
if (this.InstallingExtensions.has(extId)) {
|
|
||||||
return ExtensionInstallationState.INSTALLING;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.UninstallingExtensions.has(extId)) {
|
|
||||||
return ExtensionInstallationState.UNINSTALLING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExtensionInstallationState.IDLE;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the extension is currently INSTALLING
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
*/
|
|
||||||
isExtensionInstalling = (extId: string): boolean =>
|
|
||||||
this.getInstallationState(extId) === ExtensionInstallationState.INSTALLING;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the extension is currently UNINSTALLING
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
*/
|
|
||||||
isExtensionUninstalling = (extId: string): boolean =>
|
|
||||||
this.getInstallationState(extId) ===
|
|
||||||
ExtensionInstallationState.UNINSTALLING;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the extension is currently IDLE
|
|
||||||
* @param extId The ID of the extension
|
|
||||||
*/
|
|
||||||
isExtensionIdle = (extId: string): boolean =>
|
|
||||||
this.getInstallationState(extId) === ExtensionInstallationState.IDLE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current number of extensions installing
|
|
||||||
*/
|
|
||||||
@computed get installing(): number {
|
|
||||||
return this.InstallingExtensions.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current number of extensions uninstalling
|
|
||||||
*/
|
|
||||||
get uninstalling(): number {
|
|
||||||
return this.UninstallingExtensions.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there is at least one extension currently installing
|
|
||||||
*/
|
|
||||||
get anyInstalling(): boolean {
|
|
||||||
return this.installing > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there is at least one extension currently uninstalling
|
|
||||||
*/
|
|
||||||
get anyUninstalling(): boolean {
|
|
||||||
return this.uninstalling > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current number of extensions preinstalling
|
|
||||||
*/
|
|
||||||
get preinstalling(): number {
|
|
||||||
return this.PreInstallIds.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there is at least one extension currently downloading
|
|
||||||
*/
|
|
||||||
get anyPreinstalling(): boolean {
|
|
||||||
return this.preinstalling > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there is at least one installing or preinstalling step taking place
|
|
||||||
*/
|
|
||||||
get anyPreInstallingOrInstalling(): boolean {
|
|
||||||
return this.anyInstalling || this.anyPreinstalling;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import extensionLoaderInjectable from "./extension-loader.injectable";
|
||||||
|
|
||||||
|
const getInstalledExtensionInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const store = di.inject(extensionLoaderInjectable);
|
||||||
|
|
||||||
|
return (extId: string) => store.getExtension(extId);
|
||||||
|
},
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getInstalledExtensionInjectable;
|
||||||
17
src/extensions/installation-state/logger.injectable.ts
Normal file
17
src/extensions/installation-state/logger.injectable.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import createChildLoggerInjectable from "../../common/logger/create-child-logger.injectable";
|
||||||
|
|
||||||
|
const installationStateLoggerInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const createChildLogger = di.inject(createChildLoggerInjectable);
|
||||||
|
|
||||||
|
return createChildLogger("[ExtensionInstallationStore]");
|
||||||
|
},
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default installationStateLoggerInjectable;
|
||||||
16
src/extensions/installation-state/state-channels.ts
Normal file
16
src/extensions/installation-state/state-channels.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import type { EmitterChannel } from "../../common/communication/emitter";
|
||||||
|
|
||||||
|
export type SetInstalling = EmitterChannel<[extId: string]>;
|
||||||
|
|
||||||
|
export const setInstallingChannel = "extension-installation-state-store:install";
|
||||||
|
export const setInstallingChannelInjectionToken = getInjectionToken<SetInstalling>();
|
||||||
|
|
||||||
|
export type ClearInstalling = EmitterChannel<[extId: string]>;
|
||||||
|
|
||||||
|
export const clearInstallingChannel = "extension-installation-state-store:clear-install";
|
||||||
|
export const clearInstallingChannelInjectionToken = getInjectionToken<ClearInstalling>();
|
||||||
13
src/extensions/installation-state/state.ts
Normal file
13
src/extensions/installation-state/state.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The possible installation states
|
||||||
|
*/
|
||||||
|
export enum InstallationState {
|
||||||
|
INSTALLING = "installing",
|
||||||
|
UNINSTALLING = "uninstalling",
|
||||||
|
IDLE = "idle",
|
||||||
|
}
|
||||||
@ -37,7 +37,7 @@ let ext: LensExtension = null;
|
|||||||
|
|
||||||
describe("page registry tests", () => {
|
describe("page registry tests", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
const dis = await getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
|
|
||||||
|
|||||||
@ -51,7 +51,7 @@ describe("create clusters", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs({
|
mockFs({
|
||||||
"minikube-config.yml": JSON.stringify({
|
"minikube-config.yml": JSON.stringify({
|
||||||
|
|||||||
@ -74,7 +74,7 @@ describe("ContextHandler", () => {
|
|||||||
let createContextHandler: (cluster: Cluster) => ContextHandler;
|
let createContextHandler: (cluster: Cluster) => ContextHandler;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs({
|
mockFs({
|
||||||
"tmp": {},
|
"tmp": {},
|
||||||
|
|||||||
@ -89,7 +89,7 @@ describe("kube auth proxy tests", () => {
|
|||||||
"tmp": {},
|
"tmp": {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs(mockMinikubeConfig);
|
mockFs(mockMinikubeConfig);
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@ describe("kubeconfig manager tests", () => {
|
|||||||
let createKubeconfigManager: (cluster: Cluster) => KubeconfigManager;
|
let createKubeconfigManager: (cluster: Cluster) => KubeconfigManager;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
|
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
|
||||||
|
|
||||||
|
|||||||
26
src/main/app-paths/app-paths-channel.injectable.ts
Normal file
26
src/main/app-paths/app-paths-channel.injectable.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { appPathsInjectionChannelToken, appPathsIpcChannel } from "../../common/app-paths/app-path-channel-injection-token";
|
||||||
|
import registerChannelInjectable from "../communication/register-channel.injectable";
|
||||||
|
import type { Channel } from "../../common/communication/channel";
|
||||||
|
import type { AppPaths } from "../../common/app-paths/app-paths";
|
||||||
|
import { appPathsInjectionToken } from "../../common/app-paths/app-path-injection-token";
|
||||||
|
|
||||||
|
let channel: Channel<[], AppPaths>;
|
||||||
|
|
||||||
|
const appPathsInjectable = getInjectable({
|
||||||
|
setup: (di) => {
|
||||||
|
const appPaths = di.inject(appPathsInjectionToken);
|
||||||
|
const registerChannel = di.inject(registerChannelInjectable);
|
||||||
|
|
||||||
|
channel = registerChannel(appPathsIpcChannel, () => appPaths);
|
||||||
|
},
|
||||||
|
instantiate: () => channel,
|
||||||
|
injectionToken: appPathsInjectionChannelToken,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default appPathsInjectable;
|
||||||
@ -2,67 +2,38 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import {
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
DependencyInjectionContainer,
|
|
||||||
getInjectable,
|
|
||||||
lifecycleEnum,
|
|
||||||
} from "@ogre-tools/injectable";
|
|
||||||
|
|
||||||
import {
|
|
||||||
appPathsInjectionToken,
|
|
||||||
appPathsIpcChannel,
|
|
||||||
} from "../../common/app-paths/app-path-injection-token";
|
|
||||||
|
|
||||||
import registerChannelInjectable from "./register-channel/register-channel.injectable";
|
|
||||||
import { getAppPaths } from "./get-app-paths";
|
import { getAppPaths } from "./get-app-paths";
|
||||||
import getElectronAppPathInjectable from "./get-electron-app-path/get-electron-app-path.injectable";
|
import getElectronAppPathInjectable from "./get-electron-app-path/get-electron-app-path.injectable";
|
||||||
import setElectronAppPathInjectable from "./set-electron-app-path/set-electron-app-path.injectable";
|
import setElectronAppPathInjectable from "./set-electron-app-path/set-electron-app-path.injectable";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import appNameInjectable from "./app-name/app-name.injectable";
|
import appNameInjectable from "./app-name/app-name.injectable";
|
||||||
import directoryForIntegrationTestingInjectable from "./directory-for-integration-testing/directory-for-integration-testing.injectable";
|
import directoryForIntegrationTestingInjectable from "./directory-for-integration-testing/directory-for-integration-testing.injectable";
|
||||||
|
import { appPathsInjectionToken } from "../../common/app-paths/app-path-injection-token";
|
||||||
|
|
||||||
const appPathsInjectable = getInjectable({
|
const appPathsInjectable = getInjectable({
|
||||||
setup: (di) => {
|
instantiate: (di) => {
|
||||||
const directoryForIntegrationTesting = di.inject(
|
const directoryForIntegrationTesting = di.inject(directoryForIntegrationTestingInjectable);
|
||||||
directoryForIntegrationTestingInjectable,
|
const setElectronAppPath = di.inject(setElectronAppPathInjectable);
|
||||||
);
|
|
||||||
|
|
||||||
if (directoryForIntegrationTesting) {
|
if (directoryForIntegrationTesting) {
|
||||||
setupPathForAppDataInIntegrationTesting(di, directoryForIntegrationTesting);
|
// TODO: this kludge is here only until we have a proper place to setup integration testing.
|
||||||
|
setElectronAppPath("appData", directoryForIntegrationTesting);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPathForUserData(di);
|
// Set path for user data
|
||||||
registerAppPathsChannel(di);
|
const appName = di.inject(appNameInjectable);
|
||||||
|
const getAppPath = di.inject(getElectronAppPathInjectable);
|
||||||
|
const appDataPath = getAppPath("appData");
|
||||||
|
|
||||||
|
setElectronAppPath("userData", path.join(appDataPath, appName));
|
||||||
|
|
||||||
|
return getAppPaths({
|
||||||
|
getAppPath: di.inject(getElectronAppPathInjectable),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
instantiate: (di) =>
|
|
||||||
getAppPaths({ getAppPath: di.inject(getElectronAppPathInjectable) }),
|
|
||||||
|
|
||||||
injectionToken: appPathsInjectionToken,
|
injectionToken: appPathsInjectionToken,
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default appPathsInjectable;
|
export default appPathsInjectable;
|
||||||
|
|
||||||
const registerAppPathsChannel = (di: DependencyInjectionContainer) => {
|
|
||||||
const registerChannel = di.inject(registerChannelInjectable);
|
|
||||||
|
|
||||||
registerChannel(appPathsIpcChannel, () => di.inject(appPathsInjectable));
|
|
||||||
};
|
|
||||||
|
|
||||||
const setupPathForUserData = (di: DependencyInjectionContainer) => {
|
|
||||||
const setElectronAppPath = di.inject(setElectronAppPathInjectable);
|
|
||||||
const appName = di.inject(appNameInjectable);
|
|
||||||
const getAppPath = di.inject(getElectronAppPathInjectable);
|
|
||||||
|
|
||||||
const appDataPath = getAppPath("appData");
|
|
||||||
|
|
||||||
setElectronAppPath("userData", path.join(appDataPath, appName));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Todo: this kludge is here only until we have a proper place to setup integration testing.
|
|
||||||
const setupPathForAppDataInIntegrationTesting = (di: DependencyInjectionContainer, appDataPath: string) => {
|
|
||||||
const setElectronAppPath = di.inject(setElectronAppPathInjectable);
|
|
||||||
|
|
||||||
setElectronAppPath("appData", appDataPath);
|
|
||||||
};
|
|
||||||
|
|||||||
@ -2,13 +2,13 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { fromPairs } from "lodash/fp";
|
import { pathNames, PathName, AppPaths } from "../../common/app-paths/app-paths";
|
||||||
import { pathNames, PathName } from "../../common/app-paths/app-path-names";
|
import { fromEntries } from "../../renderer/utils";
|
||||||
import type { AppPaths } from "../../common/app-paths/app-path-injection-token";
|
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
getAppPath: (name: PathName) => string
|
getAppPath: (name: PathName) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAppPaths = ({ getAppPath }: Dependencies) =>
|
export function getAppPaths({ getAppPath }: Dependencies): AppPaths {
|
||||||
fromPairs(pathNames.map((name) => [name, getAppPath(name)])) as AppPaths;
|
return fromEntries(pathNames.map((name) => [name, getAppPath(name)]));
|
||||||
|
}
|
||||||
|
|||||||
@ -6,13 +6,13 @@ import electronAppInjectable from "./electron-app/electron-app.injectable";
|
|||||||
import getElectronAppPathInjectable from "./get-electron-app-path.injectable";
|
import getElectronAppPathInjectable from "./get-electron-app-path.injectable";
|
||||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
import type { App } from "electron";
|
import type { App } from "electron";
|
||||||
import registerChannelInjectable from "../register-channel/register-channel.injectable";
|
import registerChannelInjectable from "../../communication/register-channel.injectable";
|
||||||
|
|
||||||
describe("get-electron-app-path", () => {
|
describe("get-electron-app-path", () => {
|
||||||
let getElectronAppPath: (name: string) => string | null;
|
let getElectronAppPath: (name: string) => string | null;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: false });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: false });
|
||||||
|
|
||||||
const appStub = {
|
const appStub = {
|
||||||
name: "some-app-name",
|
name: "some-app-name",
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { App } from "electron";
|
import type { App } from "electron";
|
||||||
import type { PathName } from "../../../common/app-paths/app-path-names";
|
import type { PathName } from "../../../common/app-paths/app-paths";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
app: App;
|
app: App;
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import ipcMainInjectable from "./ipc-main/ipc-main.injectable";
|
|
||||||
import { registerChannel } from "./register-channel";
|
|
||||||
|
|
||||||
const registerChannelInjectable = getInjectable({
|
|
||||||
instantiate: (di) => registerChannel({
|
|
||||||
ipcMain: di.inject(ipcMainInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default registerChannelInjectable;
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import type { IpcMain } from "electron";
|
|
||||||
import type { Channel } from "../../../common/ipc-channel/channel";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
ipcMain: IpcMain;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const registerChannel =
|
|
||||||
({ ipcMain }: Dependencies) =>
|
|
||||||
<TChannel extends Channel<TInstance>, TInstance>(
|
|
||||||
channel: TChannel,
|
|
||||||
getValue: () => TInstance,
|
|
||||||
) =>
|
|
||||||
ipcMain.handle(channel.name, getValue);
|
|
||||||
@ -3,12 +3,17 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import type { PathName } from "../../../common/app-paths/app-path-names";
|
import type { PathName } from "../../../common/app-paths/app-paths";
|
||||||
import electronAppInjectable from "../get-electron-app-path/electron-app/electron-app.injectable";
|
import electronAppInjectable from "../get-electron-app-path/electron-app/electron-app.injectable";
|
||||||
|
|
||||||
const setElectronAppPathInjectable = getInjectable({
|
const setElectronAppPathInjectable = getInjectable({
|
||||||
instantiate: (di) => (name: PathName, path: string) : void =>
|
instantiate: (di) => {
|
||||||
di.inject(electronAppInjectable).setPath(name, path),
|
const app = di.inject(electronAppInjectable);
|
||||||
|
|
||||||
|
return (name: PathName, path: string): void => {
|
||||||
|
app.setPath(name, path);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -38,7 +38,7 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
let computeDiff: ReturnType<typeof computeDiffFor>;
|
let computeDiff: ReturnType<typeof computeDiffFor>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
|
|
||||||
|
|||||||
20
src/main/communication/ipc-handle.injectable.ts
Normal file
20
src/main/communication/ipc-handle.injectable.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { IpcMainInvokeEvent } from "electron";
|
||||||
|
import ipcMainInjectable from "./ipc-main.injectable";
|
||||||
|
|
||||||
|
const ipcHandleInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const ipcMain = di.inject(ipcMainInjectable);
|
||||||
|
|
||||||
|
return (channel: string, listener: (event: IpcMainInvokeEvent, ...args: any[]) => any): void => {
|
||||||
|
ipcMain.handle(channel, listener);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ipcHandleInjectable;
|
||||||
22
src/main/communication/ipc-on.injectable.ts
Normal file
22
src/main/communication/ipc-on.injectable.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { IpcMainEvent } from "electron";
|
||||||
|
import { ipcOnEventInjectionToken } from "../../common/communication/ipc-on-event-injection-token";
|
||||||
|
import ipcMainInjectable from "./ipc-main.injectable";
|
||||||
|
|
||||||
|
const ipcOnInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const ipcMain = di.inject(ipcMainInjectable);
|
||||||
|
|
||||||
|
return (channel: string, listener: (event: IpcMainEvent, ...args: any[]) => void): void => {
|
||||||
|
ipcMain.on(channel, listener);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
injectionToken: ipcOnEventInjectionToken,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ipcOnInjectable;
|
||||||
31
src/main/communication/register-channel.injectable.ts
Normal file
31
src/main/communication/register-channel.injectable.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { IpcMainInvokeEvent } from "electron";
|
||||||
|
import type { Channel } from "../../common/communication/channel";
|
||||||
|
import ipcHandleInjectable from "./ipc-handle.injectable";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
handle: (channel: string, listener: (event: IpcMainInvokeEvent, ...args: any[]) => void) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerChannel({ handle }: Dependencies) {
|
||||||
|
return function <Parameters extends any[], Value>(name: string, getValue: (...args: Parameters) => Value): Channel<Parameters, Value> {
|
||||||
|
handle(name, async (event, ...args: Parameters) => await getValue(...args));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
throw new Error(`Invoking channel ${name} on main is invalid`);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerChannelInjectable = getInjectable({
|
||||||
|
instantiate: (di) => registerChannel({
|
||||||
|
handle: di.inject(ipcHandleInjectable),
|
||||||
|
}),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default registerChannelInjectable;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import registerEmitterChannelInjectable from "../../../common/communication/register-emitter.injectable";
|
||||||
|
import installationStateLoggerInjectable from "../../../extensions/installation-state/logger.injectable";
|
||||||
|
import { clearInstallingChannel, clearInstallingChannelInjectionToken } from "../../../extensions/installation-state/state-channels";
|
||||||
|
|
||||||
|
const clearInstallingChannelInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const registerEmitterChannel = di.inject(registerEmitterChannelInjectable);
|
||||||
|
const logger = di.inject(installationStateLoggerInjectable);
|
||||||
|
|
||||||
|
return registerEmitterChannel(clearInstallingChannel, logger);
|
||||||
|
},
|
||||||
|
injectionToken: clearInstallingChannelInjectionToken,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clearInstallingChannelInjectable;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import registerEmitterChannelInjectable from "../../../common/communication/register-emitter.injectable";
|
||||||
|
import installationStateLoggerInjectable from "../../../extensions/installation-state/logger.injectable";
|
||||||
|
import { setInstallingChannel, setInstallingChannelInjectionToken } from "../../../extensions/installation-state/state-channels";
|
||||||
|
|
||||||
|
const setInstallingChannelInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const registerEmitterChannel = di.inject(registerEmitterChannelInjectable);
|
||||||
|
const logger = di.inject(installationStateLoggerInjectable);
|
||||||
|
|
||||||
|
return registerEmitterChannel(setInstallingChannel, logger);
|
||||||
|
},
|
||||||
|
injectionToken: setInstallingChannelInjectionToken,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default setInstallingChannelInjectable;
|
||||||
@ -11,20 +11,22 @@ import { setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-global
|
|||||||
import getElectronAppPathInjectable from "./app-paths/get-electron-app-path/get-electron-app-path.injectable";
|
import getElectronAppPathInjectable from "./app-paths/get-electron-app-path/get-electron-app-path.injectable";
|
||||||
import setElectronAppPathInjectable from "./app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
import setElectronAppPathInjectable from "./app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
||||||
import appNameInjectable from "./app-paths/app-name/app-name.injectable";
|
import appNameInjectable from "./app-paths/app-name/app-name.injectable";
|
||||||
import registerChannelInjectable from "./app-paths/register-channel/register-channel.injectable";
|
import registerEventSinkInjectable from "../common/communication/register-event-sink.injectable";
|
||||||
import writeJsonFileInjectable from "../common/fs/write-json-file.injectable";
|
import registerChannelInjectable from "./communication/register-channel.injectable";
|
||||||
import readJsonFileInjectable from "../common/fs/read-json-file.injectable";
|
import { overrideFsFunctions } from "../test-utils/override-fs-functions";
|
||||||
|
|
||||||
export const getDiForUnitTesting = (
|
interface DiForTestingOptions {
|
||||||
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
doGeneralOverrides?: boolean;
|
||||||
) => {
|
doIpcOverrides?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDiForUnitTesting({ doGeneralOverrides = false, doIpcOverrides = true }: DiForTestingOptions = {}) {
|
||||||
const di = createContainer();
|
const di = createContainer();
|
||||||
|
|
||||||
setLegacyGlobalDiForExtensionApi(di);
|
setLegacyGlobalDiForExtensionApi(di);
|
||||||
|
|
||||||
for (const filePath of getInjectableFilePaths()) {
|
for (const filePath of getInjectableFilePaths()) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
const { default: injectableInstance } = await import(filePath);
|
||||||
const injectableInstance = require(filePath).default;
|
|
||||||
|
|
||||||
di.register({
|
di.register({
|
||||||
id: filePath,
|
id: filePath,
|
||||||
@ -36,26 +38,21 @@ export const getDiForUnitTesting = (
|
|||||||
di.preventSideEffects();
|
di.preventSideEffects();
|
||||||
|
|
||||||
if (doGeneralOverrides) {
|
if (doGeneralOverrides) {
|
||||||
di.override(
|
di.override(getElectronAppPathInjectable, () => (name: string) => `some-electron-app-path-for-${kebabCase(name)}`);
|
||||||
getElectronAppPathInjectable,
|
|
||||||
() => (name: string) => `some-electron-app-path-for-${kebabCase(name)}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
di.override(setElectronAppPathInjectable, () => () => undefined);
|
di.override(setElectronAppPathInjectable, () => () => undefined);
|
||||||
di.override(appNameInjectable, () => "some-electron-app-name");
|
di.override(appNameInjectable, () => "some-electron-app-name");
|
||||||
di.override(registerChannelInjectable, () => () => undefined);
|
|
||||||
|
|
||||||
di.override(writeJsonFileInjectable, () => () => {
|
overrideFsFunctions(di);
|
||||||
throw new Error("Tried to write JSON file to file system without specifying explicit override.");
|
}
|
||||||
});
|
|
||||||
|
|
||||||
di.override(readJsonFileInjectable, () => () => {
|
if (doIpcOverrides) {
|
||||||
throw new Error("Tried to read JSON file from file system without specifying explicit override.");
|
di.override(registerEventSinkInjectable, () => () => () => undefined);
|
||||||
});
|
di.override(registerChannelInjectable, () => () => () => undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
return di;
|
return di;
|
||||||
};
|
}
|
||||||
|
|
||||||
const getInjectableFilePaths = memoize(() => [
|
const getInjectableFilePaths = memoize(() => [
|
||||||
...glob.sync("./**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
...glob.sync("./**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||||
|
|||||||
19
src/main/ipc/emit-event.ts
Normal file
19
src/main/ipc/emit-event.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import ipcMainInjectable from "../communication/ipc-main.injectable";
|
||||||
|
|
||||||
|
const emitEventInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const ipcMain = di.inject(ipcMainInjectable);
|
||||||
|
|
||||||
|
return (channel: string, ...args: any[]) => {
|
||||||
|
ipcMain.emit(channel, ...args);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default emitEventInjectable;
|
||||||
@ -9,7 +9,7 @@ import request from "request";
|
|||||||
import { ensureDir, pathExists } from "fs-extra";
|
import { ensureDir, pathExists } from "fs-extra";
|
||||||
import * as tar from "tar";
|
import * as tar from "tar";
|
||||||
import { isWindows } from "../common/vars";
|
import { isWindows } from "../common/vars";
|
||||||
import type winston from "winston";
|
import type { LensLogger } from "../common/logger";
|
||||||
|
|
||||||
export type LensBinaryOpts = {
|
export type LensBinaryOpts = {
|
||||||
version: string;
|
version: string;
|
||||||
@ -32,7 +32,7 @@ export class LensBinary {
|
|||||||
protected arch: string;
|
protected arch: string;
|
||||||
protected originalBinaryName: string;
|
protected originalBinaryName: string;
|
||||||
protected requestOpts: request.Options;
|
protected requestOpts: request.Options;
|
||||||
protected logger: Console | winston.Logger;
|
protected logger: Omit<LensLogger, "silly" | "verbose">;
|
||||||
|
|
||||||
constructor(opts: LensBinaryOpts) {
|
constructor(opts: LensBinaryOpts) {
|
||||||
const baseDir = opts.baseDir;
|
const baseDir = opts.baseDir;
|
||||||
@ -68,7 +68,7 @@ export class LensBinary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public setLogger(logger: Console | winston.Logger) {
|
public setLogger(logger: LensLogger) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ describe("electron-menu-items", () => {
|
|||||||
let extensionsStub: ObservableMap<string, LensMainExtension>;
|
let extensionsStub: ObservableMap<string, LensMainExtension>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
extensionsStub = new ObservableMap();
|
extensionsStub = new ObservableMap();
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ describe("protocol router tests", () => {
|
|||||||
let extensionsStore: ExtensionsStore;
|
let extensionsStore: ExtensionsStore;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs({
|
mockFs({
|
||||||
"tmp": {},
|
"tmp": {},
|
||||||
|
|||||||
@ -17,7 +17,7 @@ describe("tray-menu-items", () => {
|
|||||||
let extensionsStub: ObservableMap<string, LensMainExtension>;
|
let extensionsStub: ObservableMap<string, LensMainExtension>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
await di.runSetups();
|
await di.runSetups();
|
||||||
|
|
||||||
|
|||||||
19
src/renderer/app-paths/app-paths-channel.injectable.ts
Normal file
19
src/renderer/app-paths/app-paths-channel.injectable.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { appPathsInjectionChannelToken, appPathsIpcChannel } from "../../common/app-paths/app-path-channel-injection-token";
|
||||||
|
import registerChannelInjectable from "../communication/register-channel.injectable";
|
||||||
|
|
||||||
|
const appPathsChannelInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const registerChannel = di.inject(registerChannelInjectable);
|
||||||
|
|
||||||
|
return registerChannel(appPathsIpcChannel);
|
||||||
|
},
|
||||||
|
injectionToken: appPathsInjectionChannelToken,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default appPathsChannelInjectable;
|
||||||
@ -3,24 +3,20 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { AppPaths, appPathsInjectionToken, appPathsIpcChannel } from "../../common/app-paths/app-path-injection-token";
|
import { appPathsInjectionChannelToken } from "../../common/app-paths/app-path-channel-injection-token";
|
||||||
import getValueFromRegisteredChannelInjectable from "./get-value-from-registered-channel/get-value-from-registered-channel.injectable";
|
import { appPathsInjectionToken } from "../../common/app-paths/app-path-injection-token";
|
||||||
|
import type { AppPaths } from "../../common/app-paths/app-paths";
|
||||||
|
|
||||||
let syncAppPaths: AppPaths;
|
let syncAppPaths: AppPaths;
|
||||||
|
|
||||||
const appPathsInjectable = getInjectable({
|
const appPathsInjectable = getInjectable({
|
||||||
setup: async (di) => {
|
setup: async (di) => {
|
||||||
const getValueFromRegisteredChannel = di.inject(
|
const appPathsChannel = di.inject(appPathsInjectionChannelToken);
|
||||||
getValueFromRegisteredChannelInjectable,
|
|
||||||
);
|
|
||||||
|
|
||||||
syncAppPaths = await getValueFromRegisteredChannel(appPathsIpcChannel);
|
syncAppPaths = await appPathsChannel();
|
||||||
},
|
},
|
||||||
|
|
||||||
instantiate: () => syncAppPaths,
|
instantiate: () => syncAppPaths,
|
||||||
|
|
||||||
injectionToken: appPathsInjectionToken,
|
injectionToken: appPathsInjectionToken,
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import ipcRendererInjectable from "./ipc-renderer/ipc-renderer.injectable";
|
|
||||||
import { getValueFromRegisteredChannel } from "./get-value-from-registered-channel";
|
|
||||||
|
|
||||||
const getValueFromRegisteredChannelInjectable = getInjectable({
|
|
||||||
instantiate: (di) =>
|
|
||||||
getValueFromRegisteredChannel({ ipcRenderer: di.inject(ipcRendererInjectable) }),
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default getValueFromRegisteredChannelInjectable;
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import type { IpcRenderer } from "electron";
|
|
||||||
import type { Channel } from "../../../common/ipc-channel/channel";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
ipcRenderer: IpcRenderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getValueFromRegisteredChannel =
|
|
||||||
({ ipcRenderer }: Dependencies) =>
|
|
||||||
<TChannel extends Channel<TInstance>, TInstance>(
|
|
||||||
channel: TChannel,
|
|
||||||
): Promise<TChannel["_template"]> =>
|
|
||||||
ipcRenderer.invoke(channel.name);
|
|
||||||
@ -30,7 +30,6 @@ import { DiContextProvider } from "@ogre-tools/injectable-react";
|
|||||||
import type { DependencyInjectionContainer } from "@ogre-tools/injectable";
|
import type { DependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||||
import extensionLoaderInjectable from "../extensions/extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../extensions/extension-loader/extension-loader.injectable";
|
||||||
import extensionDiscoveryInjectable from "../extensions/extension-discovery/extension-discovery.injectable";
|
import extensionDiscoveryInjectable from "../extensions/extension-discovery/extension-discovery.injectable";
|
||||||
import extensionInstallationStateStoreInjectable from "../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
|
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
|
||||||
import userStoreInjectable from "../common/user-store/user-store.injectable";
|
import userStoreInjectable from "../common/user-store/user-store.injectable";
|
||||||
import initRootFrameInjectable from "./frames/root-frame/init-root-frame/init-root-frame.injectable";
|
import initRootFrameInjectable from "./frames/root-frame/init-root-frame/init-root-frame.injectable";
|
||||||
@ -114,10 +113,6 @@ export async function bootstrap(di: DependencyInjectionContainer) {
|
|||||||
|
|
||||||
WeblinkStore.createInstance();
|
WeblinkStore.createInstance();
|
||||||
|
|
||||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
|
||||||
|
|
||||||
extensionInstallationStateStore.bindIpcListeners();
|
|
||||||
|
|
||||||
HelmRepoManager.createInstance(); // initialize the manager
|
HelmRepoManager.createInstance(); // initialize the manager
|
||||||
|
|
||||||
// Register additional store listeners
|
// Register additional store listeners
|
||||||
|
|||||||
17
src/renderer/communication/ipc-invoke.injectable.ts
Normal file
17
src/renderer/communication/ipc-invoke.injectable.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import ipcRendererInjectable from "./ipc-renderer.injectable";
|
||||||
|
|
||||||
|
const ipcInvokeInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||||
|
|
||||||
|
return (channel: string, ...args: any[]) => ipcRenderer.invoke(channel, ...args);
|
||||||
|
},
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ipcInvokeInjectable;
|
||||||
22
src/renderer/communication/ipc-on.injectable.ts
Normal file
22
src/renderer/communication/ipc-on.injectable.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { IpcRendererEvent } from "electron";
|
||||||
|
import { ipcOnEventInjectionToken } from "../../common/communication/ipc-on-event-injection-token";
|
||||||
|
import ipcRendererInjectable from "./ipc-renderer.injectable";
|
||||||
|
|
||||||
|
const ipcOnInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||||
|
|
||||||
|
return (channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void => {
|
||||||
|
ipcRenderer.on(channel, listener);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
injectionToken: ipcOnEventInjectionToken,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ipcOnInjectable;
|
||||||
27
src/renderer/communication/register-channel.injectable.ts
Normal file
27
src/renderer/communication/register-channel.injectable.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import ipcInvokeInjectable from "./ipc-invoke.injectable";
|
||||||
|
import type { Channel } from "../../common/communication/channel";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
invoke: (channel: string, ...args: any[]) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerChannel({ invoke }: Dependencies) {
|
||||||
|
return function <Parameters extends any[], Value>(name: string): Channel<Parameters, Value> {
|
||||||
|
return (...args: Parameters) => invoke(name, ...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerChannelInjectable = getInjectable({
|
||||||
|
instantiate: (di) => registerChannel({
|
||||||
|
invoke: di.inject(ipcInvokeInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default registerChannelInjectable;
|
||||||
@ -37,8 +37,8 @@ class TestCategory extends CatalogCategory {
|
|||||||
describe("Custom Category Columns", () => {
|
describe("Custom Category Columns", () => {
|
||||||
let di: ConfigurableDependencyInjectionContainer;
|
let di: ConfigurableDependencyInjectionContainer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
di = getDiForUnitTesting();
|
di = await getDiForUnitTesting();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("without extensions", () => {
|
describe("without extensions", () => {
|
||||||
|
|||||||
@ -15,8 +15,8 @@ import customCategoryViewsInjectable from "../custom-views.injectable";
|
|||||||
describe("Custom Category Views", () => {
|
describe("Custom Category Views", () => {
|
||||||
let di: ConfigurableDependencyInjectionContainer;
|
let di: ConfigurableDependencyInjectionContainer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
di = getDiForUnitTesting();
|
di = await getDiForUnitTesting();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should order items correctly over all extensions", () => {
|
it("should order items correctly over all extensions", () => {
|
||||||
|
|||||||
@ -101,7 +101,7 @@ describe("<Catalog />", () => {
|
|||||||
let render: DiRender;
|
let render: DiRender;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||||
|
import removeDirInjectable from "../../../../common/fs/remove-dir.injectable";
|
||||||
|
import getInstalledExtensionInjectable from "../../../../extensions/extension-loader/get-installed-extension.injectable";
|
||||||
|
import { InstallationState } from "../../../../extensions/installation-state/state";
|
||||||
|
import { getDisForUnitTesting } from "../../../../test-utils/get-dis-for-unit-testing";
|
||||||
|
import getInstallationStateInjectable from "../../../extensions/installation-state/get-installation-state.injectable";
|
||||||
|
import { noop } from "../../../utils";
|
||||||
|
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
||||||
|
import createTempFilesAndValidateInjectable from "../attempt-install/create-temp-files-and-validate/create-temp-files-and-validate.injectable";
|
||||||
|
import unpackExtensionInjectable from "../attempt-install/unpack-extension/unpack-extension.injectable";
|
||||||
|
|
||||||
|
describe("attemptInstall()", () => {
|
||||||
|
let rendererDi: ConfigurableDependencyInjectionContainer;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const dis = await getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
rendererDi = dis.rendererDi;
|
||||||
|
|
||||||
|
await dis.runSetups();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should attempt to remove any broken remnants of a previous install", async () => {
|
||||||
|
const removeDir = jest.fn();
|
||||||
|
|
||||||
|
rendererDi.override(createTempFilesAndValidateInjectable, () => ({ fileName }) => Promise.resolve({
|
||||||
|
fileName,
|
||||||
|
data: Buffer.from([]),
|
||||||
|
id: "some-extension-id",
|
||||||
|
manifest: {
|
||||||
|
name: "some-extension-name",
|
||||||
|
version: "1.0.0",
|
||||||
|
},
|
||||||
|
tempFile: "/some-fole-path",
|
||||||
|
}));
|
||||||
|
rendererDi.override(getInstallationStateInjectable, () => () => InstallationState.IDLE);
|
||||||
|
rendererDi.override(getInstalledExtensionInjectable, () => () => undefined);
|
||||||
|
rendererDi.override(unpackExtensionInjectable, () => () => Promise.resolve());
|
||||||
|
rendererDi.override(removeDirInjectable, () => removeDir);
|
||||||
|
|
||||||
|
const attemptInstall = rendererDi.inject(attemptInstallInjectable);
|
||||||
|
|
||||||
|
await attemptInstall({ fileName: "foobar", dataP: Promise.resolve(Buffer.from([])) }, noop);
|
||||||
|
expect(removeDir).toBeCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -48,7 +48,7 @@ describe("Extensions", () => {
|
|||||||
let render: DiRender;
|
let render: DiRender;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
di.override(directoryForDownloadsInjectable, () => "some-directory-for-downloads");
|
di.override(directoryForDownloadsInjectable, () => "some-directory-for-downloads");
|
||||||
|
|||||||
@ -6,15 +6,14 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|||||||
import { attemptInstallByInfo } from "./attempt-install-by-info";
|
import { attemptInstallByInfo } from "./attempt-install-by-info";
|
||||||
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
||||||
import getBaseRegistryUrlInjectable from "../get-base-registry-url/get-base-registry-url.injectable";
|
import getBaseRegistryUrlInjectable from "../get-base-registry-url/get-base-registry-url.injectable";
|
||||||
import extensionInstallationStateStoreInjectable
|
import startPreInstallInjectable from "../../../extensions/installation-state/start-pre-install.injectable";
|
||||||
from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
|
|
||||||
const attemptInstallByInfoInjectable = getInjectable({
|
const attemptInstallByInfoInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) =>
|
||||||
attemptInstallByInfo({
|
attemptInstallByInfo({
|
||||||
attemptInstall: di.inject(attemptInstallInjectable),
|
attemptInstall: di.inject(attemptInstallInjectable),
|
||||||
getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable),
|
getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable),
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
startPreInstall: di.inject(startPreInstallInjectable),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { downloadFile, downloadJson, ExtendableDisposer } from "../../../../common/utils";
|
import { Disposer, downloadFile, downloadJson } from "../../../../common/utils";
|
||||||
import { Notifications } from "../../notifications";
|
import { Notifications } from "../../notifications";
|
||||||
import { ConfirmDialog } from "../../confirm-dialog";
|
import { ConfirmDialog } from "../../confirm-dialog";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -11,7 +11,6 @@ import { SemVer } from "semver";
|
|||||||
import URLParse from "url-parse";
|
import URLParse from "url-parse";
|
||||||
import type { InstallRequest } from "../attempt-install/install-request";
|
import type { InstallRequest } from "../attempt-install/install-request";
|
||||||
import lodash from "lodash";
|
import lodash from "lodash";
|
||||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
|
|
||||||
export interface ExtensionInfo {
|
export interface ExtensionInfo {
|
||||||
name: string;
|
name: string;
|
||||||
@ -20,17 +19,19 @@ export interface ExtensionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise<void>;
|
attemptInstall: (request: InstallRequest, d: Disposer) => Promise<void>;
|
||||||
getBaseRegistryUrl: () => Promise<string>;
|
getBaseRegistryUrl: () => Promise<string>;
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore
|
startPreInstall: () => Disposer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const attemptInstallByInfo = ({ attemptInstall, getBaseRegistryUrl, extensionInstallationStateStore }: Dependencies) => async ({
|
export const attemptInstallByInfo = ({ attemptInstall, getBaseRegistryUrl, startPreInstall }: Dependencies) => async (
|
||||||
|
{
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
requireConfirmation = false,
|
requireConfirmation = false,
|
||||||
}: ExtensionInfo) => {
|
}: ExtensionInfo,
|
||||||
const disposer = extensionInstallationStateStore.startPreInstall();
|
disposer = startPreInstall(),
|
||||||
|
) => {
|
||||||
const baseUrl = await getBaseRegistryUrl();
|
const baseUrl = await getBaseRegistryUrl();
|
||||||
const registryUrl = new URLParse(baseUrl).set("pathname", name).toString();
|
const registryUrl = new URLParse(baseUrl).set("pathname", name).toString();
|
||||||
let json: any;
|
let json: any;
|
||||||
|
|||||||
@ -3,25 +3,25 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
|
||||||
import uninstallExtensionInjectable from "../uninstall-extension/uninstall-extension.injectable";
|
import uninstallExtensionInjectable from "../uninstall-extension/uninstall-extension.injectable";
|
||||||
import { attemptInstall } from "./attempt-install";
|
import { attemptInstall } from "./attempt-install";
|
||||||
import unpackExtensionInjectable from "./unpack-extension/unpack-extension.injectable";
|
import unpackExtensionInjectable from "./unpack-extension/unpack-extension.injectable";
|
||||||
import getExtensionDestFolderInjectable
|
import getExtensionDestFolderInjectable from "./get-extension-dest-folder/get-extension-dest-folder.injectable";
|
||||||
from "./get-extension-dest-folder/get-extension-dest-folder.injectable";
|
|
||||||
import createTempFilesAndValidateInjectable from "./create-temp-files-and-validate/create-temp-files-and-validate.injectable";
|
import createTempFilesAndValidateInjectable from "./create-temp-files-and-validate/create-temp-files-and-validate.injectable";
|
||||||
import extensionInstallationStateStoreInjectable
|
import removeDirInjectable from "../../../../common/fs/remove-dir.injectable";
|
||||||
from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
import getInstallationStateInjectable from "../../../extensions/installation-state/get-installation-state.injectable";
|
||||||
|
import getInstalledExtensionInjectable from "../../../../extensions/extension-loader/get-installed-extension.injectable";
|
||||||
|
|
||||||
const attemptInstallInjectable = getInjectable({
|
const attemptInstallInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) =>
|
||||||
attemptInstall({
|
attemptInstall({
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
getInstalledExtension: di.inject(getInstalledExtensionInjectable),
|
||||||
uninstallExtension: di.inject(uninstallExtensionInjectable),
|
uninstallExtension: di.inject(uninstallExtensionInjectable),
|
||||||
unpackExtension: di.inject(unpackExtensionInjectable),
|
unpackExtension: di.inject(unpackExtensionInjectable),
|
||||||
createTempFilesAndValidate: di.inject(createTempFilesAndValidateInjectable),
|
createTempFilesAndValidate: di.inject(createTempFilesAndValidateInjectable),
|
||||||
getExtensionDestFolder: di.inject(getExtensionDestFolderInjectable),
|
getExtensionDestFolder: di.inject(getExtensionDestFolderInjectable),
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
getInstallationState: di.inject(getInstallationStateInjectable),
|
||||||
|
removeDir: di.inject(removeDirInjectable),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
|||||||
@ -2,70 +2,51 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import {
|
import type { Disposer } from "../../../../common/utils";
|
||||||
Disposer,
|
|
||||||
disposer,
|
|
||||||
ExtendableDisposer,
|
|
||||||
} from "../../../../common/utils";
|
|
||||||
import { Notifications } from "../../notifications";
|
import { Notifications } from "../../notifications";
|
||||||
import { Button } from "../../button";
|
import { Button } from "../../button";
|
||||||
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
|
||||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { remove as removeDir } from "fs-extra";
|
|
||||||
import { shell } from "electron";
|
import { shell } from "electron";
|
||||||
import type { InstallRequestValidated } from "./create-temp-files-and-validate/create-temp-files-and-validate";
|
import type { InstallRequestValidated } from "./create-temp-files-and-validate/create-temp-files-and-validate";
|
||||||
import type { InstallRequest } from "./install-request";
|
import type { InstallRequest } from "./install-request";
|
||||||
import {
|
import { InstallationState } from "../../../../extensions/installation-state/state";
|
||||||
ExtensionInstallationState,
|
import type { InstalledExtension } from "../../../../extensions/extension-discovery/extension-discovery";
|
||||||
ExtensionInstallationStateStore,
|
|
||||||
} from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
extensionLoader: ExtensionLoader;
|
getInstalledExtension: (extId: string) => InstalledExtension | undefined;
|
||||||
uninstallExtension: (id: LensExtensionId) => Promise<boolean>;
|
uninstallExtension: (id: LensExtensionId) => Promise<boolean>;
|
||||||
|
unpackExtension: (request: InstallRequestValidated, disposeDownloading: Disposer) => Promise<void>;
|
||||||
unpackExtension: (
|
createTempFilesAndValidate: (installRequest: InstallRequest) => Promise<InstallRequestValidated | null>;
|
||||||
request: InstallRequestValidated,
|
getExtensionDestFolder: (name: string) => string;
|
||||||
disposeDownloading: Disposer,
|
getInstallationState: (extId: string) => InstallationState;
|
||||||
) => Promise<void>;
|
removeDir: (dir: string) => Promise<void>;
|
||||||
|
|
||||||
createTempFilesAndValidate: (
|
|
||||||
installRequest: InstallRequest,
|
|
||||||
) => Promise<InstallRequestValidated | null>;
|
|
||||||
|
|
||||||
getExtensionDestFolder: (name: string) => string
|
|
||||||
|
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const attemptInstall =
|
export const attemptInstall =
|
||||||
({
|
({
|
||||||
extensionLoader,
|
getInstalledExtension,
|
||||||
uninstallExtension,
|
uninstallExtension,
|
||||||
unpackExtension,
|
unpackExtension,
|
||||||
createTempFilesAndValidate,
|
createTempFilesAndValidate,
|
||||||
getExtensionDestFolder,
|
getExtensionDestFolder,
|
||||||
extensionInstallationStateStore,
|
getInstallationState,
|
||||||
|
removeDir,
|
||||||
}: Dependencies) =>
|
}: Dependencies) =>
|
||||||
async (request: InstallRequest, d?: ExtendableDisposer): Promise<void> => {
|
async (request: InstallRequest, dispose: Disposer): Promise<void> => {
|
||||||
const dispose = disposer(
|
|
||||||
extensionInstallationStateStore.startPreInstall(),
|
|
||||||
d,
|
|
||||||
);
|
|
||||||
|
|
||||||
const validatedRequest = await createTempFilesAndValidate(request);
|
const validatedRequest = await createTempFilesAndValidate(request);
|
||||||
|
|
||||||
if (!validatedRequest) {
|
if (!validatedRequest) {
|
||||||
return dispose();
|
return dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, version, description } = validatedRequest.manifest;
|
const {
|
||||||
const curState = extensionInstallationStateStore.getInstallationState(
|
id,
|
||||||
validatedRequest.id,
|
manifest: { name, version, description },
|
||||||
);
|
} = validatedRequest;
|
||||||
|
const curState = getInstallationState(id);
|
||||||
|
|
||||||
if (curState !== ExtensionInstallationState.IDLE) {
|
if (curState !== InstallationState.IDLE) {
|
||||||
dispose();
|
dispose();
|
||||||
|
|
||||||
return void Notifications.error(
|
return void Notifications.error(
|
||||||
@ -80,21 +61,17 @@ export const attemptInstall =
|
|||||||
}
|
}
|
||||||
|
|
||||||
const extensionFolder = getExtensionDestFolder(name);
|
const extensionFolder = getExtensionDestFolder(name);
|
||||||
const installedExtension = extensionLoader.getExtension(validatedRequest.id);
|
const installedExtension = getInstalledExtension(id);
|
||||||
|
|
||||||
if (installedExtension) {
|
if (installedExtension) {
|
||||||
const { version: oldVersion } = installedExtension.manifest;
|
const { manifest: { version: oldVersion }} = installedExtension;
|
||||||
|
|
||||||
// confirm to uninstall old version before installing new version
|
// confirm uninstall and then install new version
|
||||||
const removeNotification = Notifications.info(
|
const removeNotification = Notifications.info(
|
||||||
<div className="InstallingExtensionNotification flex gaps align-center">
|
<div className="InstallingExtensionNotification flex gaps align-center">
|
||||||
<div className="flex column gaps">
|
<div className="flex column gaps">
|
||||||
<p>
|
<p>
|
||||||
Install extension{" "}
|
Install extension<b>{name}@{version}</b>?
|
||||||
<b>
|
|
||||||
{name}@{version}
|
|
||||||
</b>
|
|
||||||
?
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Description: <em>{description}</em>
|
Description: <em>{description}</em>
|
||||||
@ -103,8 +80,7 @@ export const attemptInstall =
|
|||||||
className="remove-folder-warning"
|
className="remove-folder-warning"
|
||||||
onClick={() => shell.openPath(extensionFolder)}
|
onClick={() => shell.openPath(extensionFolder)}
|
||||||
>
|
>
|
||||||
<b>Warning:</b> {name}@{oldVersion} will be removed before
|
<b>Warning:</b> {name}@{oldVersion} will be removed before installation.
|
||||||
installation.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@ -113,7 +89,7 @@ export const attemptInstall =
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
removeNotification();
|
removeNotification();
|
||||||
|
|
||||||
if (await uninstallExtension(validatedRequest.id)) {
|
if (await uninstallExtension(id)) {
|
||||||
await unpackExtension(validatedRequest, dispose);
|
await unpackExtension(validatedRequest, dispose);
|
||||||
} else {
|
} else {
|
||||||
dispose();
|
dispose();
|
||||||
@ -126,10 +102,8 @@ export const attemptInstall =
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// clean up old data if still around
|
// Remove the old dir because it isn't a valid extension anyway
|
||||||
await removeDir(extensionFolder);
|
await removeDir(extensionFolder);
|
||||||
|
|
||||||
// install extension if not yet exists
|
|
||||||
await unpackExtension(validatedRequest, dispose);
|
await unpackExtension(validatedRequest, dispose);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,17 +5,17 @@
|
|||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { unpackExtension } from "./unpack-extension";
|
import { unpackExtension } from "./unpack-extension";
|
||||||
import extensionLoaderInjectable from "../../../../../extensions/extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../../../../../extensions/extension-loader/extension-loader.injectable";
|
||||||
import getExtensionDestFolderInjectable
|
import getExtensionDestFolderInjectable from "../get-extension-dest-folder/get-extension-dest-folder.injectable";
|
||||||
from "../get-extension-dest-folder/get-extension-dest-folder.injectable";
|
import setInstallingInjectable from "../../../../extensions/installation-state/set-installing.injectable";
|
||||||
import extensionInstallationStateStoreInjectable
|
import clearInstallingInjectable from "../../../../extensions/installation-state/clear-installing.injectable";
|
||||||
from "../../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
|
|
||||||
const unpackExtensionInjectable = getInjectable({
|
const unpackExtensionInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) =>
|
||||||
unpackExtension({
|
unpackExtension({
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||||
getExtensionDestFolder: di.inject(getExtensionDestFolderInjectable),
|
getExtensionDestFolder: di.inject(getExtensionDestFolderInjectable),
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
setInstalling: di.inject(setInstallingInjectable),
|
||||||
|
clearInstalling: di.inject(clearInstallingInjectable),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
|||||||
@ -13,20 +13,20 @@ import path from "path";
|
|||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import { when } from "mobx";
|
import { when } from "mobx";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { ExtensionInstallationStateStore } from "../../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
extensionLoader: ExtensionLoader
|
extensionLoader: ExtensionLoader;
|
||||||
getExtensionDestFolder: (name: string) => string
|
getExtensionDestFolder: (name: string) => string;
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore
|
setInstalling: (extId: string) => void;
|
||||||
|
clearInstalling: (extId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unpackExtension =
|
export const unpackExtension = ({
|
||||||
({
|
|
||||||
extensionLoader,
|
extensionLoader,
|
||||||
getExtensionDestFolder,
|
getExtensionDestFolder,
|
||||||
extensionInstallationStateStore,
|
setInstalling,
|
||||||
}: Dependencies) =>
|
clearInstalling,
|
||||||
|
}: Dependencies) => (
|
||||||
async (request: InstallRequestValidated, disposeDownloading?: Disposer) => {
|
async (request: InstallRequestValidated, disposeDownloading?: Disposer) => {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
@ -35,7 +35,7 @@ export const unpackExtension =
|
|||||||
manifest: { name, version },
|
manifest: { name, version },
|
||||||
} = request;
|
} = request;
|
||||||
|
|
||||||
extensionInstallationStateStore.setInstalling(id);
|
setInstalling(id);
|
||||||
disposeDownloading?.();
|
disposeDownloading?.();
|
||||||
|
|
||||||
const displayName = extensionDisplayName(name, version);
|
const displayName = extensionDisplayName(name, version);
|
||||||
@ -92,10 +92,11 @@ export const unpackExtension =
|
|||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
// Remove install state once finished
|
// Remove install state once finished
|
||||||
extensionInstallationStateStore.clearInstalling(id);
|
clearInstalling(id);
|
||||||
|
|
||||||
// clean up
|
// clean up
|
||||||
fse.remove(unpackingTempFolder).catch(noop);
|
fse.remove(unpackingTempFolder).catch(noop);
|
||||||
fse.unlink(tempFile).catch(noop);
|
fse.unlink(tempFile).catch(noop);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|||||||
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
|
||||||
|
import { readFileNotify } from "./read-file-notify/read-file-notify";
|
||||||
|
import path from "path";
|
||||||
|
import type { InstallRequest } from "./attempt-install/install-request";
|
||||||
|
import type { Disposer } from "../../utils";
|
||||||
|
import startPreInstallInjectable from "../../extensions/installation-state/start-pre-install.injectable";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
attemptInstall: (request: InstallRequest, disposer: Disposer) => Promise<void>;
|
||||||
|
startPreInstall: () => Disposer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const attemptInstalls = ({ attemptInstall, startPreInstall }: Dependencies) => (
|
||||||
|
async (filePaths: string[]): Promise<void> => {
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
const disposer = startPreInstall();
|
||||||
|
|
||||||
|
for (const filePath of filePaths) {
|
||||||
|
promises.push(
|
||||||
|
attemptInstall({
|
||||||
|
fileName: path.basename(filePath),
|
||||||
|
dataP: readFileNotify(filePath),
|
||||||
|
}, disposer),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.allSettled(promises);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const attemptInstallsInjectable = getInjectable({
|
||||||
|
instantiate: (di) => attemptInstalls({
|
||||||
|
attemptInstall: di.inject(attemptInstallInjectable),
|
||||||
|
startPreInstall: di.inject(startPreInstallInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default attemptInstallsInjectable;
|
||||||
@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import { attemptInstalls } from "./attempt-installs";
|
|
||||||
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
|
||||||
|
|
||||||
const attemptInstallsInjectable = getInjectable({
|
|
||||||
instantiate: (di) =>
|
|
||||||
attemptInstalls({
|
|
||||||
attemptInstall: di.inject(attemptInstallInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default attemptInstallsInjectable;
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { readFileNotify } from "../read-file-notify/read-file-notify";
|
|
||||||
import path from "path";
|
|
||||||
import type { InstallRequest } from "../attempt-install/install-request";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
attemptInstall: (request: InstallRequest) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const attemptInstalls =
|
|
||||||
({ attemptInstall }: Dependencies) =>
|
|
||||||
async (filePaths: string[]): Promise<void> => {
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
|
|
||||||
for (const filePath of filePaths) {
|
|
||||||
promises.push(
|
|
||||||
attemptInstall({
|
|
||||||
fileName: path.basename(filePath),
|
|
||||||
dataP: readFileNotify(filePath),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.allSettled(promises);
|
|
||||||
};
|
|
||||||
@ -4,13 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "./extensions.scss";
|
import "./extensions.scss";
|
||||||
import {
|
import { IComputedValue, makeObservable, observable, reaction, when } from "mobx";
|
||||||
IComputedValue,
|
|
||||||
makeObservable,
|
|
||||||
observable,
|
|
||||||
reaction,
|
|
||||||
when,
|
|
||||||
} from "mobx";
|
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { InstalledExtension } from "../../../extensions/extension-discovery/extension-discovery";
|
import type { InstalledExtension } from "../../../extensions/extension-discovery/extension-discovery";
|
||||||
@ -26,30 +20,25 @@ import userExtensionsInjectable from "./user-extensions/user-extensions.injectab
|
|||||||
import enableExtensionInjectable from "./enable-extension/enable-extension.injectable";
|
import enableExtensionInjectable from "./enable-extension/enable-extension.injectable";
|
||||||
import disableExtensionInjectable from "./disable-extension/disable-extension.injectable";
|
import disableExtensionInjectable from "./disable-extension/disable-extension.injectable";
|
||||||
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension/confirm-uninstall-extension.injectable";
|
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension/confirm-uninstall-extension.injectable";
|
||||||
import installFromInputInjectable from "./install-from-input/install-from-input.injectable";
|
|
||||||
import installFromSelectFileDialogInjectable from "./install-from-select-file-dialog.injectable";
|
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
import installOnDropInjectable from "./install-on-drop/install-on-drop.injectable";
|
import installOnDropInjectable from "./install-on-drop.injectable";
|
||||||
import { supportedExtensionFormats } from "./supported-extension-formats";
|
import { supportedExtensionFormats } from "./supported-extension-formats";
|
||||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
import anyExtensionsInstallingInjectable from "../../extensions/installation-state/any-installing.injectable";
|
||||||
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
userExtensions: IComputedValue<InstalledExtension[]>;
|
readonly userExtensions: IComputedValue<InstalledExtension[]>;
|
||||||
enableExtension: (id: LensExtensionId) => void;
|
enableExtension: (id: LensExtensionId) => void;
|
||||||
disableExtension: (id: LensExtensionId) => void;
|
disableExtension: (id: LensExtensionId) => void;
|
||||||
confirmUninstallExtension: (extension: InstalledExtension) => Promise<void>;
|
confirmUninstallExtension: (extension: InstalledExtension) => Promise<void>;
|
||||||
installFromInput: (input: string) => Promise<void>;
|
|
||||||
installFromSelectFileDialog: () => Promise<void>;
|
|
||||||
installOnDrop: (files: File[]) => Promise<void>;
|
installOnDrop: (files: File[]) => Promise<void>;
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore
|
readonly anyExtensionsInstalling: IComputedValue<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class NonInjectedExtensions extends React.Component<Dependencies> {
|
class NonInjectedExtensions extends React.Component<Dependencies> {
|
||||||
@observable installPath = "";
|
@observable installPath = "";
|
||||||
|
|
||||||
constructor(props: Dependencies) {
|
constructor(readonly props: Dependencies) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
@ -59,7 +48,7 @@ class NonInjectedExtensions extends React.Component<Dependencies> {
|
|||||||
reaction(() => this.props.userExtensions.get().length, (curSize, prevSize) => {
|
reaction(() => this.props.userExtensions.get().length, (curSize, prevSize) => {
|
||||||
if (curSize > prevSize) {
|
if (curSize > prevSize) {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
when(() => !this.props.extensionInstallationStateStore.anyInstalling, () => this.installPath = ""),
|
when(() => !this.props.anyExtensionsInstalling.get(), () => this.installPath = ""),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -86,8 +75,6 @@ class NonInjectedExtensions extends React.Component<Dependencies> {
|
|||||||
<Install
|
<Install
|
||||||
supportedFormats={supportedExtensionFormats}
|
supportedFormats={supportedExtensionFormats}
|
||||||
onChange={value => (this.installPath = value)}
|
onChange={value => (this.installPath = value)}
|
||||||
installFromInput={() => this.props.installFromInput(this.installPath)}
|
|
||||||
installFromSelectFileDialog={this.props.installFromSelectFileDialog}
|
|
||||||
installPath={this.installPath}
|
installPath={this.installPath}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -112,9 +99,7 @@ export const Extensions = withInjectables<Dependencies>(NonInjectedExtensions, {
|
|||||||
enableExtension: di.inject(enableExtensionInjectable),
|
enableExtension: di.inject(enableExtensionInjectable),
|
||||||
disableExtension: di.inject(disableExtensionInjectable),
|
disableExtension: di.inject(disableExtensionInjectable),
|
||||||
confirmUninstallExtension: di.inject(confirmUninstallExtensionInjectable),
|
confirmUninstallExtension: di.inject(confirmUninstallExtensionInjectable),
|
||||||
installFromInput: di.inject(installFromInputInjectable),
|
|
||||||
installOnDrop: di.inject(installOnDropInjectable),
|
installOnDrop: di.inject(installOnDropInjectable),
|
||||||
installFromSelectFileDialog: di.inject(installFromSelectFileDialogInjectable),
|
anyExtensionsInstalling: di.inject(anyExtensionsInstallingInjectable),
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
|
||||||
|
import attemptInstallByInfoInjectable from "./attempt-install-by-info/attempt-install-by-info.injectable";
|
||||||
|
import startPreInstallInjectable from "../../extensions/installation-state/start-pre-install.injectable";
|
||||||
|
import { Disposer, downloadFile } from "../../../common/utils";
|
||||||
|
import { InputValidators } from "../input";
|
||||||
|
import { getMessageFromError } from "./get-message-from-error/get-message-from-error";
|
||||||
|
import logger from "../../../main/logger";
|
||||||
|
import { Notifications } from "../notifications";
|
||||||
|
import path from "path";
|
||||||
|
import { readFileNotify } from "./read-file-notify/read-file-notify";
|
||||||
|
import type { InstallRequest } from "./attempt-install/install-request";
|
||||||
|
import type { ExtensionInfo } from "./attempt-install-by-info/attempt-install-by-info";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
attemptInstall: (request: InstallRequest, disposer: Disposer) => Promise<void>,
|
||||||
|
attemptInstallByInfo: (extensionInfo: ExtensionInfo, disposer: Disposer) => Promise<void>,
|
||||||
|
startPreInstall: () => Disposer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const installFromInput = ({ attemptInstall, attemptInstallByInfo, startPreInstall }: Dependencies) => (
|
||||||
|
async (input: string) => {
|
||||||
|
const disposer = startPreInstall();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// fixme: improve error messages for non-tar-file URLs
|
||||||
|
if (InputValidators.isUrl.validate(input)) {
|
||||||
|
// install via url
|
||||||
|
const { promise } = downloadFile({ url: input, timeout: 10 * 60 * 1000 });
|
||||||
|
const fileName = path.basename(input);
|
||||||
|
|
||||||
|
await attemptInstall({ fileName, dataP: promise }, disposer);
|
||||||
|
} else if (InputValidators.isPath.validate(input)) {
|
||||||
|
// install from system path
|
||||||
|
const fileName = path.basename(input);
|
||||||
|
|
||||||
|
await attemptInstall({ fileName, dataP: readFileNotify(input) }, disposer);
|
||||||
|
} else if (InputValidators.isExtensionNameInstall.validate(input)) {
|
||||||
|
const [{ groups: { name, version }}] = [...input.matchAll(InputValidators.isExtensionNameInstallRegex)];
|
||||||
|
|
||||||
|
await attemptInstallByInfo({ name, version }, disposer);
|
||||||
|
} else {
|
||||||
|
throw new Error("unknown input format");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const message = getMessageFromError(error);
|
||||||
|
|
||||||
|
logger.info(`[EXTENSION-INSTALL]: installation has failed: ${message}`, { error, installPath: input });
|
||||||
|
Notifications.error(<p>Installation has failed: <b>{message}</b></p>);
|
||||||
|
} finally {
|
||||||
|
disposer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const installFromInputInjectable = getInjectable({
|
||||||
|
instantiate: (di) =>
|
||||||
|
installFromInput({
|
||||||
|
attemptInstall: di.inject(attemptInstallInjectable),
|
||||||
|
attemptInstallByInfo: di.inject(attemptInstallByInfoInjectable),
|
||||||
|
startPreInstall: di.inject(startPreInstallInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default installFromInputInjectable;
|
||||||
@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
|
||||||
import { installFromInput } from "./install-from-input";
|
|
||||||
import attemptInstallByInfoInjectable from "../attempt-install-by-info/attempt-install-by-info.injectable";
|
|
||||||
import extensionInstallationStateStoreInjectable
|
|
||||||
from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
|
|
||||||
const installFromInputInjectable = getInjectable({
|
|
||||||
instantiate: (di) =>
|
|
||||||
installFromInput({
|
|
||||||
attemptInstall: di.inject(attemptInstallInjectable),
|
|
||||||
attemptInstallByInfo: di.inject(attemptInstallByInfoInjectable),
|
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default installFromInputInjectable;
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { downloadFile, ExtendableDisposer } from "../../../../common/utils";
|
|
||||||
import { InputValidators } from "../../input";
|
|
||||||
import { getMessageFromError } from "../get-message-from-error/get-message-from-error";
|
|
||||||
import logger from "../../../../main/logger";
|
|
||||||
import { Notifications } from "../../notifications";
|
|
||||||
import path from "path";
|
|
||||||
import React from "react";
|
|
||||||
import { readFileNotify } from "../read-file-notify/read-file-notify";
|
|
||||||
import type { InstallRequest } from "../attempt-install/install-request";
|
|
||||||
import type { ExtensionInfo } from "../attempt-install-by-info/attempt-install-by-info";
|
|
||||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
attemptInstall: (request: InstallRequest, disposer?: ExtendableDisposer) => Promise<void>,
|
|
||||||
attemptInstallByInfo: (extensionInfo: ExtensionInfo) => Promise<void>,
|
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore
|
|
||||||
}
|
|
||||||
|
|
||||||
export const installFromInput = ({ attemptInstall, attemptInstallByInfo, extensionInstallationStateStore }: Dependencies) => async (input: string) => {
|
|
||||||
let disposer: ExtendableDisposer | undefined = undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// fixme: improve error messages for non-tar-file URLs
|
|
||||||
if (InputValidators.isUrl.validate(input)) {
|
|
||||||
// install via url
|
|
||||||
disposer = extensionInstallationStateStore.startPreInstall();
|
|
||||||
const { promise } = downloadFile({ url: input, timeout: 10 * 60 * 1000 });
|
|
||||||
const fileName = path.basename(input);
|
|
||||||
|
|
||||||
await attemptInstall({ fileName, dataP: promise }, disposer);
|
|
||||||
} else if (InputValidators.isPath.validate(input)) {
|
|
||||||
// install from system path
|
|
||||||
const fileName = path.basename(input);
|
|
||||||
|
|
||||||
await attemptInstall({ fileName, dataP: readFileNotify(input) });
|
|
||||||
} else if (InputValidators.isExtensionNameInstall.validate(input)) {
|
|
||||||
const [{ groups: { name, version }}] = [...input.matchAll(InputValidators.isExtensionNameInstallRegex)];
|
|
||||||
|
|
||||||
await attemptInstallByInfo({ name, version });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const message = getMessageFromError(error);
|
|
||||||
|
|
||||||
logger.info(`[EXTENSION-INSTALL]: installation has failed: ${message}`, { error, installPath: input });
|
|
||||||
Notifications.error(<p>Installation has failed: <b>{message}</b></p>);
|
|
||||||
} finally {
|
|
||||||
disposer?.();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -5,15 +5,17 @@
|
|||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { requestOpenFilePickingDialog } from "../../ipc";
|
import { requestOpenFilePickingDialog } from "../../ipc";
|
||||||
import { supportedExtensionFormats } from "./supported-extension-formats";
|
import { supportedExtensionFormats } from "./supported-extension-formats";
|
||||||
import attemptInstallsInjectable from "./attempt-installs/attempt-installs.injectable";
|
import attemptInstallsInjectable from "./attempt-installs.injectable";
|
||||||
import directoryForDownloadsInjectable from "../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
|
import directoryForDownloadsInjectable from "../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
attemptInstalls: (filePaths: string[]) => Promise<void>
|
attemptInstalls: (filePaths: string[]) => Promise<void>;
|
||||||
directoryForDownloads: string
|
directoryForDownloads: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const installFromSelectFileDialog = ({ attemptInstalls, directoryForDownloads }: Dependencies) => async () => {
|
const installFromSelectFileDialog = ({ attemptInstalls, directoryForDownloads }: Dependencies) => (
|
||||||
|
async () => {
|
||||||
const { canceled, filePaths } = await requestOpenFilePickingDialog({
|
const { canceled, filePaths } = await requestOpenFilePickingDialog({
|
||||||
defaultPath: directoryForDownloads,
|
defaultPath: directoryForDownloads,
|
||||||
properties: ["openFile", "multiSelections"],
|
properties: ["openFile", "multiSelections"],
|
||||||
@ -25,7 +27,8 @@ const installFromSelectFileDialog = ({ attemptInstalls, directoryForDownloads }:
|
|||||||
if (!canceled) {
|
if (!canceled) {
|
||||||
await attemptInstalls(filePaths);
|
await attemptInstalls(filePaths);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const installFromSelectFileDialogInjectable = getInjectable({
|
const installFromSelectFileDialogInjectable = getInjectable({
|
||||||
instantiate: (di) => installFromSelectFileDialog({
|
instantiate: (di) => installFromSelectFileDialog({
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import attemptInstallsInjectable from "./attempt-installs.injectable";
|
||||||
|
import logger from "../../../main/logger";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
attemptInstalls: (filePaths: string[]) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const installOnDrop = ({ attemptInstalls }: Dependencies) => (
|
||||||
|
async (files: File[]) => {
|
||||||
|
logger.info("Install from D&D");
|
||||||
|
await attemptInstalls(files.map(({ path }) => path));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const installOnDropInjectable = getInjectable({
|
||||||
|
instantiate: (di) => installOnDrop({
|
||||||
|
attemptInstalls: di.inject(attemptInstallsInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default installOnDropInjectable;
|
||||||
@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import { installOnDrop } from "./install-on-drop";
|
|
||||||
import attemptInstallsInjectable from "../attempt-installs/attempt-installs.injectable";
|
|
||||||
|
|
||||||
const installOnDropInjectable = getInjectable({
|
|
||||||
instantiate: (di) =>
|
|
||||||
installOnDrop({
|
|
||||||
attemptInstalls: di.inject(attemptInstallsInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default installOnDropInjectable;
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import logger from "../../../../main/logger";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
attemptInstalls: (filePaths: string[]) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const installOnDrop =
|
|
||||||
({ attemptInstalls }: Dependencies) =>
|
|
||||||
async (files: File[]) => {
|
|
||||||
logger.info("Install from D&D");
|
|
||||||
await attemptInstalls(files.map(({ path }) => path));
|
|
||||||
};
|
|
||||||
@ -12,21 +12,22 @@ import { observer } from "mobx-react";
|
|||||||
import { Input, InputValidator, InputValidators } from "../input";
|
import { Input, InputValidator, InputValidators } from "../input";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import { TooltipPosition } from "../tooltip";
|
import { TooltipPosition } from "../tooltip";
|
||||||
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
import extensionInstallationStateStoreInjectable
|
|
||||||
from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import installFromInputInjectable from "./install-from-input.injectable";
|
||||||
|
import installFromSelectFileDialogInjectable from "./install-from-select-file-dialog.injectable";
|
||||||
|
import isCurrentlyIdleInjectable from "../../extensions/installation-state/is-currently-idle.injectable";
|
||||||
|
|
||||||
interface Props {
|
export interface InstallProps {
|
||||||
installPath: string;
|
installPath: string;
|
||||||
supportedFormats: string[];
|
supportedFormats: string[];
|
||||||
onChange: (path: string) => void;
|
onChange: (path: string) => void;
|
||||||
installFromInput: () => void;
|
|
||||||
installFromSelectFileDialog: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
isCurrentlyIdle: IComputedValue<boolean>;
|
||||||
|
installFromInput: (input: string) => Promise<void>;
|
||||||
|
installFromSelectFileDialog: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const installInputValidators = [
|
const installInputValidators = [
|
||||||
@ -42,28 +43,26 @@ const installInputValidator: InputValidator = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const NonInjectedInstall: React.FC<Dependencies & Props> = ({
|
const NonInjectedInstall = observer(({
|
||||||
installPath,
|
installPath,
|
||||||
supportedFormats,
|
supportedFormats,
|
||||||
onChange,
|
onChange,
|
||||||
installFromInput,
|
installFromInput,
|
||||||
installFromSelectFileDialog,
|
installFromSelectFileDialog,
|
||||||
extensionInstallationStateStore,
|
isCurrentlyIdle,
|
||||||
}) => (
|
}: Dependencies & InstallProps) => {
|
||||||
|
const showAsWaiting = isCurrentlyIdle.get();
|
||||||
|
const formats = supportedFormats.join(", ");
|
||||||
|
|
||||||
|
return (
|
||||||
<section className="mt-2">
|
<section className="mt-2">
|
||||||
<SubTitle
|
<SubTitle title={`Name or file path or URL to an extension package (${formats})`} />
|
||||||
title={`Name or file path or URL to an extension package (${supportedFormats.join(
|
|
||||||
", ",
|
|
||||||
)})`}
|
|
||||||
/>
|
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Input
|
<Input
|
||||||
className="box grow mr-6"
|
className="box grow mr-6"
|
||||||
theme="round-black"
|
theme="round-black"
|
||||||
disabled={
|
disabled={showAsWaiting}
|
||||||
extensionInstallationStateStore.anyPreInstallingOrInstalling
|
|
||||||
}
|
|
||||||
placeholder={"Name or file path or URL"}
|
placeholder={"Name or file path or URL"}
|
||||||
showErrorsAsTooltip={{ preferredPositions: TooltipPosition.BOTTOM }}
|
showErrorsAsTooltip={{ preferredPositions: TooltipPosition.BOTTOM }}
|
||||||
validators={installPath ? installInputValidator : undefined}
|
validators={installPath ? installInputValidator : undefined}
|
||||||
@ -85,11 +84,9 @@ const NonInjectedInstall: React.FC<Dependencies & Props> = ({
|
|||||||
primary
|
primary
|
||||||
label="Install"
|
label="Install"
|
||||||
className="w-80 h-full"
|
className="w-80 h-full"
|
||||||
disabled={
|
disabled={showAsWaiting}
|
||||||
extensionInstallationStateStore.anyPreInstallingOrInstalling
|
waiting={showAsWaiting}
|
||||||
}
|
onClick={() => installFromInput(installPath)}
|
||||||
waiting={extensionInstallationStateStore.anyPreInstallingOrInstalling}
|
|
||||||
onClick={installFromInput}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -97,17 +94,14 @@ const NonInjectedInstall: React.FC<Dependencies & Props> = ({
|
|||||||
<b>Pro-Tip</b>: you can drag-n-drop tarball-file to this area
|
<b>Pro-Tip</b>: you can drag-n-drop tarball-file to this area
|
||||||
</small>
|
</small>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export const Install = withInjectables<Dependencies, Props>(
|
export const Install = withInjectables<Dependencies, InstallProps>(NonInjectedInstall, {
|
||||||
observer(NonInjectedInstall),
|
|
||||||
{
|
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
extensionInstallationStateStore: di.inject(
|
isCurrentlyIdle: di.inject(isCurrentlyIdleInjectable),
|
||||||
extensionInstallationStateStoreInjectable,
|
installFromInput: di.inject(installFromInputInjectable),
|
||||||
),
|
installFromSelectFileDialog: di.inject(installFromSelectFileDialogInjectable),
|
||||||
|
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|||||||
@ -5,10 +5,7 @@
|
|||||||
|
|
||||||
import styles from "./installed-extensions.module.scss";
|
import styles from "./installed-extensions.module.scss";
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import type {
|
import type { ExtensionDiscovery, InstalledExtension } from "../../../extensions/extension-discovery/extension-discovery";
|
||||||
ExtensionDiscovery,
|
|
||||||
InstalledExtension,
|
|
||||||
} from "../../../extensions/extension-discovery/extension-discovery";
|
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { List } from "../list/list";
|
import { List } from "../list/list";
|
||||||
import { MenuActions, MenuItem } from "../menu";
|
import { MenuActions, MenuItem } from "../menu";
|
||||||
@ -17,15 +14,13 @@ import { cssNames } from "../../utils";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { Row } from "react-table";
|
import type { Row } from "react-table";
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
import extensionDiscoveryInjectable
|
import extensionDiscoveryInjectable from "../../../extensions/extension-discovery/extension-discovery.injectable";
|
||||||
from "../../../extensions/extension-discovery/extension-discovery.injectable";
|
|
||||||
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import extensionInstallationStateStoreInjectable
|
import type { IComputedValue } from "mobx";
|
||||||
from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
import isUninstallingInjectable from "../../extensions/installation-state/is-uninstalling.injectable";
|
||||||
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
import anyExtensionsUninstallingInjectable from "../../extensions/installation-state/any-uninstalling.injectable";
|
||||||
|
|
||||||
interface Props {
|
export interface InstalledExtensionsProps {
|
||||||
extensions: InstalledExtension[];
|
extensions: InstalledExtension[];
|
||||||
enable: (id: LensExtensionId) => void;
|
enable: (id: LensExtensionId) => void;
|
||||||
disable: (id: LensExtensionId) => void;
|
disable: (id: LensExtensionId) => void;
|
||||||
@ -34,7 +29,8 @@ interface Props {
|
|||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
extensionDiscovery: ExtensionDiscovery;
|
extensionDiscovery: ExtensionDiscovery;
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
isUninstalling: (extId: string) => boolean;
|
||||||
|
anyUninstalling: IComputedValue<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatus(extension: InstalledExtension) {
|
function getStatus(extension: InstalledExtension) {
|
||||||
@ -45,7 +41,7 @@ function getStatus(extension: InstalledExtension) {
|
|||||||
return extension.isEnabled ? "Enabled" : "Disabled";
|
return extension.isEnabled ? "Enabled" : "Disabled";
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedInstalledExtensions : React.FC<Dependencies & Props> = (({ extensionDiscovery, extensionInstallationStateStore, extensions, uninstall, enable, disable }) => {
|
const NonInjectedInstalledExtensions = observer(({ extensionDiscovery, isUninstalling, anyUninstalling, extensions, uninstall, enable, disable }: Dependencies & InstalledExtensionsProps) => {
|
||||||
const filters = [
|
const filters = [
|
||||||
(extension: InstalledExtension) => extension.manifest.name,
|
(extension: InstalledExtension) => extension.manifest.name,
|
||||||
(extension: InstalledExtension) => getStatus(extension),
|
(extension: InstalledExtension) => getStatus(extension),
|
||||||
@ -86,12 +82,10 @@ const NonInjectedInstalledExtensions : React.FC<Dependencies & Props> = (({ exte
|
|||||||
], [],
|
], [],
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = useMemo(
|
const data = useMemo(() => extensions.map(extension => {
|
||||||
() => {
|
|
||||||
return extensions.map(extension => {
|
|
||||||
const { id, isEnabled, isCompatible, manifest } = extension;
|
const { id, isEnabled, isCompatible, manifest } = extension;
|
||||||
const { name, description, version } = manifest;
|
const { name, description, version } = manifest;
|
||||||
const isUninstalling = extensionInstallationStateStore.isExtensionUninstalling(id);
|
const uninstalling = isUninstalling(id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
extension: (
|
extension: (
|
||||||
@ -110,41 +104,39 @@ const NonInjectedInstalledExtensions : React.FC<Dependencies & Props> = (({ exte
|
|||||||
),
|
),
|
||||||
actions: (
|
actions: (
|
||||||
<MenuActions usePortal toolbar={false}>
|
<MenuActions usePortal toolbar={false}>
|
||||||
{ isCompatible && (
|
{isCompatible && (
|
||||||
<>
|
<>
|
||||||
{isEnabled ? (
|
{isEnabled ? (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={isUninstalling}
|
disabled={uninstalling}
|
||||||
onClick={() => disable(id)}
|
onClick={() => disable(id)}
|
||||||
>
|
>
|
||||||
<Icon material="unpublished"/>
|
<Icon material="unpublished" />
|
||||||
<span className="title" aria-disabled={isUninstalling}>Disable</span>
|
<span className="title" aria-disabled={uninstalling}>Disable</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
) : (
|
) : (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={isUninstalling}
|
disabled={uninstalling}
|
||||||
onClick={() => enable(id)}
|
onClick={() => enable(id)}
|
||||||
>
|
>
|
||||||
<Icon material="check_circle"/>
|
<Icon material="check_circle" />
|
||||||
<span className="title" aria-disabled={isUninstalling}>Enable</span>
|
<span className="title" aria-disabled={uninstalling}>Enable</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={isUninstalling}
|
disabled={uninstalling}
|
||||||
onClick={() => uninstall(extension)}
|
onClick={() => uninstall(extension)}
|
||||||
>
|
>
|
||||||
<Icon material="delete"/>
|
<Icon material="delete" />
|
||||||
<span className="title" aria-disabled={isUninstalling}>Uninstall</span>
|
<span className="title" aria-disabled={uninstalling}>Uninstall</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuActions>
|
</MenuActions>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
});
|
}), [extensions, anyUninstalling.get()]);
|
||||||
}, [extensions, extensionInstallationStateStore.anyUninstalling],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!extensionDiscovery.isLoaded) {
|
if (!extensionDiscovery.isLoaded) {
|
||||||
return <div><Spinner center /></div>;
|
return <div><Spinner center /></div>;
|
||||||
@ -175,15 +167,11 @@ const NonInjectedInstalledExtensions : React.FC<Dependencies & Props> = (({ exte
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const InstalledExtensions = withInjectables<Dependencies, Props>(
|
export const InstalledExtensions = withInjectables<Dependencies, InstalledExtensionsProps>(NonInjectedInstalledExtensions, {
|
||||||
observer(NonInjectedInstalledExtensions),
|
|
||||||
|
|
||||||
{
|
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
extensionDiscovery: di.inject(extensionDiscoveryInjectable),
|
extensionDiscovery: di.inject(extensionDiscoveryInjectable),
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
isUninstalling: di.inject(isUninstallingInjectable),
|
||||||
|
anyUninstalling: di.inject(anyExtensionsUninstallingInjectable),
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|||||||
@ -5,17 +5,17 @@
|
|||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
||||||
import { uninstallExtension } from "./uninstall-extension";
|
import { uninstallExtension } from "./uninstall-extension";
|
||||||
import extensionInstallationStateStoreInjectable
|
import extensionDiscoveryInjectable from "../../../../extensions/extension-discovery/extension-discovery.injectable";
|
||||||
from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
import clearUninstallingInjectable from "../../../extensions/installation-state/clear-uninstalling.injectable";
|
||||||
import extensionDiscoveryInjectable
|
import setUninstallingInjectable from "../../../extensions/installation-state/set-uninstalling.injectable";
|
||||||
from "../../../../extensions/extension-discovery/extension-discovery.injectable";
|
|
||||||
|
|
||||||
const uninstallExtensionInjectable = getInjectable({
|
const uninstallExtensionInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) =>
|
||||||
uninstallExtension({
|
uninstallExtension({
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||||
extensionDiscovery: di.inject(extensionDiscoveryInjectable),
|
extensionDiscovery: di.inject(extensionDiscoveryInjectable),
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
setUninstalling: di.inject(setUninstallingInjectable),
|
||||||
|
clearUninstalling: di.inject(clearUninstallingInjectable),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
|||||||
@ -10,23 +10,22 @@ import { Notifications } from "../../notifications";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { when } from "mobx";
|
import { when } from "mobx";
|
||||||
import { getMessageFromError } from "../get-message-from-error/get-message-from-error";
|
import { getMessageFromError } from "../get-message-from-error/get-message-from-error";
|
||||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
extensionLoader: ExtensionLoader
|
extensionLoader: ExtensionLoader;
|
||||||
extensionDiscovery: ExtensionDiscovery
|
extensionDiscovery: ExtensionDiscovery;
|
||||||
extensionInstallationStateStore: ExtensionInstallationStateStore
|
setUninstalling: (extId: string) => void;
|
||||||
|
clearUninstalling: (extId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const uninstallExtension =
|
export const uninstallExtension = ({ extensionLoader, extensionDiscovery, setUninstalling, clearUninstalling }: Dependencies) => (
|
||||||
({ extensionLoader, extensionDiscovery, extensionInstallationStateStore }: Dependencies) =>
|
|
||||||
async (extensionId: LensExtensionId): Promise<boolean> => {
|
async (extensionId: LensExtensionId): Promise<boolean> => {
|
||||||
const { manifest } = extensionLoader.getExtension(extensionId);
|
const { manifest } = extensionLoader.getExtension(extensionId);
|
||||||
const displayName = extensionDisplayName(manifest.name, manifest.version);
|
const displayName = extensionDisplayName(manifest.name, manifest.version);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.debug(`[EXTENSIONS]: trying to uninstall ${extensionId}`);
|
logger.debug(`[EXTENSIONS]: trying to uninstall ${extensionId}`);
|
||||||
extensionInstallationStateStore.setUninstalling(extensionId);
|
setUninstalling(extensionId);
|
||||||
|
|
||||||
await extensionDiscovery.uninstallExtension(extensionId);
|
await extensionDiscovery.uninstallExtension(extensionId);
|
||||||
|
|
||||||
@ -57,6 +56,7 @@ export const uninstallExtension =
|
|||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
// Remove uninstall state on uninstall failure
|
// Remove uninstall state on uninstall failure
|
||||||
extensionInstallationStateStore.clearUninstalling(extensionId);
|
clearUninstalling(extensionId);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|||||||
@ -17,7 +17,7 @@ describe("ClusterRoleBindingDialog tests", () => {
|
|||||||
let render: DiRender;
|
let render: DiRender;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
await di.runSetups();
|
await di.runSetups();
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ describe("RoleBindingDialog tests", () => {
|
|||||||
let render: DiRender;
|
let render: DiRender;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,7 @@ describe("<Welcome/>", () => {
|
|||||||
let welcomeBannersStub: WelcomeBannerRegistration[];
|
let welcomeBannersStub: WelcomeBannerRegistration[];
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
await di.runSetups();
|
await di.runSetups();
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ describe("<PodTolerations />", () => {
|
|||||||
let render: DiRender;
|
let render: DiRender;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.override(
|
di.override(
|
||||||
directoryForLensLocalStorageInjectable,
|
directoryForLensLocalStorageInjectable,
|
||||||
|
|||||||
@ -89,7 +89,7 @@ describe("<DeleteClusterDialog />", () => {
|
|||||||
let createCluster: (model: ClusterModel) => Cluster;
|
let createCluster: (model: ClusterModel) => Cluster;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const { mainDi, runSetups } = getDisForUnitTesting({ doGeneralOverrides: true });
|
const { mainDi, runSetups } = await getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
|
|
||||||
|
|||||||
@ -59,7 +59,7 @@ describe("<DockTabs />", () => {
|
|||||||
let render: DiRender;
|
let render: DiRender;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = await getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
|
||||||
render = renderFor(di);
|
render = renderFor(di);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user