diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a71fc42771..7ebb23ec34 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04, macos-11, windows-2019] + os: [ubuntu-20.04, macos-11, windows-2019] node-version: [16.x] steps: - name: Checkout Release from lens diff --git a/package.json b/package.json index 395f1b04ec..b32fcc50c2 100644 --- a/package.json +++ b/package.json @@ -227,6 +227,7 @@ "@tanstack/react-table": "^8.5.11", "@tanstack/react-virtual": "3.0.0-beta.18", "@types/circular-dependency-plugin": "5.0.5", + "abort-controller": "^3.0.0", "auto-bind": "^4.0.0", "await-lock": "^2.2.2", "byline": "^5.0.0", @@ -300,10 +301,10 @@ "@material-ui/lab": "^4.0.0-alpha.60", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@sentry/types": "^6.19.7", - "@swc/core": "^1.2.223", + "@swc/core": "^1.2.242", "@swc/jest": "^0.2.22", "@testing-library/dom": "^7.31.2", - "@testing-library/jest-dom": "^5.16.4", + "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.1.5", "@testing-library/user-event": "^13.5.0", "@types/byline": "^4.2.33", @@ -325,12 +326,12 @@ "@types/jest": "^28.1.6", "@types/js-yaml": "^4.0.5", "@types/jsdom": "^16.2.14", - "@types/lodash": "^4.14.181", + "@types/lodash": "^4.14.184", "@types/marked": "^4.0.3", "@types/md5-file": "^4.0.2", "@types/mini-css-extract-plugin": "^2.4.0", "@types/mock-fs": "^4.13.1", - "@types/node": "^16.11.47", + "@types/node": "^16.11.55", "@types/node-fetch": "^2.6.2", "@types/npm": "^2.0.32", "@types/proper-lockfile": "^4.1.2", @@ -357,9 +358,9 @@ "@types/uuid": "^8.3.4", "@types/webpack": "^5.28.0", "@types/webpack-dev-server": "^4.7.2", - "@types/webpack-env": "^1.17.0", + "@types/webpack-env": "^1.18.0", "@types/webpack-node-externals": "^2.5.3", - "@typescript-eslint/eslint-plugin": "^5.32.0", + "@typescript-eslint/eslint-plugin": "^5.34.0", "@typescript-eslint/parser": "^5.31.0", "adr": "^1.4.1", "ansi_up": "^5.1.0", @@ -372,10 +373,10 @@ "css-loader": "^6.7.1", "deepdash": "^5.3.9", "dompurify": "^2.3.10", - "electron": "^19.0.4", + "electron": "^19.0.13", "electron-builder": "^23.3.3", "electron-notarize": "^0.3.0", - "esbuild": "^0.14.53", + "esbuild": "^0.15.5", "esbuild-loader": "^2.19.0", "eslint": "^8.21.0", "eslint-plugin-header": "^3.1.1", @@ -412,7 +413,7 @@ "react-select": "^5.4.0", "react-select-event": "^5.5.1", "react-window": "^1.8.7", - "sass": "^1.54.2", + "sass": "^1.54.5", "sass-loader": "^12.6.0", "sharp": "^0.30.7", "style-loader": "^3.3.1", @@ -428,7 +429,7 @@ "typescript-plugin-css-modules": "^3.4.0", "webpack": "^5.74.0", "webpack-cli": "^4.9.2", - "webpack-dev-server": "^4.9.3", + "webpack-dev-server": "^4.10.0", "webpack-node-externals": "^3.0.0", "xterm": "^4.19.0", "xterm-addon-fit": "^0.5.0" diff --git a/src/common/cluster/cluster.ts b/src/common/cluster/cluster.ts index 11b67bd002..f1a943732c 100644 --- a/src/common/cluster/cluster.ts +++ b/src/common/cluster/cluster.ts @@ -4,7 +4,6 @@ */ import { action, comparer, computed, makeObservable, observable, reaction, when } from "mobx"; -import { broadcastMessage } from "../ipc"; import type { ClusterContextHandler } from "../../main/context-handler/context-handler"; import type { KubeConfig } from "@kubernetes/client-node"; import { HttpError } from "@kubernetes/client-node"; @@ -25,6 +24,7 @@ import type { CanI } from "./authorization-review.injectable"; import type { ListNamespaces } from "./list-namespaces.injectable"; import assert from "assert"; import type { Logger } from "../logger"; +import type { BroadcastMessage } from "../ipc/broadcast-message.injectable"; export interface ClusterDependencies { readonly directoryForKubeConfigs: string; @@ -36,6 +36,7 @@ export interface ClusterDependencies { createAuthorizationReview: (config: KubeConfig) => CanI; createListNamespaces: (config: KubeConfig) => ListNamespaces; createVersionDetector: (cluster: Cluster) => VersionDetector; + broadcastMessage: BroadcastMessage; } /** @@ -602,7 +603,7 @@ export class Cluster implements ClusterModel, ClusterState { */ pushState(state = this.getState()) { this.dependencies.logger.silly(`[CLUSTER]: push-state`, state); - broadcastMessage("cluster:state", this.id, state); + this.dependencies.broadcastMessage("cluster:state", this.id, state); } // get cluster system meta, e.g. use in "logger" @@ -625,7 +626,7 @@ export class Cluster implements ClusterModel, ClusterState { const update: KubeAuthUpdate = { message, isError }; this.dependencies.logger.debug(`[CLUSTER]: broadcasting connection update`, { ...update, meta: this.getMeta() }); - broadcastMessage(`cluster:${this.id}:connection-update`, update); + this.dependencies.broadcastMessage(`cluster:${this.id}:connection-update`, update); } protected async getAllowedNamespaces(proxyConfig: KubeConfig) { @@ -645,7 +646,7 @@ export class Cluster implements ClusterModel, ClusterState { const { response } = error as HttpError & { response: Response }; this.dependencies.logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id, error: response.body }); - broadcastMessage(clusterListNamespaceForbiddenChannel, this.id); + this.dependencies.broadcastMessage(clusterListNamespaceForbiddenChannel, this.id); } return namespaceList; diff --git a/src/common/fs/stat/stat.global-override-for-injectable.ts b/src/common/fs/stat/stat.global-override-for-injectable.ts new file mode 100644 index 0000000000..2afeda7b77 --- /dev/null +++ b/src/common/fs/stat/stat.global-override-for-injectable.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import statInjectable from "./stat.injectable"; +import { getGlobalOverride } from "../../test-utils/get-global-override"; + +export default getGlobalOverride(statInjectable, () => () => { + throw new Error("Tried to call stat without explicit override"); +}); diff --git a/src/common/fs/stat/stat.injectable.ts b/src/common/fs/stat/stat.injectable.ts new file mode 100644 index 0000000000..aa1ce44447 --- /dev/null +++ b/src/common/fs/stat/stat.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import fsInjectable from "../fs.injectable"; + +const statInjectable = getInjectable({ + id: "stat", + + instantiate: (di) => di.inject(fsInjectable).stat, +}); + +export default statInjectable; diff --git a/src/common/fs/validate-directory.injectable.ts b/src/common/fs/validate-directory.injectable.ts new file mode 100644 index 0000000000..9d68ede8d5 --- /dev/null +++ b/src/common/fs/validate-directory.injectable.ts @@ -0,0 +1,76 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { AsyncResult } from "../utils/async-result"; +import { isErrnoException } from "../utils"; +import type { Stats } from "fs-extra"; +import { lowerFirst } from "lodash/fp"; +import statInjectable from "./stat/stat.injectable"; + +export type ValidateDirectory = (path: string) => Promise>; + +function getUserReadableFileType(stats: Stats): string { + if (stats.isFile()) { + return "a file"; + } + + if (stats.isFIFO()) { + return "a pipe"; + } + + if (stats.isSocket()) { + return "a socket"; + } + + if (stats.isBlockDevice()) { + return "a block device"; + } + + if (stats.isCharacterDevice()) { + return "a character device"; + } + + return "an unknown file type"; +} + +const validateDirectoryInjectable = getInjectable({ + id: "validate-directory", + + instantiate: (di): ValidateDirectory => { + const stat = di.inject(statInjectable); + + return async (path) => { + try { + const stats = await stat(path); + + if (stats.isDirectory()) { + return { callWasSuccessful: true, response: undefined }; + } + + return { callWasSuccessful: false, error: `the provided path is ${getUserReadableFileType(stats)} and not a directory.` }; + } catch (error) { + if (!isErrnoException(error)) { + return { callWasSuccessful: false, error: "of an unknown error, please try again." }; + } + + const humanReadableErrors: Record = { + ENOENT: "the provided path does not exist.", + EACCES: "search permissions is denied for one of the directories in the prefix of the provided path.", + ELOOP: "the provided path is a sym-link which points to a chain of sym-links that is too long to resolve. Perhaps it is cyclic.", + ENAMETOOLONG: "the pathname is too long to be used.", + ENOTDIR: "a prefix of the provided path is not a directory.", + }; + + const humanReadableError = error.code + ? humanReadableErrors[error.code] + : lowerFirst(String(error)); + + return { callWasSuccessful: false, error: humanReadableError }; + } + }; + }, +}); + +export default validateDirectoryInjectable; diff --git a/src/common/fs/watch/watch.global-override-for-injectable.ts b/src/common/fs/watch/watch.global-override-for-injectable.ts new file mode 100644 index 0000000000..689c7150cf --- /dev/null +++ b/src/common/fs/watch/watch.global-override-for-injectable.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getGlobalOverride } from "../../test-utils/get-global-override"; +import watchInjectable from "./watch.injectable"; + +export default getGlobalOverride(watchInjectable, () => () => { + throw new Error("Tried to call file system watch without explicit override"); +}); diff --git a/src/common/fs/watch/watch.injectable.ts b/src/common/fs/watch/watch.injectable.ts new file mode 100644 index 0000000000..44d34f20f5 --- /dev/null +++ b/src/common/fs/watch/watch.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { FSWatcher, WatchOptions } from "chokidar"; +import { watch } from "chokidar"; + +export type Watch = (path: string, options?: WatchOptions) => FSWatcher; + +// TODO: Introduce wrapper to allow simpler API +const watchInjectable = getInjectable({ + id: "watch", + instantiate: (): Watch => watch, + causesSideEffects: true, +}); + +export default watchInjectable; diff --git a/src/common/ipc/broadcast-message.injectable.ts b/src/common/ipc/broadcast-message.injectable.ts index 9df36ac27a..41db749d65 100644 --- a/src/common/ipc/broadcast-message.injectable.ts +++ b/src/common/ipc/broadcast-message.injectable.ts @@ -5,9 +5,11 @@ import { getInjectable } from "@ogre-tools/injectable"; import { broadcastMessage } from "./ipc"; +export type BroadcastMessage = (channel: string, ...args: any[]) => Promise; + const broadcastMessageInjectable = getInjectable({ id: "broadcast-message", - instantiate: () => broadcastMessage, + instantiate: (): BroadcastMessage => broadcastMessage, causesSideEffects: true, }); diff --git a/src/common/ipc/ipc.ts b/src/common/ipc/ipc.ts index bddeb0ff3c..a714af73a4 100644 --- a/src/common/ipc/ipc.ts +++ b/src/common/ipc/ipc.ts @@ -13,10 +13,17 @@ import logger from "../../main/logger"; import type { ClusterFrameInfo } from "../cluster-frames"; import { clusterFrameMap } from "../cluster-frames"; import type { Disposer } from "../utils"; +import ipcMainInjectable from "../../main/utils/channel/ipc-main/ipc-main.injectable"; +import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable"; export const broadcastMainChannel = "ipc:broadcast-main"; export function ipcMainHandle(channel: string, listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any) { + const di = getLegacyGlobalDiForExtensionApi(); + + const ipcMain = di.inject(ipcMainInjectable); + ipcMain.handle(channel, async (event, ...args) => { return sanitizePayload(await listener(event, ...args)); }); @@ -78,12 +85,20 @@ export async function broadcastMessage(channel: string, ...args: any[]): Promise } export function ipcMainOn(channel: string, listener: (event: Electron.IpcMainEvent, ...args: any[]) => any): Disposer { + const di = getLegacyGlobalDiForExtensionApi(); + + const ipcMain = di.inject(ipcMainInjectable); + ipcMain.on(channel, listener); return () => ipcMain.off(channel, listener); } export function ipcRendererOn(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): Disposer { + const di = getLegacyGlobalDiForExtensionApi(); + + const ipcRenderer = di.inject(ipcRendererInjectable); + ipcRenderer.on(channel, listener); return () => ipcRenderer.off(channel, listener); diff --git a/src/common/k8s-api/__tests__/kube-api.test.ts b/src/common/k8s-api/__tests__/kube-api.test.ts index 0b0efd86da..05da1f89d5 100644 --- a/src/common/k8s-api/__tests__/kube-api.test.ts +++ b/src/common/k8s-api/__tests__/kube-api.test.ts @@ -14,6 +14,7 @@ import { DeploymentApi, Ingress, IngressApi, Pod, PodApi } from "../endpoints"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; import apiManagerInjectable from "../api-manager/manager.injectable"; import autoRegistrationInjectable from "../api-manager/auto-registration.injectable"; +import AbortController from "abort-controller"; jest.mock("../api-manager"); diff --git a/src/common/k8s-api/endpoints/pod.api.ts b/src/common/k8s-api/endpoints/pod.api.ts index 98ea47b270..4ac49489c9 100644 --- a/src/common/k8s-api/endpoints/pod.api.ts +++ b/src/common/k8s-api/endpoints/pod.api.ts @@ -840,6 +840,10 @@ export class Pod extends KubeObject< return this.spec?.priorityClassName || ""; } + getServiceAccountName() { + return this.spec?.serviceAccountName || ""; + } + getStatus(): PodStatusPhase { const phase = this.getStatusPhase(); const reason = this.getReason(); diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index 31fad33d72..ddc2456668 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -27,9 +27,7 @@ import type { PartialDeep } from "type-fest"; import logger from "../logger"; import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import autoRegistrationEmitterInjectable from "./api-manager/auto-registration-emitter.injectable"; - -// TODO: upgrade node-fetch once we are starting to use ES modules -type LegacyAbortSignal = NonNullable; +import type AbortController from "abort-controller"; /** * The options used for creating a `KubeApi` @@ -719,7 +717,7 @@ export class KubeApi< const requestParams = timeout ? { query: { timeoutSeconds: timeout }} : {}; const watchUrl = this.getWatchUrl(namespace); const responsePromise = this.request.getResponse(watchUrl, requestParams, { - signal: abortController.signal as LegacyAbortSignal, + signal: abortController.signal, timeout: 600_000, }); diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 852e615332..5412bbc87c 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -20,9 +20,7 @@ import logger from "../logger"; import assert from "assert"; import type { PartialDeep } from "type-fest"; import { entries } from "../utils/objects"; - -// TODO: upgrade node-fetch once we are starting to use ES modules -type LegacyAbortSignal = NonNullable; +import AbortController from "abort-controller"; export type OnLoadFailure = (error: unknown) => void; @@ -480,7 +478,7 @@ export abstract class KubeObjectStore< }); // TODO: upgrade node-fetch once we are starting to use ES modules - const signal = abortController.signal as LegacyAbortSignal; + const signal = abortController.signal; const callback: KubeApiWatchCallback = (data, error) => { if (!this.isLoaded || error?.type === "aborted") return; diff --git a/src/common/protocol-handler/router.ts b/src/common/protocol-handler/router.ts index 804f70c80b..1c0ae909eb 100644 --- a/src/common/protocol-handler/router.ts +++ b/src/common/protocol-handler/router.ts @@ -8,7 +8,6 @@ import { matchPath } from "react-router"; import { countBy } from "lodash"; import { isDefined, iter } from "../utils"; import { pathToRegexp } from "path-to-regexp"; -import logger from "../../main/logger"; import type Url from "url-parse"; import { RoutingError, RoutingErrorType } from "./error"; import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store"; @@ -17,6 +16,7 @@ import type { LensExtension } from "../../extensions/lens-extension"; import type { RouteHandler, RouteParams } from "../../extensions/registries/protocol-handler"; import { when } from "mobx"; import { ipcRenderer } from "electron"; +import type { Logger } from "../logger"; // IPC channel for protocol actions. Main broadcasts the open-url events to this channel. export const ProtocolHandlerIpcPrefix = "protocol-handler"; @@ -66,6 +66,7 @@ export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: R export interface LensProtocolRouterDependencies { readonly extensionLoader: ExtensionLoader; readonly extensionsStore: ExtensionsStore; + readonly logger: Logger; } export abstract class LensProtocolRouter { @@ -130,7 +131,7 @@ export abstract class LensProtocolRouter { data.extensionName = extensionName; } - logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data); + this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data); return RouteAttempt.MISSING; } @@ -183,7 +184,7 @@ export abstract class LensProtocolRouter { timeout: 5_000, }); } catch (error) { - logger.info( + this.dependencies.logger.info( `${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not installed (${error})`, ); @@ -193,18 +194,18 @@ export abstract class LensProtocolRouter { const extension = extensionLoader.getInstanceByName(name); if (!extension) { - logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but does not have a class for ${ipcRenderer ? "renderer" : "main"}`); + this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but does not have a class for ${ipcRenderer ? "renderer" : "main"}`); return name; } if (!this.dependencies.extensionsStore.isEnabled(extension)) { - logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`); + this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`); return name; } - logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched`); + this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched`); return extension; } @@ -250,7 +251,7 @@ export abstract class LensProtocolRouter { */ public addInternalHandler(urlSchema: string, handler: RouteHandler): this { pathToRegexp(urlSchema); // verify now that the schema is valid - logger.info(`${LensProtocolRouter.LoggingPrefix}: internal registering ${urlSchema}`); + this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: internal registering ${urlSchema}`); this.internalRoutes.set(urlSchema, handler); return this; diff --git a/src/common/utils/abort-controller.ts b/src/common/utils/abort-controller.ts index 5382281cf9..8172e6a81a 100644 --- a/src/common/utils/abort-controller.ts +++ b/src/common/utils/abort-controller.ts @@ -3,6 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import AbortController from "abort-controller"; + /** * This is like an `AbortController` but will also abort if the parent aborts, * but won't make the parent abort if this aborts (single direction) diff --git a/src/common/utils/delay.ts b/src/common/utils/delay.ts index d86395026b..96171f6535 100644 --- a/src/common/utils/delay.ts +++ b/src/common/utils/delay.ts @@ -3,6 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type AbortController from "abort-controller"; + /** * Return a promise that will be resolved after at least `timeout` ms have * passed. If `failFast` is provided then the promise is also resolved if it has diff --git a/src/common/utils/reject-promise.ts b/src/common/utils/reject-promise.ts index 0211586783..8212bacd3f 100644 --- a/src/common/utils/reject-promise.ts +++ b/src/common/utils/reject-promise.ts @@ -3,6 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { AbortSignal } from "abort-controller"; + /** * Creates a new promise that will be rejected when the signal rejects. * diff --git a/src/extensions/__tests__/extension-loader.test.ts b/src/extensions/__tests__/extension-loader.test.ts index 765762c9bc..fa14f5856f 100644 --- a/src/extensions/__tests__/extension-loader.test.ts +++ b/src/extensions/__tests__/extension-loader.test.ts @@ -10,8 +10,10 @@ import extensionLoaderInjectable from "../extension-loader/extension-loader.inje import { runInAction } from "mobx"; import updateExtensionsStateInjectable from "../extension-loader/update-extensions-state/update-extensions-state.injectable"; import mockFs from "mock-fs"; -import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import { delay } from "../../renderer/utils"; +import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting"; +import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable"; +import type { IpcRenderer } from "electron"; console = new Console(stdout, stderr); @@ -19,10 +21,14 @@ const manifestPath = "manifest/path"; const manifestPath2 = "manifest/path2"; const manifestPath3 = "manifest/path3"; -jest.mock( - "electron", - () => ({ - ipcRenderer: { +describe("ExtensionLoader", () => { + let extensionLoader: ExtensionLoader; + let updateExtensionStateMock: jest.Mock; + + beforeEach(() => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + di.override(ipcRendererInjectable, () => ({ invoke: jest.fn(async (channel: string) => { if (channel === "extension-loader:main:state") { return [ @@ -59,59 +65,46 @@ jest.mock( return []; }), - on: jest.fn( - (channel: string, listener: (event: any, ...args: any[]) => void) => { - if (channel === "extension-loader:main:state") { - // First initialize with extensions 1 and 2 - // and then broadcast event to remove extension 2 and add extension number 3 - setTimeout(() => { - listener({}, [ - [ + + on: (channel: string, listener: (event: any, ...args: any[]) => void) => { + if (channel === "extension-loader:main:state") { + // First initialize with extensions 1 and 2 + // and then broadcast event to remove extension 2 and add extension number 3 + setTimeout(() => { + listener({}, [ + [ + manifestPath, + { + manifest: { + name: "TestExtension", + version: "1.0.0", + }, + id: manifestPath, + absolutePath: "/test/1", manifestPath, - { - manifest: { - name: "TestExtension", - version: "1.0.0", - }, - id: manifestPath, - absolutePath: "/test/1", - manifestPath, - isBundled: false, - isEnabled: true, + isBundled: false, + isEnabled: true, + }, + ], + [ + manifestPath3, + { + manifest: { + name: "TestExtension3", + version: "3.0.0", }, - ], - [ - manifestPath3, - { - manifest: { - name: "TestExtension3", - version: "3.0.0", - }, - id: manifestPath3, - absolutePath: "/test/3", - manifestPath: manifestPath3, - isBundled: false, - isEnabled: true, - }, - ], - ]); - }, 10); - } - }, - ), - }, - }), - { - virtual: true, - }, -); - -describe("ExtensionLoader", () => { - let extensionLoader: ExtensionLoader; - let updateExtensionStateMock: jest.Mock; - - beforeEach(() => { - const di = getDiForUnitTesting({ doGeneralOverrides: true }); + id: manifestPath3, + absolutePath: "/test/3", + manifestPath: manifestPath3, + isBundled: false, + isEnabled: true, + }, + ], + ]); + }, 10); + } + }, + }) as unknown as IpcRenderer); mockFs(); diff --git a/src/extensions/extension-discovery/extension-discovery.injectable.ts b/src/extensions/extension-discovery/extension-discovery.injectable.ts index f7ffda8a80..e6ebaa769c 100644 --- a/src/extensions/extension-discovery/extension-discovery.injectable.ts +++ b/src/extensions/extension-discovery/extension-discovery.injectable.ts @@ -13,6 +13,10 @@ import installExtensionInjectable from "../extension-installer/install-extension 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 staticFilesDirectoryInjectable from "../../common/vars/static-files-directory.injectable"; +import readJsonFileInjectable from "../../common/fs/read-json-file.injectable"; +import loggerInjectable from "../../common/logger.injectable"; +import pathExistsInjectable from "../../common/fs/path-exists.injectable"; +import watchInjectable from "../../common/fs/watch/watch.injectable"; const extensionDiscoveryInjectable = getInjectable({ id: "extension-discovery", @@ -40,6 +44,10 @@ const extensionDiscoveryInjectable = getInjectable({ ), staticFilesDirectory: di.inject(staticFilesDirectoryInjectable), + readJsonFile: di.inject(readJsonFileInjectable), + pathExists: di.inject(pathExistsInjectable), + watch: di.inject(watchInjectable), + logger: di.inject(loggerInjectable), }), }); diff --git a/src/extensions/extension-discovery/extension-discovery.test.ts b/src/extensions/extension-discovery/extension-discovery.test.ts index cfbad4e11c..7feca8f99d 100644 --- a/src/extensions/extension-discovery/extension-discovery.test.ts +++ b/src/extensions/extension-discovery/extension-discovery.test.ts @@ -4,53 +4,29 @@ */ import type { FSWatcher } from "chokidar"; -import { watch } from "chokidar"; import path from "path"; import os from "os"; import { Console } from "console"; -import * as fse from "fs-extra"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import extensionDiscoveryInjectable from "../extension-discovery/extension-discovery.injectable"; import type { ExtensionDiscovery } from "../extension-discovery/extension-discovery"; -import installExtensionInjectable - from "../extension-installer/install-extension/install-extension.injectable"; -import directoryForUserDataInjectable - from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable"; +import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import mockFs from "mock-fs"; import { delay } from "../../renderer/utils"; import { observable, when } from "mobx"; import appVersionInjectable from "../../common/vars/app-version.injectable"; - -jest.setTimeout(60_000); - -jest.mock("../../common/ipc"); -jest.mock("chokidar", () => ({ - watch: jest.fn(), -})); - -jest.mock("fs-extra"); -jest.mock("electron", () => ({ - app: { - getVersion: () => "99.99.99", - getName: () => "lens", - setName: jest.fn(), - setPath: jest.fn(), - getPath: () => "tmp", - getLocale: () => "en", - setLoginItemSettings: jest.fn(), - }, - ipcMain: { - on: jest.fn(), - handle: jest.fn(), - }, -})); +import readJsonFileInjectable from "../../common/fs/read-json-file.injectable"; +import pathExistsInjectable from "../../common/fs/path-exists.injectable"; +import watchInjectable from "../../common/fs/watch/watch.injectable"; console = new Console(process.stdout, process.stderr); // fix mockFS -const mockedWatch = watch as jest.MockedFunction; -const mockedFse = fse as jest.Mocked; describe("ExtensionDiscovery", () => { let extensionDiscovery: ExtensionDiscovery; + let readJsonFileMock: jest.Mock; + let pathExistsMock: jest.Mock; + let watchMock: jest.Mock; beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); @@ -59,6 +35,15 @@ describe("ExtensionDiscovery", () => { di.override(installExtensionInjectable, () => () => Promise.resolve()); di.override(appVersionInjectable, () => "5.0.0"); + readJsonFileMock = jest.fn(); + di.override(readJsonFileInjectable, () => readJsonFileMock); + + pathExistsMock = jest.fn(() => Promise.resolve(true)); + di.override(pathExistsInjectable, () => pathExistsMock); + + watchMock = jest.fn(); + di.override(watchInjectable, () => watchMock); + mockFs(); extensionDiscovery = di.inject(extensionDiscoveryInjectable); @@ -72,7 +57,7 @@ describe("ExtensionDiscovery", () => { const letTestFinish = observable.box(false); let addHandler!: (filePath: string) => void; - mockedFse.readJson.mockImplementation((p) => { + readJsonFileMock.mockImplementation((p) => { expect(p).toBe(path.join(os.homedir(), ".k8slens/extensions/my-extension/package.json")); return { @@ -84,8 +69,6 @@ describe("ExtensionDiscovery", () => { }; }); - mockedFse.pathExists.mockImplementation(() => true); - const mockWatchInstance = { on: jest.fn((event: string, handler: typeof addHandler) => { if (event === "add") { @@ -96,7 +79,7 @@ describe("ExtensionDiscovery", () => { }), } as unknown as FSWatcher; - mockedWatch.mockImplementationOnce(() => mockWatchInstance); + watchMock.mockImplementationOnce(() => mockWatchInstance); // Need to force isLoaded to be true so that the file watching is started extensionDiscovery.isLoaded = true; @@ -139,7 +122,7 @@ describe("ExtensionDiscovery", () => { }), } as unknown as FSWatcher; - mockedWatch.mockImplementationOnce(() => mockWatchInstance); + watchMock.mockImplementationOnce(() => mockWatchInstance); // Need to force isLoaded to be true so that the file watching is started extensionDiscovery.isLoaded = true; diff --git a/src/extensions/extension-discovery/extension-discovery.ts b/src/extensions/extension-discovery/extension-discovery.ts index 3576e66362..1de6425c19 100644 --- a/src/extensions/extension-discovery/extension-discovery.ts +++ b/src/extensions/extension-discovery/extension-discovery.ts @@ -3,7 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { watch } from "chokidar"; import { ipcRenderer } from "electron"; import { EventEmitter } from "events"; import fse from "fs-extra"; @@ -12,7 +11,6 @@ import os from "os"; import path from "path"; import { broadcastMessage, ipcMainHandle, ipcRendererOn } from "../../common/ipc"; import { isErrnoException, toJS } from "../../common/utils"; -import logger from "../../main/logger"; import type { ExtensionsStore } from "../extensions-store/extensions-store"; import type { ExtensionLoader } from "../extension-loader"; import type { LensExtensionId, LensExtensionManifest } from "../lens-extension"; @@ -21,6 +19,10 @@ import type { ExtensionInstallationStateStore } from "../extension-installation- import type { PackageJson } from "type-fest"; import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling"; import { requestInitialExtensionDiscovery } from "../../renderer/ipc"; +import type { ReadJson } from "../../common/fs/read-json-file.injectable"; +import type { Logger } from "../../common/logger"; +import type { PathExists } from "../../common/fs/path-exists.injectable"; +import type { Watch } from "../../common/fs/watch/watch.injectable"; interface Dependencies { extensionLoader: ExtensionLoader; @@ -35,6 +37,10 @@ interface Dependencies { installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise; extensionPackageRootDirectory: string; staticFilesDirectory: string; + readJsonFile: ReadJson; + pathExists: PathExists; + watch: Watch; + logger: Logger; } export interface InstalledExtension { @@ -155,13 +161,12 @@ export class ExtensionDiscovery { * Dependencies are installed automatically after an extension folder is copied. */ async watchExtensions(): Promise { - logger.info(`${logModule} watching extension add/remove in ${this.localFolderPath}`); + this.dependencies.logger.info(`${logModule} watching extension add/remove in ${this.localFolderPath}`); // Wait until .load() has been called and has been resolved await this.whenLoaded; - // chokidar works better than fs.watch - watch(this.localFolderPath, { + this.dependencies.watch(this.localFolderPath, { // For adding and removing symlinks to work, the depth has to be 1. depth: 1, ignoreInitial: true, @@ -206,11 +211,11 @@ export class ExtensionDiscovery { await this.dependencies.installExtension(extension.absolutePath); this.extensions.set(extension.id, extension); - logger.info(`${logModule} Added extension ${extension.manifest.name}`); + this.dependencies.logger.info(`${logModule} Added extension ${extension.manifest.name}`); this.events.emit("add", extension); } } catch (error) { - logger.error(`${logModule}: failed to add extension: ${error}`, { error }); + this.dependencies.logger.error(`${logModule}: failed to add extension: ${error}`, { error }); } finally { this.dependencies.extensionInstallationStateStore.clearInstallingFromMain(manifestPath); } @@ -247,13 +252,13 @@ export class ExtensionDiscovery { const lensExtensionId = extension.manifestPath; this.extensions.delete(extension.id); - logger.info(`${logModule} removed extension ${extensionName}`); + this.dependencies.logger.info(`${logModule} removed extension ${extensionName}`); this.events.emit("remove", lensExtensionId); return; } - logger.warn(`${logModule} extension ${extensionFolderName} not found, can't remove`); + this.dependencies.logger.warn(`${logModule} extension ${extensionFolderName} not found, can't remove`); }; /** @@ -275,12 +280,12 @@ export class ExtensionDiscovery { const extension = this.extensions.get(extensionId) ?? this.dependencies.extensionLoader.getExtension(extensionId); if (!extension) { - return void logger.warn(`${logModule} could not uninstall extension, not found`, { id: extensionId }); + return void this.dependencies.logger.warn(`${logModule} could not uninstall extension, not found`, { id: extensionId }); } const { manifest, absolutePath } = extension; - logger.info(`${logModule} Uninstalling ${manifest.name}`); + this.dependencies.logger.info(`${logModule} Uninstalling ${manifest.name}`); await this.removeSymlinkByPackageName(manifest.name); @@ -296,7 +301,7 @@ export class ExtensionDiscovery { this.loadStarted = true; - logger.info( + this.dependencies.logger.info( `${logModule} loading extensions from ${this.dependencies.extensionPackageRootDirectory}`, ); @@ -358,12 +363,12 @@ export class ExtensionDiscovery { */ protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise { try { - const manifest = await fse.readJson(manifestPath) as LensExtensionManifest; + const manifest = await this.dependencies.readJsonFile(manifestPath) as unknown as LensExtensionManifest; const id = this.getInstalledManifestPath(manifest.name); const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled }); const extensionDir = path.dirname(manifestPath); const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`); - const absolutePath = (isProduction && await fse.pathExists(npmPackage)) ? npmPackage : extensionDir; + const absolutePath = (isProduction && await this.dependencies.pathExists(npmPackage)) ? npmPackage : extensionDir; const isCompatible = (isBundled && this.dependencies.isCompatibleBundledExtension(manifest)) || this.dependencies.isCompatibleExtension(manifest); return { @@ -378,9 +383,9 @@ export class ExtensionDiscovery { } catch (error) { if (isErrnoException(error) && error.code === "ENOTDIR") { // ignore this error, probably from .DS_Store file - logger.debug(`${logModule}: failed to load extension manifest through a not-dir-like at ${manifestPath}`); + this.dependencies.logger.debug(`${logModule}: failed to load extension manifest through a not-dir-like at ${manifestPath}`); } else { - logger.error(`${logModule}: can't load extension manifest at ${manifestPath}: ${error}`); + this.dependencies.logger.error(`${logModule}: can't load extension manifest at ${manifestPath}: ${error}`); } return null; @@ -395,7 +400,7 @@ export class ExtensionDiscovery { const userExtensions = await this.loadFromFolder(this.localFolderPath, bundledExtensions.map((extension) => extension.manifest.name)); for (const extension of userExtensions) { - if ((await fse.pathExists(extension.manifestPath)) === false) { + if (!(await this.dependencies.pathExists(extension.manifestPath))) { try { await this.dependencies.installExtension(extension.absolutePath); } catch (error) { @@ -404,7 +409,7 @@ export class ExtensionDiscovery { : String(error || "unknown error"); const { name, version } = extension.manifest; - logger.error(`${logModule}: failed to install user extension ${name}@${version}: ${message}`); + this.dependencies.logger.error(`${logModule}: failed to install user extension ${name}@${version}: ${message}`); } } } @@ -438,7 +443,7 @@ export class ExtensionDiscovery { extensions.push(extension); } } - logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions }); + this.dependencies.logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions }); return extensions; } @@ -473,7 +478,7 @@ export class ExtensionDiscovery { } } - logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions }); + this.dependencies.logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions }); return extensions; } diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index d26fd92d7c..16b28de8e1 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { ipcRenderer } from "electron"; +import { ipcMain, ipcRenderer } from "electron"; import { isEqual } from "lodash"; import type { ObservableMap } from "mobx"; import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx"; @@ -127,10 +127,10 @@ export class ExtensionLoader { @action async init() { - if (ipcRenderer) { - await this.initRenderer(); - } else { + if (ipcMain) { await this.initMain(); + } else { + await this.initRenderer(); } await Promise.all([this.whenLoaded]); diff --git a/src/main/__test__/cluster.test.ts b/src/main/__test__/cluster.test.ts index 5e42c8e5fe..0244141ab3 100644 --- a/src/main/__test__/cluster.test.ts +++ b/src/main/__test__/cluster.test.ts @@ -2,11 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ - -jest.mock("../../common/ipc"); -jest.mock("request"); -jest.mock("request-promise-native"); - +import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable"; import { Console } from "console"; import type { Cluster } from "../../common/cluster/cluster"; import { Kubectl } from "../kubectl/kubectl"; @@ -41,6 +37,7 @@ describe("create clusters", () => { di.override(kubectlBinaryNameInjectable, () => "kubectl"); di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); di.override(normalizedPlatformInjectable, () => "darwin"); + di.override(broadcastMessageInjectable, () => async () => {}); di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true)); di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ])); di.override(createContextHandlerInjectable, () => (cluster) => ({ diff --git a/src/main/__test__/kube-auth-proxy.test.ts b/src/main/__test__/kube-auth-proxy.test.ts index 1e66d0d42b..47064ec215 100644 --- a/src/main/__test__/kube-auth-proxy.test.ts +++ b/src/main/__test__/kube-auth-proxy.test.ts @@ -3,45 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -jest.mock("winston", () => ({ - format: { - colorize: jest.fn(), - combine: jest.fn(), - simple: jest.fn(), - label: jest.fn(), - timestamp: jest.fn(), - printf: jest.fn(), - padLevels: jest.fn(), - ms: jest.fn(), - splat: jest.fn(), - }, - createLogger: jest.fn().mockReturnValue({ - silly: jest.fn(), - debug: jest.fn(), - log: jest.fn(), - info: jest.fn(), - error: jest.fn(), - crit: jest.fn(), - }), - transports: { - Console: jest.fn(), - File: jest.fn(), - }, -})); - -jest.mock("../../common/ipc"); -jest.mock("child_process"); -jest.mock("tcp-port-used"); - +import waitUntilPortIsUsedInjectable from "../kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.injectable"; import type { Cluster } from "../../common/cluster/cluster"; import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy"; -import { broadcastMessage } from "../../common/ipc"; import type { ChildProcess } from "child_process"; -import { spawn } from "child_process"; import { Kubectl } from "../kubectl/kubectl"; import type { DeepMockProxy } from "jest-mock-extended"; import { mockDeep, mock } from "jest-mock-extended"; -import { waitUntilUsed } from "tcp-port-used"; import type { Readable } from "stream"; import { EventEmitter } from "stream"; import { Console } from "console"; @@ -59,17 +27,18 @@ import directoryForTempInjectable from "../../common/app-paths/directory-for-tem import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable"; import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable"; import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable"; +import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable"; console = new Console(stdout, stderr); -const mockBroadcastIpc = broadcastMessage as jest.MockedFunction; -const mockSpawn = spawn as jest.MockedFunction; -const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction; const clusterServerUrl = "https://192.168.64.3:8443"; describe("kube auth proxy tests", () => { let createCluster: CreateCluster; let createKubeAuthProxy: (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy; + let spawnMock: jest.Mock; + let waitUntilPortIsUsedMock: jest.Mock; + let broadcastMessageMock: jest.Mock; beforeEach(async () => { jest.clearAllMocks(); @@ -105,7 +74,15 @@ describe("kube auth proxy tests", () => { di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); di.override(directoryForTempInjectable, () => "some-directory-for-temp"); - di.override(spawnInjectable, () => mockSpawn); + spawnMock = jest.fn(); + di.override(spawnInjectable, () => spawnMock); + + waitUntilPortIsUsedMock = jest.fn(); + di.override(waitUntilPortIsUsedInjectable, () => waitUntilPortIsUsedMock); + + broadcastMessageMock = jest.fn(); + di.override(broadcastMessageInjectable, () => broadcastMessageMock); + di.override(kubectlBinaryNameInjectable, () => "kubectl"); di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); di.override(normalizedPlatformInjectable, () => "darwin"); @@ -211,12 +188,12 @@ describe("kube auth proxy tests", () => { return stdout; }); - mockSpawn.mockImplementationOnce((command: string): ChildProcess => { + spawnMock.mockImplementationOnce((command: string): ChildProcess => { expect(path.basename(command).split(".")[0]).toBe("lens-k8s-proxy"); return mockedCP; }); - mockWaitUntilUsed.mockReturnValueOnce(Promise.resolve()); + waitUntilPortIsUsedMock.mockReturnValueOnce(Promise.resolve()); const cluster = createCluster({ id: "foobar", @@ -233,34 +210,34 @@ describe("kube auth proxy tests", () => { await proxy.run(); listeners.emit("error", { message: "foobarbat" }); - expect(mockBroadcastIpc).toBeCalledWith("cluster:foobar:connection-update", { message: "foobarbat", isError: true }); + expect(broadcastMessageMock).toBeCalledWith("cluster:foobar:connection-update", { message: "foobarbat", isError: true }); }); it("should call spawn and broadcast exit", async () => { await proxy.run(); listeners.emit("exit", 0); - expect(mockBroadcastIpc).toBeCalledWith("cluster:foobar:connection-update", { message: "proxy exited with code: 0", isError: false }); + expect(broadcastMessageMock).toBeCalledWith("cluster:foobar:connection-update", { message: "proxy exited with code: 0", isError: false }); }); it("should call spawn and broadcast errors from stderr", async () => { await proxy.run(); listeners.emit("stderr/data", "an error"); - expect(mockBroadcastIpc).toBeCalledWith("cluster:foobar:connection-update", { message: "an error", isError: true }); + expect(broadcastMessageMock).toBeCalledWith("cluster:foobar:connection-update", { message: "an error", isError: true }); }); it("should call spawn and broadcast stdout serving info", async () => { await proxy.run(); - expect(mockBroadcastIpc).toBeCalledWith("cluster:foobar:connection-update", { message: "Authentication proxy started", isError: false }); + expect(broadcastMessageMock).toBeCalledWith("cluster:foobar:connection-update", { message: "Authentication proxy started", isError: false }); }); it("should call spawn and broadcast stdout other info", async () => { await proxy.run(); listeners.emit("stdout/data", "some info"); - expect(mockBroadcastIpc).toBeCalledWith("cluster:foobar:connection-update", { message: "some info", isError: false }); + expect(broadcastMessageMock).toBeCalledWith("cluster:foobar:connection-update", { message: "some info", isError: false }); }); }); }); diff --git a/src/main/create-cluster/create-cluster.injectable.ts b/src/main/create-cluster/create-cluster.injectable.ts index 07c2ee3247..d8bf4209f0 100644 --- a/src/main/create-cluster/create-cluster.injectable.ts +++ b/src/main/create-cluster/create-cluster.injectable.ts @@ -15,6 +15,7 @@ import listNamespacesInjectable from "../../common/cluster/list-namespaces.injec import loggerInjectable from "../../common/logger.injectable"; import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable"; import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable"; +import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable"; const createClusterInjectable = getInjectable({ id: "create-cluster", @@ -30,6 +31,7 @@ const createClusterInjectable = getInjectable({ logger: di.inject(loggerInjectable), detectorRegistry: di.inject(detectorRegistryInjectable), createVersionDetector: di.inject(createVersionDetectorInjectable), + broadcastMessage: di.inject(broadcastMessageInjectable), }; return (model, configData) => new Cluster(dependencies, model, configData); diff --git a/src/main/getDiForUnitTesting.ts b/src/main/getDiForUnitTesting.ts index a333e5b1d4..af891b1412 100644 --- a/src/main/getDiForUnitTesting.ts +++ b/src/main/getDiForUnitTesting.ts @@ -58,7 +58,6 @@ import type { ClusterFrameInfo } from "../common/cluster-frames"; import { observable } from "mobx"; import waitForElectronToBeReadyInjectable from "./electron-app/features/wait-for-electron-to-be-ready.injectable"; import setupListenerForCurrentClusterFrameInjectable from "./start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable"; -import ipcMainInjectable from "./utils/channel/ipc-main/ipc-main.injectable"; import setupRunnablesAfterWindowIsOpenedInjectable from "./electron-app/runnables/setup-runnables-after-window-is-opened.injectable"; import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable"; import getElectronThemeInjectable from "./electron-app/features/get-electron-theme.injectable"; @@ -252,7 +251,6 @@ const overrideElectronFeatures = (di: DiContainer) => { di.override(shouldStartHiddenInjectable, () => false); di.override(showMessagePopupInjectable, () => () => {}); di.override(waitForElectronToBeReadyInjectable, () => () => Promise.resolve()); - di.override(ipcMainInjectable, () => ({})); di.override(getElectronThemeInjectable, () => () => "dark"); di.override(syncThemeFromOperatingSystemInjectable, () => ({ start: () => {}, stop: () => {} })); di.override(electronQuitAndInstallUpdateInjectable, () => () => {}); diff --git a/src/main/helm/__mocks__/helm-chart-manager.ts b/src/main/helm/__mocks__/helm-chart-manager.ts deleted file mode 100644 index 62c4ce4508..0000000000 --- a/src/main/helm/__mocks__/helm-chart-manager.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { sortCharts } from "../../../common/utils"; -import type { HelmRepo } from "../../../common/helm/helm-repo"; - -const charts = new Map([ - ["stable", { - "invalid-semver": sortCharts([ - { - apiVersion: "3.0.0", - name: "weird-versioning", - version: "I am not semver", - repo: "stable", - digest: "test", - created: "now", - }, - { - apiVersion: "3.0.0", - name: "weird-versioning", - version: "v4.3.0", - repo: "stable", - digest: "test", - created: "now", - }, - { - apiVersion: "3.0.0", - name: "weird-versioning", - version: "I am not semver but more", - repo: "stable", - digest: "test", - created: "now", - }, - { - apiVersion: "3.0.0", - name: "weird-versioning", - version: "v4.4.0", - repo: "stable", - digest: "test", - created: "now", - }, - ]), - "apm-server": sortCharts([ - { - apiVersion: "3.0.0", - name: "apm-server", - version: "2.1.7", - repo: "stable", - digest: "test", - created: "now", - }, - { - apiVersion: "3.0.0", - name: "apm-server", - version: "2.1.6", - repo: "stable", - digest: "test", - created: "now", - }, - ]), - "redis": sortCharts([ - { - apiVersion: "3.0.0", - name: "apm-server", - version: "1.0.0", - repo: "stable", - digest: "test", - created: "now", - }, - { - apiVersion: "3.0.0", - name: "apm-server", - version: "0.0.9", - repo: "stable", - digest: "test", - created: "now", - }, - ]), - }], - ["experiment", { - "fairwind": sortCharts([ - { - apiVersion: "3.0.0", - name: "fairwind", - version: "0.0.1", - repo: "experiment", - digest: "test", - created: "now", - }, - { - apiVersion: "3.0.0", - name: "fairwind", - version: "0.0.2", - repo: "experiment", - digest: "test", - deprecated: true, - created: "now", - }, - ]), - }], - ["bitnami", { - "hotdog": sortCharts([ - { - apiVersion: "3.0.0", - name: "hotdog", - version: "1.0.1", - repo: "bitnami", - digest: "test", - created: "now", - }, - { - apiVersion: "3.0.0", - name: "hotdog", - version: "1.0.2", - repo: "bitnami", - digest: "test", - created: "now", - }, - ]), - "pretzel": sortCharts([ - { - apiVersion: "3.0.0", - name: "pretzel", - version: "1.0", - repo: "bitnami", - digest: "test", - created: "now", - }, - { - apiVersion: "3.0.0", - name: "pretzel", - version: "1.0.1", - repo: "bitnami", - digest: "test", - created: "now", - }, - ]), - }], -]); - -export class HelmChartManager { - constructor(private repo: HelmRepo){ } - - static forRepo(repo: HelmRepo) { - return new this(repo); - } - - public async charts(): Promise { - return charts.get(this.repo.name) ?? {}; - } -} diff --git a/src/main/helm/__tests__/helm-service.test.ts b/src/main/helm/__tests__/helm-service.test.ts index fab93bf1e5..ea0a28b1e9 100644 --- a/src/main/helm/__tests__/helm-service.test.ts +++ b/src/main/helm/__tests__/helm-service.test.ts @@ -8,8 +8,8 @@ import listHelmChartsInjectable from "../helm-service/list-helm-charts.injectabl import getActiveHelmRepositoriesInjectable from "../repositories/get-active-helm-repositories/get-active-helm-repositories.injectable"; import type { AsyncResult } from "../../../common/utils/async-result"; import type { HelmRepo } from "../../../common/helm/helm-repo"; - -jest.mock("../helm-chart-manager"); +import { sortCharts } from "../../../common/utils"; +import helmChartManagerInjectable from "../helm-chart-manager.injectable"; describe("Helm Service tests", () => { let listHelmCharts: () => Promise; @@ -20,6 +20,11 @@ describe("Helm Service tests", () => { getActiveHelmRepositoriesMock = jest.fn(); + di.override( + helmChartManagerInjectable, + (di, repo) => new HelmChartManagerFake(repo) as unknown, + ); + di.override(getActiveHelmRepositoriesInjectable, () => getActiveHelmRepositoriesMock); di.unoverride(listHelmChartsInjectable); @@ -195,3 +200,145 @@ describe("Helm Service tests", () => { }); }); }); + +const charts = new Map([ + ["stable", { + "invalid-semver": sortCharts([ + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "I am not semver", + repo: "stable", + digest: "test", + created: "now", + }, + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "v4.3.0", + repo: "stable", + digest: "test", + created: "now", + }, + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "I am not semver but more", + repo: "stable", + digest: "test", + created: "now", + }, + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "v4.4.0", + repo: "stable", + digest: "test", + created: "now", + }, + ]), + "apm-server": sortCharts([ + { + apiVersion: "3.0.0", + name: "apm-server", + version: "2.1.7", + repo: "stable", + digest: "test", + created: "now", + }, + { + apiVersion: "3.0.0", + name: "apm-server", + version: "2.1.6", + repo: "stable", + digest: "test", + created: "now", + }, + ]), + "redis": sortCharts([ + { + apiVersion: "3.0.0", + name: "apm-server", + version: "1.0.0", + repo: "stable", + digest: "test", + created: "now", + }, + { + apiVersion: "3.0.0", + name: "apm-server", + version: "0.0.9", + repo: "stable", + digest: "test", + created: "now", + }, + ]), + }], + ["experiment", { + "fairwind": sortCharts([ + { + apiVersion: "3.0.0", + name: "fairwind", + version: "0.0.1", + repo: "experiment", + digest: "test", + created: "now", + }, + { + apiVersion: "3.0.0", + name: "fairwind", + version: "0.0.2", + repo: "experiment", + digest: "test", + deprecated: true, + created: "now", + }, + ]), + }], + ["bitnami", { + "hotdog": sortCharts([ + { + apiVersion: "3.0.0", + name: "hotdog", + version: "1.0.1", + repo: "bitnami", + digest: "test", + created: "now", + }, + { + apiVersion: "3.0.0", + name: "hotdog", + version: "1.0.2", + repo: "bitnami", + digest: "test", + created: "now", + }, + ]), + "pretzel": sortCharts([ + { + apiVersion: "3.0.0", + name: "pretzel", + version: "1.0", + repo: "bitnami", + digest: "test", + created: "now", + }, + { + apiVersion: "3.0.0", + name: "pretzel", + version: "1.0.1", + repo: "bitnami", + digest: "test", + created: "now", + }, + ]), + }], +]); + +class HelmChartManagerFake { + constructor(private repo: HelmRepo){ } + + public async charts(): Promise { + return charts.get(this.repo.name) ?? {}; + } +} diff --git a/src/main/helm/helm-chart-manager-cache.injectable.ts b/src/main/helm/helm-chart-manager-cache.injectable.ts new file mode 100644 index 0000000000..49df5e0843 --- /dev/null +++ b/src/main/helm/helm-chart-manager-cache.injectable.ts @@ -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 } from "@ogre-tools/injectable"; + +export interface ChartCacheEntry { + data: string; // serialized JSON + mtimeMs: number; +} + +export type HelmChartManagerCache = Map; + +const helmChartManagerCacheInjectable = getInjectable({ + id: "helm-chart-manager-cache", + instantiate: (): HelmChartManagerCache => new Map(), +}); + +export default helmChartManagerCacheInjectable; diff --git a/src/main/helm/helm-chart-manager.injectable.ts b/src/main/helm/helm-chart-manager.injectable.ts new file mode 100644 index 0000000000..367c47ae3b --- /dev/null +++ b/src/main/helm/helm-chart-manager.injectable.ts @@ -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 type { HelmRepo } from "../../common/helm/helm-repo"; +import { HelmChartManager } from "./helm-chart-manager"; +import helmChartManagerCacheInjectable from "./helm-chart-manager-cache.injectable"; +import loggerInjectable from "../../common/logger.injectable"; + +const helmChartManagerInjectable = getInjectable({ + id: "helm-chart-manager", + + instantiate: (di, repo: HelmRepo) => { + const cache = di.inject(helmChartManagerCacheInjectable); + const logger = di.inject(loggerInjectable); + + return new HelmChartManager(repo, { cache, logger }); + }, + + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, repo: HelmRepo) => repo.name, + }), +}); + +export default helmChartManagerInjectable; diff --git a/src/main/helm/helm-chart-manager.ts b/src/main/helm/helm-chart-manager.ts index 8ba7a83101..2eae449aea 100644 --- a/src/main/helm/helm-chart-manager.ts +++ b/src/main/helm/helm-chart-manager.ts @@ -5,39 +5,34 @@ import fs from "fs"; import * as yaml from "js-yaml"; -import logger from "../logger"; import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api"; import { iter, put, sortCharts } from "../../common/utils"; import { execHelm } from "./exec"; import type { SetRequired } from "type-fest"; import { assert } from "console"; import type { HelmRepo } from "../../common/helm/helm-repo"; - -interface ChartCacheEntry { - data: string; // serialized JSON - mtimeMs: number; -} +import type { HelmChartManagerCache } from "./helm-chart-manager-cache.injectable"; +import type { Logger } from "../../common/logger"; export interface HelmCacheFile { apiVersion: string; entries: RepoHelmChartList; } -export class HelmChartManager { - static readonly #cache = new Map(); +interface Dependencies { + cache: HelmChartManagerCache; + logger: Logger; +} +export class HelmChartManager { protected readonly repo: SetRequired; - private constructor(repo: HelmRepo) { + constructor(repo: HelmRepo, private dependencies: Dependencies) { assert(repo.cacheFilePath, "CacheFilePath must be provided on the helm repo"); this.repo = repo as SetRequired; } - static forRepo(repo: HelmRepo) { - return new this(repo); - } - public async chartVersions(name: string) { const charts = await this.charts(); @@ -48,7 +43,7 @@ export class HelmChartManager { try { return await this.cachedYaml(); } catch(error) { - logger.error("HELM-CHART-MANAGER]: failed to list charts", { error }); + this.dependencies.logger.error("HELM-CHART-MANAGER]: failed to list charts", { error }); return {}; } @@ -84,7 +79,7 @@ export class HelmChartManager { const normalized = normalizeHelmCharts(this.repo.name, data.entries); return put( - HelmChartManager.#cache, + this.dependencies.cache, this.repo.name, { data: JSON.stringify(normalized), @@ -94,7 +89,7 @@ export class HelmChartManager { } protected async cachedYaml(): Promise { - let cacheEntry = HelmChartManager.#cache.get(this.repo.name); + let cacheEntry = this.dependencies.cache.get(this.repo.name); if (!cacheEntry) { cacheEntry = await this.updateYamlCache(); diff --git a/src/main/helm/helm-service/get-helm-chart-values.injectable.ts b/src/main/helm/helm-service/get-helm-chart-values.injectable.ts index 2394a591f0..489f8b8521 100644 --- a/src/main/helm/helm-service/get-helm-chart-values.injectable.ts +++ b/src/main/helm/helm-service/get-helm-chart-values.injectable.ts @@ -3,14 +3,16 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { HelmChartManager } from "../helm-chart-manager"; import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable"; +import type { HelmRepo } from "../../../common/helm/helm-repo"; +import helmChartManagerInjectable from "../helm-chart-manager.injectable"; const getHelmChartValuesInjectable = getInjectable({ id: "get-helm-chart-values", instantiate: (di) => { const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable); + const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo); return async (repoName: string, chartName: string, version = "") => { const repo = await getActiveHelmRepository(repoName); @@ -19,7 +21,7 @@ const getHelmChartValuesInjectable = getInjectable({ return undefined; } - return HelmChartManager.forRepo(repo).getValues(chartName, version); + return getChartManager(repo).getValues(chartName, version); }; }, diff --git a/src/main/helm/helm-service/get-helm-chart.injectable.ts b/src/main/helm/helm-service/get-helm-chart.injectable.ts index 1a5a43da36..bd9615a3c4 100644 --- a/src/main/helm/helm-service/get-helm-chart.injectable.ts +++ b/src/main/helm/helm-service/get-helm-chart.injectable.ts @@ -3,14 +3,16 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { HelmChartManager } from "../helm-chart-manager"; import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable"; +import type { HelmRepo } from "../../../common/helm/helm-repo"; +import helmChartManagerInjectable from "../helm-chart-manager.injectable"; const getHelmChartInjectable = getInjectable({ id: "get-helm-chart", instantiate: (di) => { const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable); + const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo); return async (repoName: string, chartName: string, version = "") => { const repo = await getActiveHelmRepository(repoName); @@ -19,7 +21,7 @@ const getHelmChartInjectable = getInjectable({ return undefined; } - const chartManager = HelmChartManager.forRepo(repo); + const chartManager = getChartManager(repo); return { readme: await chartManager.getReadme(chartName, version), diff --git a/src/main/helm/helm-service/list-helm-charts.injectable.ts b/src/main/helm/helm-service/list-helm-charts.injectable.ts index b537021f0f..c5be54fd67 100644 --- a/src/main/helm/helm-service/list-helm-charts.injectable.ts +++ b/src/main/helm/helm-service/list-helm-charts.injectable.ts @@ -5,14 +5,16 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { object } from "../../../common/utils"; -import { HelmChartManager } from "../helm-chart-manager"; import getActiveHelmRepositoriesInjectable from "../repositories/get-active-helm-repositories/get-active-helm-repositories.injectable"; +import type { HelmRepo } from "../../../common/helm/helm-repo"; +import helmChartManagerInjectable from "../helm-chart-manager.injectable"; const listHelmChartsInjectable = getInjectable({ id: "list-helm-charts", instantiate: (di) => { const getActiveHelmRepositories = di.inject(getActiveHelmRepositoriesInjectable); + const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo); return async () => { const result = await getActiveHelmRepositories(); @@ -27,7 +29,7 @@ const listHelmChartsInjectable = getInjectable({ async (repo) => [ repo.name, - await HelmChartManager.forRepo(repo).charts(), + await getChartManager(repo).charts(), ] as const, ), ), diff --git a/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts b/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts index 8546adb4fb..67cfd8c26e 100644 --- a/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts +++ b/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts @@ -13,6 +13,7 @@ import spawnInjectable from "../child-process/spawn.injectable"; import { getKubeAuthProxyCertificate } from "./get-kube-auth-proxy-certificate"; import loggerInjectable from "../../common/logger.injectable"; import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable"; +import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used/wait-until-port-is-used.injectable"; export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy; @@ -29,6 +30,7 @@ const createKubeAuthProxyInjectable = getInjectable({ proxyCert: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate), spawn: di.inject(spawnInjectable), logger: di.inject(loggerInjectable), + waitUntilPortIsUsed: di.inject(waitUntilPortIsUsedInjectable), }; return new KubeAuthProxy(dependencies, cluster, environmentVariables); diff --git a/src/main/kube-auth-proxy/kube-auth-proxy.ts b/src/main/kube-auth-proxy/kube-auth-proxy.ts index fbbfa67a85..a6067e548b 100644 --- a/src/main/kube-auth-proxy/kube-auth-proxy.ts +++ b/src/main/kube-auth-proxy/kube-auth-proxy.ts @@ -4,10 +4,8 @@ */ import type { ChildProcess } from "child_process"; -import { waitUntilUsed } from "tcp-port-used"; import { randomBytes } from "crypto"; import type { Cluster } from "../../common/cluster/cluster"; -import logger from "../logger"; import { getPortFrom } from "../utils/get-port"; import { makeObservable, observable, when } from "mobx"; import type { SelfSignedCert } from "selfsigned"; @@ -15,6 +13,7 @@ import assert from "assert"; import { TypedRegEx } from "typed-regex"; import type { Spawn } from "../child-process/spawn.injectable"; import type { Logger } from "../../common/logger"; +import type { WaitUntilPortIsUsed } from "./wait-until-port-is-used/wait-until-port-is-used.injectable"; const startingServeMatcher = "starting to serve on (?
.+)"; const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"), { @@ -24,8 +23,9 @@ const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"), export interface KubeAuthProxyDependencies { readonly proxyBinPath: string; readonly proxyCert: SelfSignedCert; - spawn: Spawn; + readonly spawn: Spawn; readonly logger: Logger; + readonly waitUntilPortIsUsed: WaitUntilPortIsUsed; } export class KubeAuthProxy { @@ -106,13 +106,13 @@ export class KubeAuthProxy { onFind: () => this.cluster.broadcastConnectUpdate("Authentication proxy started"), }); - logger.info(`[KUBE-AUTH-PROXY]: found port=${this._port}`); + this.dependencies.logger.info(`[KUBE-AUTH-PROXY]: found port=${this._port}`); try { - await waitUntilUsed(this.port, 500, 10000); + await this.dependencies.waitUntilPortIsUsed(this.port, 500, 10000); this.ready = true; } catch (error) { - logger.warn("[KUBE-AUTH-PROXY]: waitUntilUsed failed", error); + this.dependencies.logger.warn("[KUBE-AUTH-PROXY]: waitUntilUsed failed", error); this.cluster.broadcastConnectUpdate("Proxy port failed to be used within timelimit, restarting...", true); this.exit(); @@ -124,7 +124,7 @@ export class KubeAuthProxy { this.ready = false; if (this.proxyProcess) { - logger.debug("[KUBE-AUTH]: stopping local proxy", this.cluster.getMeta()); + this.dependencies.logger.debug("[KUBE-AUTH]: stopping local proxy", this.cluster.getMeta()); this.proxyProcess.removeAllListeners(); this.proxyProcess.stderr?.removeAllListeners(); this.proxyProcess.stdout?.removeAllListeners(); diff --git a/src/main/kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.global-override-for-injectable.ts b/src/main/kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.global-override-for-injectable.ts new file mode 100644 index 0000000000..86f1e374cd --- /dev/null +++ b/src/main/kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.global-override-for-injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getGlobalOverride } from "../../../common/test-utils/get-global-override"; +import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used.injectable"; + +export default getGlobalOverride( + waitUntilPortIsUsedInjectable, + () => () => { + throw new Error( + "Tried to wait until port is used without explicit override.", + ); + }, +); diff --git a/src/main/kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.injectable.ts b/src/main/kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.injectable.ts new file mode 100644 index 0000000000..2d1662ac6a --- /dev/null +++ b/src/main/kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.injectable.ts @@ -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 } from "@ogre-tools/injectable"; +import { waitUntilUsed } from "tcp-port-used"; + +export type WaitUntilPortIsUsed = ( + port: number, + retryAfterMs: number, + timeoutAfterMs: number +) => Promise; + +const waitUntilPortIsUsedInjectable = getInjectable({ + id: "wait-until-port-is-used", + instantiate: (): WaitUntilPortIsUsed => waitUntilUsed, + causesSideEffects: true, +}); + +export default waitUntilPortIsUsedInjectable; diff --git a/src/main/protocol-handler/__test__/router.test.ts b/src/main/protocol-handler/__test__/router.test.ts index a0f26e9764..55aecaa8a0 100644 --- a/src/main/protocol-handler/__test__/router.test.ts +++ b/src/main/protocol-handler/__test__/router.test.ts @@ -5,7 +5,6 @@ import * as uuid from "uuid"; -import { broadcastMessage } from "../../../common/ipc"; import { ProtocolHandlerExtension, ProtocolHandlerInternal, ProtocolHandlerInvalid } from "../../../common/protocol-handler"; import { delay, noop } from "../../../common/utils"; import type { ExtensionsStore, IsEnabledExtensionDescriptor } from "../../../extensions/extensions-store/extensions-store"; @@ -19,8 +18,7 @@ import type { LensExtensionId } from "../../../extensions/lens-extension"; import type { ObservableMap } from "mobx"; import extensionInstancesInjectable from "../../../extensions/extension-loader/extension-instances.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; - -jest.mock("../../../common/ipc"); +import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable"; function throwIfDefined(val: any): void { if (val != null) { @@ -32,6 +30,7 @@ describe("protocol router tests", () => { let extensionInstances: ObservableMap; let lpr: LensProtocolRouterMain; let enabledExtensions: Set; + let broadcastMessageMock: jest.Mock; beforeEach(async () => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); @@ -46,6 +45,9 @@ describe("protocol router tests", () => { di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + broadcastMessageMock = jest.fn(); + di.override(broadcastMessageInjectable, () => broadcastMessageMock); + extensionInstances = di.inject(extensionInstancesInjectable); lpr = di.inject(lensProtocolRouterMainInjectable); @@ -54,12 +56,12 @@ describe("protocol router tests", () => { it("should broadcast invalid protocol on non-lens URLs", async () => { await lpr.route("https://google.ca"); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInvalid, "invalid protocol", "https://google.ca"); + expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInvalid, "invalid protocol", "https://google.ca"); }); it("should broadcast invalid host on non internal or non extension URLs", async () => { await lpr.route("lens://foobar"); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInvalid, "invalid host", "lens://foobar"); + expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInvalid, "invalid host", "lens://foobar"); }); it("should not throw when has valid host", async () => { @@ -101,8 +103,8 @@ describe("protocol router tests", () => { } await delay(50); - expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app", "matched"); - expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched"); + expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app", "matched"); + expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched"); }); it("should call handler if matches", async () => { @@ -117,7 +119,7 @@ describe("protocol router tests", () => { } expect(called).toBe(true); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page", "matched"); + expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page", "matched"); }); it("should call most exact handler", async () => { @@ -133,7 +135,7 @@ describe("protocol router tests", () => { } expect(called).toBe("foo"); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo", "matched"); + expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo", "matched"); }); it("should call most exact handler for an extension", async () => { @@ -174,7 +176,7 @@ describe("protocol router tests", () => { await delay(50); expect(called).toBe("foob"); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob", "matched"); + expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob", "matched"); }); it("should work with non-org extensions", async () => { @@ -244,7 +246,7 @@ describe("protocol router tests", () => { await delay(50); expect(called).toBe(1); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page", "matched"); + expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page", "matched"); }); it("should throw if urlSchema is invalid", () => { @@ -266,7 +268,7 @@ describe("protocol router tests", () => { } expect(called).toBe(3); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched"); + expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched"); }); it("should call most exact handler with 2 found handlers", async () => { @@ -283,6 +285,6 @@ describe("protocol router tests", () => { } expect(called).toBe(1); - expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched"); + expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched"); }); }); diff --git a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts index 630d63d82c..bce91119c2 100644 --- a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts +++ b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts @@ -7,6 +7,8 @@ import extensionLoaderInjectable from "../../../extensions/extension-loader/exte import { LensProtocolRouterMain } from "./lens-protocol-router-main"; import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable"; import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable"; +import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable"; +import loggerInjectable from "../../../common/logger.injectable"; const lensProtocolRouterMainInjectable = getInjectable({ id: "lens-protocol-router-main", @@ -16,6 +18,8 @@ const lensProtocolRouterMainInjectable = getInjectable({ extensionLoader: di.inject(extensionLoaderInjectable), extensionsStore: di.inject(extensionsStoreInjectable), showApplicationWindow: di.inject(showApplicationWindowInjectable), + broadcastMessage: di.inject(broadcastMessageInjectable), + logger: di.inject(loggerInjectable), }), }); diff --git a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts index 14b8fa3347..3beb596e4e 100644 --- a/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts +++ b/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts @@ -3,15 +3,14 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import logger from "../../logger"; import * as proto from "../../../common/protocol-handler"; import URLParse from "url-parse"; import type { LensExtension } from "../../../extensions/lens-extension"; -import { broadcastMessage } from "../../../common/ipc"; import { observable, when, makeObservable } from "mobx"; import type { LensProtocolRouterDependencies, RouteAttempt } from "../../../common/protocol-handler"; import { ProtocolHandlerInvalid } from "../../../common/protocol-handler"; import { disposer, noop } from "../../../common/utils"; +import type { BroadcastMessage } from "../../../common/ipc/broadcast-message.injectable"; export interface FallbackHandler { (name: string): Promise; @@ -36,6 +35,7 @@ function checkHost(url: URLParse): boolean { export interface LensProtocolRouterMainDependencies extends LensProtocolRouterDependencies { showApplicationWindow: () => Promise; + broadcastMessage: BroadcastMessage; } export class LensProtocolRouterMain extends proto.LensProtocolRouter { @@ -73,7 +73,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { this.dependencies.showApplicationWindow().catch(noop); const routeInternally = checkHost(url); - logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`); + this.dependencies.logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`); if (routeInternally) { this._routeToInternal(url); @@ -81,12 +81,12 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { await this._routeToExtension(url); } } catch (error) { - broadcastMessage(ProtocolHandlerInvalid, error ? String(error) : "unknown error", rawUrl); + this.dependencies.broadcastMessage(ProtocolHandlerInvalid, error ? String(error) : "unknown error", rawUrl); if (error instanceof proto.RoutingError) { - logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { url: error.url }); + this.dependencies.logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { url: error.url }); } else { - logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { rawUrl }); + this.dependencies.logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { rawUrl }); } } } @@ -119,7 +119,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { const rawUrl = url.toString(); // for sending to renderer const attempt = super._routeToInternal(url); - this.disposers.push(when(() => this.rendererLoaded, () => broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt))); + this.disposers.push(when(() => this.rendererLoaded, () => this.dependencies.broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt))); return attempt; } @@ -136,7 +136,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { */ const attempt = await super._routeToExtension(new URLParse(url.toString(), true)); - this.disposers.push(when(() => this.rendererLoaded, () => broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt))); + this.disposers.push(when(() => this.rendererLoaded, () => this.dependencies.broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt))); return attempt; } diff --git a/src/main/start-main-application/start-main-application.injectable.ts b/src/main/start-main-application/start-main-application.injectable.ts index 0bf77b2fcd..fcd1851575 100644 --- a/src/main/start-main-application/start-main-application.injectable.ts +++ b/src/main/start-main-application/start-main-application.injectable.ts @@ -11,7 +11,6 @@ import { beforeApplicationIsLoadingInjectionToken } from "./runnable-tokens/befo import { onLoadOfApplicationInjectionToken } from "./runnable-tokens/on-load-of-application-injection-token"; import { afterApplicationIsLoadedInjectionToken } from "./runnable-tokens/after-application-is-loaded-injection-token"; import splashWindowInjectable from "./lens-window/splash-window/splash-window.injectable"; - import openDeepLinkInjectable from "../protocol-handler/lens-protocol-router-main/open-deep-link-for-url/open-deep-link.injectable"; import { pipeline } from "@ogre-tools/fp"; import { find, map, startsWith, toLower } from "lodash/fp"; diff --git a/src/main/utils/channel/ipc-main/ipc-main.global-override-for-injectable.ts b/src/main/utils/channel/ipc-main/ipc-main.global-override-for-injectable.ts new file mode 100644 index 0000000000..e770ccdf35 --- /dev/null +++ b/src/main/utils/channel/ipc-main/ipc-main.global-override-for-injectable.ts @@ -0,0 +1,13 @@ +/** + * 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 { getGlobalOverride } from "../../../../common/test-utils/get-global-override"; +import ipcMainInjectable from "./ipc-main.injectable"; + +export default getGlobalOverride(ipcMainInjectable, () => ({ + handle: () => {}, + on: () => {}, + off: () => {}, +}) as unknown as IpcMain); diff --git a/src/renderer/api/catalog/entity/registry.injectable.ts b/src/renderer/api/catalog/entity/registry.injectable.ts index 03d4edf52e..a06413afdf 100644 --- a/src/renderer/api/catalog/entity/registry.injectable.ts +++ b/src/renderer/api/catalog/entity/registry.injectable.ts @@ -6,12 +6,14 @@ import { getInjectable } from "@ogre-tools/injectable"; import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable"; import navigateInjectable from "../../../navigation/navigate.injectable"; import { CatalogEntityRegistry } from "./registry"; +import loggerInjectable from "../../../../common/logger.injectable"; const catalogEntityRegistryInjectable = getInjectable({ id: "catalog-entity-registry", instantiate: (di) => new CatalogEntityRegistry({ categoryRegistry: di.inject(catalogCategoryRegistryInjectable), navigate: di.inject(navigateInjectable), + logger: di.inject(loggerInjectable), }), }); diff --git a/src/renderer/api/catalog/entity/registry.ts b/src/renderer/api/catalog/entity/registry.ts index 1f53cf3d94..9971be8462 100644 --- a/src/renderer/api/catalog/entity/registry.ts +++ b/src/renderer/api/catalog/entity/registry.ts @@ -10,12 +10,12 @@ import "../../../../common/catalog-entities"; import { iter } from "../../../utils"; import type { Disposer } from "../../../utils"; import { once } from "lodash"; -import logger from "../../../../common/logger"; import { CatalogRunEvent } from "../../../../common/catalog/catalog-run-event"; import { ipcRenderer } from "electron"; import { catalogInitChannel, catalogItemsChannel, catalogEntityRunListener } from "../../../../common/ipc/catalog"; import { isMainFrame } from "process"; import type { Navigate } from "../../../navigation/navigate.injectable"; +import type { Logger } from "../../../../common/logger"; export type EntityFilter = (entity: CatalogEntity) => any; export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promise; @@ -23,6 +23,7 @@ export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promis interface Dependencies { navigate: Navigate; readonly categoryRegistry: CatalogCategoryRegistry; + logger: Logger; } export class CatalogEntityRegistry { @@ -219,7 +220,7 @@ export class CatalogEntityRegistry { * @returns Whether the entities `onRun` method should be executed */ async onBeforeRun(entity: CatalogEntity): Promise { - logger.debug(`[CATALOG-ENTITY-REGISTRY]: run onBeforeRun on ${entity.getId()}`); + this.dependencies.logger.debug(`[CATALOG-ENTITY-REGISTRY]: run onBeforeRun on ${entity.getId()}`); const runEvent = new CatalogRunEvent({ target: entity }); @@ -227,7 +228,7 @@ export class CatalogEntityRegistry { try { await onBeforeRun(runEvent); } catch (error) { - logger.warn(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onBeforeRun threw an error`, error); + this.dependencies.logger.warn(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onBeforeRun threw an error`, error); } if (runEvent.defaultPrevented) { @@ -253,9 +254,9 @@ export class CatalogEntityRegistry { }, }); } else { - logger.debug(`onBeforeRun for ${entity.getId()} returned false`); + this.dependencies.logger.debug(`onBeforeRun for ${entity.getId()} returned false`); } }) - .catch(error => logger.error(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onRun threw an error`, error)); + .catch(error => this.dependencies.logger.error(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onRun threw an error`, error)); } } diff --git a/src/renderer/components/+catalog/catalog.test.tsx b/src/renderer/components/+catalog/catalog.test.tsx index 899b97bed2..885fa640d5 100644 --- a/src/renderer/components/+catalog/catalog.test.tsx +++ b/src/renderer/components/+catalog/catalog.test.tsx @@ -7,10 +7,9 @@ import React from "react"; import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { Catalog } from "./catalog"; -import { mockWindow } from "../../../../__mocks__/windowMock"; import type { CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog"; import { CatalogEntity } from "../../../common/catalog"; -import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry"; +import type { CatalogEntityOnBeforeRun, CatalogEntityRegistry } from "../../api/catalog/entity/registry"; import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; @@ -19,38 +18,15 @@ import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity- import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable"; import type { DiRender } from "../test-utils/renderFor"; import { renderFor } from "../test-utils/renderFor"; -import mockFs from "mock-fs"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import type { AppEvent } from "../../../common/app-event-bus/event-bus"; import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable"; import { computed } from "mobx"; import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable"; - -mockWindow(); -jest.mock("electron", () => ({ - app: { - getVersion: () => "99.99.99", - getName: () => "lens", - setName: jest.fn(), - setPath: jest.fn(), - getPath: () => "tmp", - getLocale: () => "en", - setLoginItemSettings: jest.fn(), - }, - ipcMain: { - on: jest.fn(), - handle: jest.fn(), - }, - ipcRenderer: { - on: jest.fn(), - invoke: jest.fn(), - }, -})); - -jest.mock("./hotbar-toggle-menu-item", () => ({ - HotbarToggleMenuItem: () =>
menu item
, -})); +import type { AsyncFnMock } from "@async-fn/jest"; +import asyncFn from "@async-fn/jest"; +import { flushPromises } from "../../../common/test-utils/flush-promises"; class MockCatalogEntity extends CatalogEntity { public apiVersion = "api"; @@ -95,7 +71,6 @@ describe("", () => { di.permitSideEffects(getConfigurationFileModelInjectable); - mockFs(); CatalogEntityDetailRegistry.createInstance(); render = renderFor(di); @@ -103,8 +78,6 @@ describe("", () => { catalogEntityItem = createMockCatalogEntity(onRun); catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); - di.override(catalogEntityRegistryInjectable, () => catalogEntityRegistry); - emitEvent = jest.fn(); di.override(appEventBusInjectable, () => ({ @@ -119,60 +92,71 @@ describe("", () => { afterEach(() => { CatalogEntityDetailRegistry.resetInstance(); - jest.clearAllMocks(); - jest.restoreAllMocks(); - mockFs.restore(); }); - it("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", (done) => { - catalogEntityRegistry.addOnBeforeRun( - (event) => { - expect(event.target.getId()).toBe("a_catalogEntity_uid"); - expect(event.target.getName()).toBe("a catalog entity"); + describe("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", () => { + let onBeforeRunMock: AsyncFnMock; - setTimeout(() => { - expect(onRun).toHaveBeenCalled(); - done(); - }, 500); - }, - ); + beforeEach(() => { + onBeforeRunMock = asyncFn(); + + catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock); + + render(); + + userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); + }); + + it("calls on before run event", () => { + const target = onBeforeRunMock.mock.calls[0][0].target; + + const actual = { id: target.getId(), name: target.getName() }; + + expect(actual).toEqual({ + id: "a_catalogEntity_uid", + name: "a catalog entity", + }); + }); + + it("does not call onRun yet", () => { + expect(onRun).not.toHaveBeenCalled(); + }); + + it("when before run event resolves, calls onRun", async () => { + await onBeforeRunMock.resolve(); + + expect(onRun).toHaveBeenCalled(); + }); + }); + + it("onBeforeRun prevents event => onRun wont be triggered", async () => { + const onBeforeRunMock = jest.fn((event) => event.preventDefault()); + + catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock); render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); + + await flushPromises(); + + expect(onRun).not.toHaveBeenCalled(); }); - it("onBeforeRun prevents event => onRun wont be triggered", (done) => { - catalogEntityRegistry.addOnBeforeRun( - (e) => { - setTimeout(() => { - expect(onRun).not.toHaveBeenCalled(); - done(); - }, 500); - e.preventDefault(); - }, - ); + it("addOnBeforeRun throw an exception => onRun will be triggered", async () => { + const onBeforeRunMock = jest.fn(() => { + throw new Error("some error"); + }); + + catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock); render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); - }); - it("addOnBeforeRun throw an exception => onRun will be triggered", (done) => { - catalogEntityRegistry.addOnBeforeRun( - () => { - setTimeout(() => { - expect(onRun).toHaveBeenCalled(); - done(); - }, 500); + await flushPromises(); - throw new Error("error!"); - }, - ); - - render(); - - userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); + expect(onRun).toHaveBeenCalled(); }); it("addOnRunHook return a promise and does not prevent run event => onRun()", (done) => { @@ -189,40 +173,34 @@ describe("", () => { userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); }); - it("addOnRunHook return a promise and prevents event wont be triggered", (done) => { - catalogEntityRegistry.addOnBeforeRun( - async (e) => { - expect(onRun).not.toBeCalled(); + it("addOnRunHook return a promise and prevents event wont be triggered", async () => { + const onBeforeRunMock = asyncFn(); - setTimeout(() => { - expect(onRun).not.toBeCalled(); - done(); - }, 500); - - e.preventDefault(); - }, - ); + catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock); render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); + + onBeforeRunMock.mock.calls[0][0].preventDefault(); + + await onBeforeRunMock.resolve(); + + expect(onRun).not.toHaveBeenCalled(); }); - it("addOnRunHook return a promise and reject => onRun will be triggered", (done) => { - catalogEntityRegistry.addOnBeforeRun( - async () => { - setTimeout(() => { - expect(onRun).toHaveBeenCalled(); - done(); - }, 500); + it("addOnRunHook return a promise and reject => onRun will be triggered", async () => { + const onBeforeRunMock = asyncFn(); - throw new Error("rejection!"); - }, - ); + catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock); render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); + + await onBeforeRunMock.reject(); + + expect(onRun).toHaveBeenCalled(); }); it("emits catalog open AppEvent", () => { diff --git a/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx b/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx index 6903e853bc..dd4044014c 100644 --- a/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx +++ b/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx @@ -4,17 +4,20 @@ */ import React from "react"; -import { render } from "@testing-library/react"; import { SecretDetails } from "../secret-details"; import { Secret, SecretType } from "../../../../common/k8s-api/endpoints"; +import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; +import { renderFor } from "../../test-utils/renderFor"; jest.mock("../../kube-object-meta/kube-object-meta", () => ({ KubeObjectMeta: () => null, })); - describe("SecretDetails tests", () => { it("should show the visibility toggle when the secret value is ''", () => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + const render = renderFor(di); + const secret = new Secret({ apiVersion: "v1", kind: "secret", diff --git a/src/renderer/components/+namespaces/namespace-select-filter.scss b/src/renderer/components/+namespaces/namespace-select-filter.scss index 869d647557..f227976b46 100644 --- a/src/renderer/components/+namespaces/namespace-select-filter.scss +++ b/src/renderer/components/+namespaces/namespace-select-filter.scss @@ -20,6 +20,7 @@ padding-left: 8px; margin-right: -8px; padding-right: 8px; + line-height: 1.1; &::-webkit-scrollbar { display: none; diff --git a/src/renderer/components/+network-policies/__tests__/network-policy-details.test.tsx b/src/renderer/components/+network-policies/__tests__/network-policy-details.test.tsx index 0a4e9b22d7..e25bebc0c2 100644 --- a/src/renderer/components/+network-policies/__tests__/network-policy-details.test.tsx +++ b/src/renderer/components/+network-policies/__tests__/network-policy-details.test.tsx @@ -4,15 +4,22 @@ */ import React from "react"; -import { findByTestId, findByText, render } from "@testing-library/react"; +import { findByTestId, findByText } from "@testing-library/react"; import { NetworkPolicy } from "../../../../common/k8s-api/endpoints"; import { NetworkPolicyDetails } from "../network-policy-details"; - -jest.mock("../../kube-object-meta/kube-object-meta", () => ({ - KubeObjectMeta: () => null, -})); +import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; +import type { DiRender } from "../../test-utils/renderFor"; +import { renderFor } from "../../test-utils/renderFor"; describe("NetworkPolicyDetails", () => { + let render: DiRender; + + beforeEach(() => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + render = renderFor(di); + }); + it("should render w/o errors", () => { const policy = new NetworkPolicy({ metadata: {} as never, diff --git a/src/renderer/components/+workloads-pods/pod-details-list.scss b/src/renderer/components/+workloads-pods/pod-details-list.scss index 0b8c5b16be..fe161b023f 100644 --- a/src/renderer/components/+workloads-pods/pod-details-list.scss +++ b/src/renderer/components/+workloads-pods/pod-details-list.scss @@ -31,6 +31,10 @@ flex-grow: 2; } + &.node { + flex-grow: 2; + } + &.namespace { flex-grow: 1.2; } diff --git a/src/renderer/components/+workloads-pods/pod-details-list.tsx b/src/renderer/components/+workloads-pods/pod-details-list.tsx index 907d60b334..46e9e2151f 100644 --- a/src/renderer/components/+workloads-pods/pod-details-list.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-list.tsx @@ -22,6 +22,7 @@ import { showDetails } from "../kube-detail-params"; enum sortBy { name = "name", + node = "node", namespace = "namespace", cpu = "cpu", memory = "memory", @@ -124,6 +125,7 @@ export class PodDetailsList extends React.Component { > {pod.getName()} + {pod.getNodeName()} {pod.getNs()} {`${pod.getRunningContainers().length} / ${pod.getContainers().length}`} @@ -165,6 +167,7 @@ export class PodDetailsList extends React.Component { virtualHeight={660} sortable={{ [sortBy.name]: pod => pod.getName(), + [sortBy.node]: pod => pod.getNodeName(), [sortBy.namespace]: pod => pod.getNs(), [sortBy.cpu]: pod => podStore.getPodKubeMetrics(pod).cpu, [sortBy.memory]: pod => podStore.getPodKubeMetrics(pod).memory, @@ -182,6 +185,7 @@ export class PodDetailsList extends React.Component { Name + Node Namespace Ready CPU diff --git a/src/renderer/components/+workloads-pods/pod-details.tsx b/src/renderer/components/+workloads-pods/pod-details.tsx index eb9bd2429d..d87ce249e0 100644 --- a/src/renderer/components/+workloads-pods/pod-details.tsx +++ b/src/renderer/components/+workloads-pods/pod-details.tsx @@ -116,6 +116,9 @@ export class PodDetails extends React.Component { > {podIPs.map(label => )} + + {pod.getServiceAccountName()} + {pod.getPriorityClassName()} diff --git a/src/renderer/components/+workloads-replicasets/scale-dialog/dialog.tsx b/src/renderer/components/+workloads-replicasets/scale-dialog/dialog.tsx index 2193da284b..3b6969a5bb 100644 --- a/src/renderer/components/+workloads-replicasets/scale-dialog/dialog.tsx +++ b/src/renderer/components/+workloads-replicasets/scale-dialog/dialog.tsx @@ -104,7 +104,7 @@ class NonInjectedReplicaSetScaleDialog extends Component - Scale Replica Set + {"Scale Replica Set "} {replicaSet.getName()} )} @@ -131,16 +131,16 @@ class NonInjectedReplicaSetScaleDialog extends Component
- +
{warning && ( diff --git a/src/renderer/components/app.scss b/src/renderer/components/app.scss index 68cc6db3d9..f072a404a7 100755 --- a/src/renderer/components/app.scss +++ b/src/renderer/components/app.scss @@ -180,6 +180,10 @@ a { } } +iframe { + color-scheme: auto; +} + // colors .success { color: var(--colorSuccess); diff --git a/src/renderer/components/cluster-settings/components/__tests__/cluster-local-terminal-settings.test.tsx b/src/renderer/components/cluster-settings/components/__tests__/cluster-local-terminal-settings.test.tsx index 1b845ed9d4..b4a78a5dc0 100644 --- a/src/renderer/components/cluster-settings/components/__tests__/cluster-local-terminal-settings.test.tsx +++ b/src/renderer/components/cluster-settings/components/__tests__/cluster-local-terminal-settings.test.tsx @@ -4,28 +4,38 @@ */ import React from "react"; -import { render, waitFor } from "@testing-library/react"; +import { waitFor } from "@testing-library/react"; import { ClusterLocalTerminalSetting } from "../cluster-local-terminal-settings"; import userEvent from "@testing-library/user-event"; -import { stat } from "fs/promises"; -import { Notifications } from "../../../notifications"; import type { Stats } from "fs"; import type { Cluster } from "../../../../../common/cluster/cluster"; - -const mockStat = stat as jest.MockedFunction; - -jest.mock("fs", () => { - const actual = jest.requireActual("fs"); - - actual.promises.stat = jest.fn(); - - return actual; -}); - -jest.mock("../../../notifications"); +import { getDiForUnitTesting } from "../../../../getDiForUnitTesting"; +import type { DiRender } from "../../../test-utils/renderFor"; +import { renderFor } from "../../../test-utils/renderFor"; +import showErrorNotificationInjectable from "../../../notifications/show-error-notification.injectable"; +import statInjectable from "../../../../../common/fs/stat/stat.injectable"; describe("ClusterLocalTerminalSettings", () => { + let render: DiRender; + let showErrorNotificationMock: jest.Mock; + let statMock: jest.Mock; + beforeEach(() => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + showErrorNotificationMock = jest.fn(); + + statMock = jest.fn(); + + di.override(statInjectable, () => statMock); + + di.override( + showErrorNotificationInjectable, + () => showErrorNotificationMock, + ); + + render = renderFor(di); + jest.resetAllMocks(); }); @@ -89,7 +99,7 @@ describe("ClusterLocalTerminalSettings", () => { }); it("should save the new CWD if path is a directory", async () => { - mockStat.mockImplementation(async (path) => { + statMock.mockImplementation(async (path) => { expect(path).toBe("/foobar"); return { @@ -114,7 +124,7 @@ describe("ClusterLocalTerminalSettings", () => { }); it("should not save the new CWD if path is a file", async () => { - mockStat.mockImplementation(async (path) => { + statMock.mockImplementation(async (path) => { expect(path).toBe("/foobar"); return { @@ -136,6 +146,6 @@ describe("ClusterLocalTerminalSettings", () => { userEvent.type(dn, "/foobar"); userEvent.click(dom.baseElement); - await waitFor(() => expect(Notifications.error).toBeCalled()); + await waitFor(() => expect(showErrorNotificationMock).toHaveBeenCalled()); }); }); diff --git a/src/renderer/components/cluster-settings/components/cluster-local-terminal-settings.tsx b/src/renderer/components/cluster-settings/components/cluster-local-terminal-settings.tsx index ab1a4b9db0..3a5c1c4d2d 100644 --- a/src/renderer/components/cluster-settings/components/cluster-local-terminal-settings.tsx +++ b/src/renderer/components/cluster-settings/components/cluster-local-terminal-settings.tsx @@ -8,79 +8,25 @@ import { observer } from "mobx-react"; import type { Cluster } from "../../../../common/cluster/cluster"; import { Input } from "../../input"; import { SubTitle } from "../../layout/sub-title"; -import { stat } from "fs/promises"; -import { Notifications } from "../../notifications"; -import { isErrnoException, resolveTilde } from "../../../utils"; +import type { ShowNotification } from "../../notifications"; +import { resolveTilde } from "../../../utils"; import { Icon } from "../../icon"; import { PathPicker } from "../../path-picker"; import { isWindows } from "../../../../common/vars"; -import type { Stats } from "fs"; -import logger from "../../../../common/logger"; -import { lowerFirst } from "lodash"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable"; +import type { ValidateDirectory } from "../../../../common/fs/validate-directory.injectable"; +import validateDirectoryInjectable from "../../../../common/fs/validate-directory.injectable"; export interface ClusterLocalTerminalSettingProps { cluster: Cluster; } - -function getUserReadableFileType(stats: Stats): string { - if (stats.isFile()) { - return "a file"; - } - - if (stats.isFIFO()) { - return "a pipe"; - } - - if (stats.isSocket()) { - return "a socket"; - } - - if (stats.isBlockDevice()) { - return "a block device"; - } - - if (stats.isCharacterDevice()) { - return "a character device"; - } - - return "an unknown file type"; +interface Dependencies { + showErrorNotification: ShowNotification; + validateDirectory: ValidateDirectory; } -/** - * Validate that `dir` currently points to a directory. If so return `false`. - * Otherwise, return a user readable error message string for displaying. - * @param dir The path to be validated - */ -async function validateDirectory(dir: string): Promise { - try { - const stats = await stat(dir); - - if (stats.isDirectory()) { - return false; - } - - return `the provided path is ${getUserReadableFileType(stats)} and not a directory.`; - } catch (error) { - switch (isErrnoException(error) ? error.code : undefined) { - case "ENOENT": - return `the provided path does not exist.`; - case "EACCES": - return `search permissions is denied for one of the directories in the prefix of the provided path.`; - case "ELOOP": - return `the provided path is a sym-link which points to a chain of sym-links that is too long to resolve. Perhaps it is cyclic.`; - case "ENAMETOOLONG": - return `the pathname is too long to be used.`; - case "ENOTDIR": - return `a prefix of the provided path is not a directory.`; - default: - logger.warn(`[CLUSTER-LOCAL-TERMINAL-SETTINGS]: unexpected error in validateDirectory for resolved path=${dir}`, error); - - return error ? lowerFirst(String(error)) : "of an unknown error, please try again."; - } - } -} - -export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTerminalSettingProps) => { +const NonInjectedClusterLocalTerminalSetting = observer(({ cluster, showErrorNotification, validateDirectory }: Dependencies & ClusterLocalTerminalSettingProps) => { if (!cluster) { return null; } @@ -109,15 +55,15 @@ export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTe cluster.preferences.terminalCWD = undefined; } else { const dir = resolveTilde(directory); - const errorMessage = await validateDirectory(dir); + const result = await validateDirectory(dir); - if (errorMessage) { - Notifications.error( + if (!result.callWasSuccessful) { + showErrorNotification( <> Terminal Working Directory

{"Your changes were not saved because "} - {errorMessage} + {result.error}

, ); @@ -166,6 +112,8 @@ export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTe material="close" title="Clear" onClick={() => setAndCommitDirectory("")} + smallest + style={{ marginRight: "var(--margin)" }} /> ) } @@ -173,6 +121,7 @@ export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTe material="folder" title="Pick from filesystem" onClick={openFilePicker} + smallest /> )} @@ -200,3 +149,16 @@ export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTe ); }); + +export const ClusterLocalTerminalSetting = withInjectables( + NonInjectedClusterLocalTerminalSetting, + + { + getProps: (di, props) => ({ + showErrorNotification: di.inject(showErrorNotificationInjectable), + validateDirectory: di.inject(validateDirectoryInjectable), + ...props, + }), + }, +); + diff --git a/src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap b/src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap index 799e841e5d..372c6482eb 100644 --- a/src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap +++ b/src/renderer/components/kube-object-menu/__snapshots__/kube-object-menu.test.tsx.snap @@ -19,7 +19,6 @@ exports[`kube-object-menu given kube object renders 1`] = ` +
+ Delete +
@@ -59,7 +61,6 @@ exports[`kube-object-menu given kube object when removing kube object renders 1` +
+ Delete +
@@ -148,7 +152,6 @@ exports[`kube-object-menu given kube object when rerendered with different kube +
+ Delete +
@@ -185,7 +191,6 @@ exports[`kube-object-menu given kube object when rerendered with different kube +
+ Delete +
@@ -277,7 +285,6 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob +
+ Delete +
@@ -369,7 +379,6 @@ exports[`kube-object-menu given kube object without namespace when removing kube +
+ Delete +
diff --git a/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx b/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx index 0f0b338d8f..80a783c19a 100644 --- a/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx +++ b/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx @@ -26,12 +26,6 @@ import createEditResourceTabInjectable from "../dock/edit-resource/edit-resource import hideDetailsInjectable from "../kube-detail-params/hide-details.injectable"; import { kubeObjectMenuItemInjectionToken } from "./kube-object-menu-item-injection-token"; -// TODO: Make tooltips free of side effects by making it deterministic -jest.mock("../tooltip/tooltip"); -jest.mock("../tooltip/withTooltip", () => ({ - withTooltip: (target: any) => target, -})); - // TODO: make `animated={false}` not required to make tests deterministic describe("kube-object-menu", () => { let di: DiContainer; diff --git a/src/renderer/components/select/select.scss b/src/renderer/components/select/select.scss index 9faa60b54a..1cc50a2c00 100644 --- a/src/renderer/components/select/select.scss +++ b/src/renderer/components/select/select.scss @@ -49,6 +49,7 @@ html { &__single-value { color: var(--textColorSecondary); + overflow: visible; } &__indicator { diff --git a/src/renderer/create-cluster/create-cluster.injectable.ts b/src/renderer/create-cluster/create-cluster.injectable.ts index 7e997477a6..af64873fad 100644 --- a/src/renderer/create-cluster/create-cluster.injectable.ts +++ b/src/renderer/create-cluster/create-cluster.injectable.ts @@ -8,6 +8,7 @@ import { Cluster } from "../../common/cluster/cluster"; import directoryForKubeConfigsInjectable from "../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token"; import loggerInjectable from "../../common/logger.injectable"; +import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable"; const createClusterInjectable = getInjectable({ id: "create-cluster", @@ -16,6 +17,7 @@ const createClusterInjectable = getInjectable({ const dependencies: ClusterDependencies = { directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable), logger: di.inject(loggerInjectable), + broadcastMessage: di.inject(broadcastMessageInjectable), // TODO: Dismantle wrong abstraction // Note: "as never" to get around strictness in unnatural scenario diff --git a/src/renderer/getDiForUnitTesting.tsx b/src/renderer/getDiForUnitTesting.tsx index b139dbb9a9..ddd560674f 100644 --- a/src/renderer/getDiForUnitTesting.tsx +++ b/src/renderer/getDiForUnitTesting.tsx @@ -32,8 +32,6 @@ import { ApiManager } from "../common/k8s-api/api-manager"; import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable"; import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable"; import apiManagerInjectable from "../common/k8s-api/api-manager/manager.injectable"; -import ipcRendererInjectable from "./utils/channel/ipc-renderer.injectable"; -import type { IpcRenderer } from "electron"; import setupOnApiErrorListenersInjectable from "./api/setup-on-api-errors.injectable"; import { observable, computed } from "mobx"; import defaultShellInjectable from "./components/+preferences/default-shell.injectable"; @@ -164,11 +162,6 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) di.override(maximizeWindowInjectable, () => () => {}); di.override(toggleMaximizeWindowInjectable, () => () => {}); - di.override(ipcRendererInjectable, () => ({ - invoke: () => {}, - on: () => {}, - }) as unknown as IpcRenderer); - overrideFunctionalInjectables(di, [ broadcastMessageInjectable, getFilePathsInjectable, diff --git a/src/renderer/ipc/index.ts b/src/renderer/ipc/index.ts index 5e204ef98b..48a005d536 100644 --- a/src/renderer/ipc/index.ts +++ b/src/renderer/ipc/index.ts @@ -4,7 +4,6 @@ */ import type { OpenDialogOptions } from "electron"; -import { ipcRenderer } from "electron"; import { clusterActivateHandler, clusterClearDeletingHandler, clusterDeleteHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterSetDeletingHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster"; import type { ClusterId, ClusterState } from "../../common/cluster-types"; import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel, type WindowAction } from "../../common/ipc/window"; @@ -14,12 +13,22 @@ import type { InstalledExtension } from "../../extensions/extension-discovery/ex import type { LensExtensionId } from "../../extensions/lens-extension"; import { toJS } from "../utils"; import type { Location } from "history"; +import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import ipcRendererInjectable from "../utils/channel/ipc-renderer.injectable"; function requestMain(channel: string, ...args: any[]) { + const di = getLegacyGlobalDiForExtensionApi(); + + const ipcRenderer = di.inject(ipcRendererInjectable); + return ipcRenderer.invoke(channel, ...args.map(toJS)); } function emitToMain(channel: string, ...args: any[]) { + const di = getLegacyGlobalDiForExtensionApi(); + + const ipcRenderer = di.inject(ipcRendererInjectable); + return ipcRenderer.send(channel, ...args.map(toJS)); } diff --git a/src/renderer/kube-watch-api/kube-watch-api.ts b/src/renderer/kube-watch-api/kube-watch-api.ts index e1de15defe..95c0442fcb 100644 --- a/src/renderer/kube-watch-api/kube-watch-api.ts +++ b/src/renderer/kube-watch-api/kube-watch-api.ts @@ -9,10 +9,7 @@ import { once } from "lodash"; import type { ClusterFrameContext } from "../cluster-frame-context/cluster-frame-context"; import logger from "../../common/logger"; import type { KubeObjectStoreLoadAllParams, KubeObjectStoreSubscribeParams } from "../../common/k8s-api/kube-object.store"; -import type { RequestInit } from "node-fetch"; - -// TODO: upgrade node-fetch once we are starting to use ES modules -type LegacyAbortSignal = NonNullable; +import AbortController from "abort-controller"; // Kubernetes watch-api client // API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams @@ -106,7 +103,7 @@ export class KubeWatchApi { const loadThenSubscribe = async (namespaces: string[] | undefined) => { try { - await store.loadAll({ namespaces, reqInit: { signal: childController.signal as LegacyAbortSignal }, onLoadFailure }); + await store.loadAll({ namespaces, reqInit: { signal: childController.signal }, onLoadFailure }); unsubscribe.push(store.subscribe({ onLoadFailure, abortController: childController })); } catch (error) { if (!(error instanceof DOMException)) { diff --git a/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts b/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts index 9aa3df8a0a..a3d85e86db 100644 --- a/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts +++ b/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts @@ -5,8 +5,8 @@ import { getInjectable } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable"; import { LensProtocolRouterRenderer } from "./lens-protocol-router-renderer"; -import extensionsStoreInjectable - from "../../../extensions/extensions-store/extensions-store.injectable"; +import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable"; +import loggerInjectable from "../../../common/logger.injectable"; const lensProtocolRouterRendererInjectable = getInjectable({ id: "lens-protocol-router-renderer", @@ -15,6 +15,7 @@ const lensProtocolRouterRendererInjectable = getInjectable({ new LensProtocolRouterRenderer({ extensionLoader: di.inject(extensionLoaderInjectable), extensionsStore: di.inject(extensionsStoreInjectable), + logger: di.inject(loggerInjectable), }), }); diff --git a/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.tsx b/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.tsx index 5cfeeb3fec..8015678361 100644 --- a/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.tsx +++ b/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.tsx @@ -8,10 +8,9 @@ import { ipcRenderer } from "electron"; import * as proto from "../../../common/protocol-handler"; import Url from "url-parse"; import { onCorrect } from "../../../common/ipc"; +import type { LensProtocolRouterDependencies } from "../../../common/protocol-handler"; import { foldAttemptResults, ProtocolHandlerInvalid, RouteAttempt } from "../../../common/protocol-handler"; import { Notifications } from "../../components/notifications"; -import type { ExtensionLoader } from "../../../extensions/extension-loader"; -import type { ExtensionsStore } from "../../../extensions/extensions-store/extensions-store"; function verifyIpcArgs(args: unknown[]): args is [string, RouteAttempt] { if (args.length !== 2) { @@ -32,11 +31,7 @@ function verifyIpcArgs(args: unknown[]): args is [string, RouteAttempt] { } } -interface Dependencies { - extensionLoader: ExtensionLoader; - extensionsStore: ExtensionsStore; -} - +interface Dependencies extends LensProtocolRouterDependencies {} export class LensProtocolRouterRenderer extends proto.LensProtocolRouter { constructor(protected dependencies: Dependencies) { diff --git a/src/renderer/utils/channel/ipc-renderer.global-override-for-injectable.ts b/src/renderer/utils/channel/ipc-renderer.global-override-for-injectable.ts new file mode 100644 index 0000000000..28043f1299 --- /dev/null +++ b/src/renderer/utils/channel/ipc-renderer.global-override-for-injectable.ts @@ -0,0 +1,12 @@ +/** + * 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 ipcRendererInjectable from "./ipc-renderer.injectable"; +import { getGlobalOverride } from "../../../common/test-utils/get-global-override"; + +export default getGlobalOverride(ipcRendererInjectable, () => ({ + invoke: () => {}, + on: () => {}, +}) as unknown as IpcRenderer); diff --git a/yarn.lock b/yarn.lock index dfdca144c4..5a1a90b3f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876" integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== +"@adobe/css-tools@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd" + integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g== + "@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -598,6 +603,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.53.tgz#251b4cd6760fadb4d68a05815e6dc5e432d69cd6" integrity sha512-W2dAL6Bnyn4xa/QRSU3ilIK4EzD5wgYXKXJiS1HDF5vU3675qc2bvFyLwbUcdmssDveyndy7FbitrCoiV/eMLg== +"@esbuild/linux-loong64@0.15.5": + version "0.15.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.5.tgz#91aef76d332cdc7c8942b600fa2307f3387e6f82" + integrity sha512-UHkDFCfSGTuXq08oQltXxSZmH1TXyWsL+4QhZDWvvLl6mEJQqk3u7/wq1LjhrrAXYIllaTtRSzUXl4Olkf2J8A== + "@eslint/eslintrc@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" @@ -1442,101 +1452,101 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@swc/core-android-arm-eabi@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-android-arm-eabi/-/core-android-arm-eabi-1.2.223.tgz#5d5ae572c90eb2e19f46c1ef56aab2e3fa7101fe" - integrity sha512-Hy/ya4oy80Ay70H9vhA8W0/FU9aQ/oQjvZ/on+wcNMATAiU9tk47i73LtPM01GruNiYJOwFcf2XWjlTpq5a0BQ== +"@swc/core-android-arm-eabi@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-android-arm-eabi/-/core-android-arm-eabi-1.2.242.tgz#3ae5d8b178a0835ae0878094175d943f2d894bec" + integrity sha512-Ukx1LQAUbPRJdREF9FMgeUwIuRtWJNpPyPF7BWl4hIkw024q75mohMbp3S2wgrF1TsSsEGW37q0DkFxPJ2uJbQ== dependencies: "@swc/wasm" "1.2.122" -"@swc/core-android-arm64@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-android-arm64/-/core-android-arm64-1.2.223.tgz#13c1751f9af36525adbb420098a4c74765afa7c4" - integrity sha512-qujrIXDBMWcPcdtTG/r+RNVBU5rg2Sk9Vg+U4FybX3c34rIyX2QYu5sxwM/HIGfd6wCbt5lyFZOvgSY000MTNw== +"@swc/core-android-arm64@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-android-arm64/-/core-android-arm64-1.2.242.tgz#2c1885c08dd5720991a6fa7585d39a93df98e773" + integrity sha512-4E/y+reQWHVCV/0Sn174gsLQyqIKlBWKnwUfPa7MA53VBacp8HTYoPY+iwKPrngsH16gEOC7iByiTJHR/4kirg== dependencies: "@swc/wasm" "1.2.130" -"@swc/core-darwin-arm64@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.223.tgz#44ee2d1fbf9350c6aeda1983a384514272a9b2b7" - integrity sha512-CX32sRhAnFj3fJI6V4vdu5IUV5frEZNZM6hIPUs1UuVpxyuto9IZwd2y7/ACItB5RipA3VDL/c7jrFdSmfrgzg== +"@swc/core-darwin-arm64@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.242.tgz#1b8b16a132cc354ea3b31d26c46908dae2fe41ed" + integrity sha512-nIqtjxdbz0Fe0gFZwCygBwUrGEXj3c4mjHjNeveidVX/6U0HE/EAj+0iXuw8zjJLof8HCMnxq8CzzvhA6gd3ZA== -"@swc/core-darwin-x64@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.2.223.tgz#a157820e158d22c9b443821d15258bc58978e154" - integrity sha512-5FVQgWtqMmpOtky0JLTIF4a1WiAkuDOe5mwgzpi8nZ7nCxNo/DNThBbnIDlNhrR4M/1M6wzPshn1wNivvD7MQw== +"@swc/core-darwin-x64@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.2.242.tgz#cde041d520fcfb0865f49b395bb2c76af8ec3f3a" + integrity sha512-iZKzI76vYYHD/t8wkQ/uIVuIyxN1eift2nLvUU7/jtmoa6b8DH/45ykB/C3vkuvYVNMiGA8HIjJIzw7RJz5XIQ== -"@swc/core-freebsd-x64@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.223.tgz#ec9b300860f378aeb183084d89ba65009910d9f9" - integrity sha512-5oumS+YZyOMMKc5D3Bvf/541SF8n4b8LQ5x4WFA2CdAzD/jCgphE0IoAZ0u3bHz9S6Tl6Emu11V+/ALHE1oUew== +"@swc/core-freebsd-x64@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.242.tgz#a95827311424dd86190fdb73bec996d24188c6d3" + integrity sha512-6JNi5/6JDvcTQzBkndELiIlJufWowoI2ZEmXlGIJpiGoj28PEDPwy5LO7KkXa4DnY5L4CSh15idFO/DxV0rGAQ== dependencies: "@swc/wasm" "1.2.130" -"@swc/core-linux-arm-gnueabihf@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.223.tgz#3403de3c9402e1eeca68b1c134ea574739c7c49c" - integrity sha512-osYjVijq101xZjwPUKR6AUib1LZU9laaM3aEOyElAi8cHolsZEp8D9ynr7cSWFUZJuzpTlY7iuJeY3FszdWrJA== +"@swc/core-linux-arm-gnueabihf@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.242.tgz#1b0bd0ba96c59a9c4b87668521ce8ba55c8c7f55" + integrity sha512-NGL9A3cv8PCbeQ1SvPfApNlHvFbf7Jn305sCAy3iZYsmwm+EU4JNlOWXGgRioP7ABhz2kwLhfYs8UMYCDIVq8Q== dependencies: "@swc/wasm" "1.2.130" -"@swc/core-linux-arm64-gnu@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.223.tgz#d0a2d5dd01c5744950e57ec59aa4696bbabf02e4" - integrity sha512-JZdPZIZzkJ6R+XB0lCnL0eD9VK/JfpZgKBqR3Gur9Fxs8Ea9p1HhZHSEAJ2T2YwV629dYjXwKqraOkLQrEMzCg== +"@swc/core-linux-arm64-gnu@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.242.tgz#f97c1b655779788fff9a035f6a80180b1545423b" + integrity sha512-OJ0kAjgeoRDJlo6Rvd2GnJ92tiIndmC/8krD9gfnQEyAgpR+jajOxbKhyBN/QZPyD2q/TG2LPqxhGYZ79q5mWQ== -"@swc/core-linux-arm64-musl@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.223.tgz#667bd6583f93909736600acdd9d5cc4c6141d577" - integrity sha512-9BVDH5Cq+VlAuJrshCgxWgziLEGzShZ2OVZ7SEA/+md1y69x2VdMR9lMSfD/EXqb6AJAaFODRe20Irtppeqr2Q== +"@swc/core-linux-arm64-musl@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.242.tgz#ad77a5c7fc79d42d64970ca1886eb9d626388ca2" + integrity sha512-VqnHSYb1a6xW5ARUx9kq88s1S3XvCw9TvQXsPcN4e5qsugrLzxWLnqIM6VnWW06prxN7pYlWo9QtrtdPfbppmA== -"@swc/core-linux-x64-gnu@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.223.tgz#c29788110fe1aa7fb86332178dcd1606368fd2c2" - integrity sha512-Z+KAxSpUDNEPfjOKX/tZk67StvzIyAhTc5FPWoVhx5CBlkGQaDBRl1TNmb1wdne/WF9xVkx6wz22pvNevX5fig== +"@swc/core-linux-x64-gnu@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.242.tgz#4c7f2c483876a4b0755a263133c25413fa69df88" + integrity sha512-DDqVJh0KpgHb+E0563+6PqAYDzYTSwgZXF/fOULwlHC7Yt50a9+ecisTFSHkWc74zPMtq27kMTuZyyLeD3gu7A== -"@swc/core-linux-x64-musl@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.223.tgz#24b7001530d4e09d7bae58c704ac79a6593bb9c1" - integrity sha512-3EkAOA0KQdm7Rw/0L5odtDKAtmzhgF7FKTL+zZb+s0ju5oMwFGN+XIIwUQdPSf11Ej3ezjHjHTFTlv0xqutfuA== +"@swc/core-linux-x64-musl@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.242.tgz#48f8769094cfde9d78dc32936666575470583b2a" + integrity sha512-P+9sWgd5eZ6kS1WxOJbCeSgWY7mLP742PhwAzpFrJqCq5nx8Q4FYo4L5mOVNAheYDWldsxR1nKXR1RIMK3S2Lw== -"@swc/core-win32-arm64-msvc@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.223.tgz#8902c20d3d7ed2c9741ff0e227581d26ce8e0497" - integrity sha512-n8LWkej30hvfvazrJgwS6kwBZXMFCevLiRsZmP8O4hpC9b1wfAa+KLm4nHOR+J8jwF7LEjiERdU6tbIWZz0Tnw== +"@swc/core-win32-arm64-msvc@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.242.tgz#e421a5a7a49d1effa71a22fea22a65256b447108" + integrity sha512-W5cevrf5aDJzdE++XeQi1BJKuigC3dlG2NaBUyt3inmep7nli6eoBJdj9Vyg5EPfFOdeI6wQiwOpFvQRoAle8Q== dependencies: "@swc/wasm" "1.2.130" -"@swc/core-win32-ia32-msvc@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.223.tgz#591405f9fa07915e9d0b94a4174a5745621fe580" - integrity sha512-kEDGFFUC6xPqCom03QtR+76Ptwtf8RABI4FqRdvrvbasw9zj0xkuLSDCvqL72zdOZCWRciiFijQVHfndLByMAQ== +"@swc/core-win32-ia32-msvc@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.242.tgz#1b836f5872195fef506a094eae7734b5fd28a1b5" + integrity sha512-XRQcgChvY9333hBre9F53EbiVfVu5MkSH4+XIiNMK14Jg8EqQ1nOcd+jvv2sEdEVbufCmBbWNjofUrCoQey60w== dependencies: "@swc/wasm" "1.2.130" -"@swc/core-win32-x64-msvc@1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.223.tgz#7a4549cedb7c0e7a3d4861c2b5230224194e0f04" - integrity sha512-nzL8rwzMFA9cBK2s+QBMPcNnoGSPMfgY9ypRw/nTp0hQDgdLOXHy9moGFJg8dbdQD39kC5s8yQ0BmyKvePILgg== +"@swc/core-win32-x64-msvc@1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.242.tgz#61a6c7d4da1dec1188b912785bd8e0bb16ff8440" + integrity sha512-Cz1hZOxcfEVgzEr2sYIW9MxT+wEEbYz7aB87ZDmTUpr7vuvBiLMwsYItm8qG847wZeJfa+J7CC+tty5GJOBOOQ== -"@swc/core@^1.2.223": - version "1.2.223" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.2.223.tgz#e44a3f6971a1f5c22d1037cf60510ef32c93d2a5" - integrity sha512-LcKX1frJ1iJDSYlY9Bg0vm0rYsXloITh6PdEYM5amT73J9mC1c2YpWLnWQiH2QpcyblyMhX1pk1eZ2JZjaynrQ== +"@swc/core@^1.2.242": + version "1.2.242" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.2.242.tgz#4392ef0012fe9667440c6eb5a419b6cc86a0a786" + integrity sha512-JQqSYVoLtHtztCNBgeCKyxmqw6AksHsC4WvVSSErLXJx6JXKaog1HFVuzd6rwx2lLCV+zBnbqJFug5OX0g2knw== optionalDependencies: - "@swc/core-android-arm-eabi" "1.2.223" - "@swc/core-android-arm64" "1.2.223" - "@swc/core-darwin-arm64" "1.2.223" - "@swc/core-darwin-x64" "1.2.223" - "@swc/core-freebsd-x64" "1.2.223" - "@swc/core-linux-arm-gnueabihf" "1.2.223" - "@swc/core-linux-arm64-gnu" "1.2.223" - "@swc/core-linux-arm64-musl" "1.2.223" - "@swc/core-linux-x64-gnu" "1.2.223" - "@swc/core-linux-x64-musl" "1.2.223" - "@swc/core-win32-arm64-msvc" "1.2.223" - "@swc/core-win32-ia32-msvc" "1.2.223" - "@swc/core-win32-x64-msvc" "1.2.223" + "@swc/core-android-arm-eabi" "1.2.242" + "@swc/core-android-arm64" "1.2.242" + "@swc/core-darwin-arm64" "1.2.242" + "@swc/core-darwin-x64" "1.2.242" + "@swc/core-freebsd-x64" "1.2.242" + "@swc/core-linux-arm-gnueabihf" "1.2.242" + "@swc/core-linux-arm64-gnu" "1.2.242" + "@swc/core-linux-arm64-musl" "1.2.242" + "@swc/core-linux-x64-gnu" "1.2.242" + "@swc/core-linux-x64-musl" "1.2.242" + "@swc/core-win32-arm64-msvc" "1.2.242" + "@swc/core-win32-ia32-msvc" "1.2.242" + "@swc/core-win32-x64-msvc" "1.2.242" "@swc/jest@^0.2.22": version "0.2.22" @@ -1621,16 +1631,16 @@ lz-string "^1.4.4" pretty-format "^26.6.2" -"@testing-library/jest-dom@^5.16.4": - version "5.16.4" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd" - integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA== +"@testing-library/jest-dom@^5.16.5": + version "5.16.5" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" + integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== dependencies: + "@adobe/css-tools" "^4.0.1" "@babel/runtime" "^7.9.2" "@types/testing-library__jest-dom" "^5.9.1" aria-query "^5.0.0" chalk "^3.0.0" - css "^3.0.0" css.escape "^1.5.1" dom-accessibility-api "^0.5.6" lodash "^4.17.15" @@ -2063,10 +2073,10 @@ dependencies: "@types/node" "*" -"@types/lodash@^4.14.181": - version "4.14.182" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" - integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== +"@types/lodash@^4.14.184": + version "4.14.184" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.184.tgz#23f96cd2a21a28e106dc24d825d4aa966de7a9fe" + integrity sha512-RoZphVtHbxPZizt4IcILciSWiC6dcn+eZ8oX9IWEYfDMcocdd42f7NPI6fQj+6zI8y4E0L7gu2pcZKLGTRaV9Q== "@types/marked@^4.0.3": version "4.0.3" @@ -2139,15 +2149,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/node@^16.11.26": - version "16.11.34" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.34.tgz#520224e4be4448c279ecad09639ab460cc441a50" - integrity sha512-UrWGDyLAlQ2Z8bNOGWTsqbP9ZcBeTYBVuTRNxXTztBy5KhWUFI3BaeDWoCP/CzV/EVGgO1NTYzv9ZytBI9GAEw== - -"@types/node@^16.11.47": - version "16.11.47" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.47.tgz#efa9e3e0f72e7aa6a138055dace7437a83d9f91c" - integrity sha512-fpP+jk2zJ4VW66+wAMFoBJlx1bxmBKx4DUFf68UHgdGCOuyUTDlLWqsaNPJh7xhNDykyJ9eIzAygilP/4WoN8g== +"@types/node@^16.11.26", "@types/node@^16.11.55": + version "16.11.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.55.tgz#4b1e4fa4238b083cf0d0b4ad9077629123950caa" + integrity sha512-ZZepNkqPNCBy6PlCjeOY0gI1q91v7l5MUhVc5RMAUV39OxRO8lF8fqGnhY2j8FWz8fxcN8HvAUWoccWpOzl/Ug== "@types/npm@^2.0.32": version "2.0.32" @@ -2492,10 +2497,10 @@ dependencies: webpack-dev-server "*" -"@types/webpack-env@^1.17.0": - version "1.17.0" - resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.17.0.tgz#f99ce359f1bfd87da90cc4a57cab0a18f34a48d0" - integrity sha512-eHSaNYEyxRA5IAG0Ym/yCyf86niZUIF/TpWKofQI/CVfh5HsMEUyfE2kwFxha4ow0s5g0LfISQxpDKjbRDrizw== +"@types/webpack-env@^1.18.0": + version "1.18.0" + resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.18.0.tgz#ed6ecaa8e5ed5dfe8b2b3d00181702c9925f13fb" + integrity sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg== "@types/webpack-node-externals@^2.5.3": version "2.5.3" @@ -2575,14 +2580,14 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.32.0": - version "5.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.32.0.tgz#e27e38cffa4a61226327c874a7be965e9a861624" - integrity sha512-CHLuz5Uz7bHP2WgVlvoZGhf0BvFakBJKAD/43Ty0emn4wXWv5k01ND0C0fHcl/Im8Td2y/7h44E9pca9qAu2ew== +"@typescript-eslint/eslint-plugin@^5.34.0": + version "5.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.34.0.tgz#d690f60e335596f38b01792e8f4b361d9bd0cb35" + integrity sha512-eRfPPcasO39iwjlUAMtjeueRGuIrW3TQ9WseIDl7i5UWuFbf83yYaU7YPs4j8+4CxUMIsj1k+4kV+E+G+6ypDQ== dependencies: - "@typescript-eslint/scope-manager" "5.32.0" - "@typescript-eslint/type-utils" "5.32.0" - "@typescript-eslint/utils" "5.32.0" + "@typescript-eslint/scope-manager" "5.34.0" + "@typescript-eslint/type-utils" "5.34.0" + "@typescript-eslint/utils" "5.34.0" debug "^4.3.4" functional-red-black-tree "^1.0.1" ignore "^5.2.0" @@ -2608,20 +2613,20 @@ "@typescript-eslint/types" "5.31.0" "@typescript-eslint/visitor-keys" "5.31.0" -"@typescript-eslint/scope-manager@5.32.0": - version "5.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.32.0.tgz#763386e963a8def470580cc36cf9228864190b95" - integrity sha512-KyAE+tUON0D7tNz92p1uetRqVJiiAkeluvwvZOqBmW9z2XApmk5WSMV9FrzOroAcVxJZB3GfUwVKr98Dr/OjOg== +"@typescript-eslint/scope-manager@5.34.0": + version "5.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.34.0.tgz#14efd13dc57602937e25f188fd911f118781e527" + integrity sha512-HNvASMQlah5RsBW6L6c7IJ0vsm+8Sope/wu5sEAf7joJYWNb1LDbJipzmdhdUOnfrDFE6LR1j57x1EYVxrY4ow== dependencies: - "@typescript-eslint/types" "5.32.0" - "@typescript-eslint/visitor-keys" "5.32.0" + "@typescript-eslint/types" "5.34.0" + "@typescript-eslint/visitor-keys" "5.34.0" -"@typescript-eslint/type-utils@5.32.0": - version "5.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.32.0.tgz#45a14506fe3fb908600b4cef2f70778f7b5cdc79" - integrity sha512-0gSsIhFDduBz3QcHJIp3qRCvVYbqzHg8D6bHFsDMrm0rURYDj+skBK2zmYebdCp+4nrd9VWd13egvhYFJj/wZg== +"@typescript-eslint/type-utils@5.34.0": + version "5.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.34.0.tgz#7a324ab9ddd102cd5e1beefc94eea6f3eb32d32d" + integrity sha512-Pxlno9bjsQ7hs1pdWRUv9aJijGYPYsHpwMeCQ/Inavhym3/XaKt1ZKAA8FIw4odTBfowBdZJDMxf2aavyMDkLg== dependencies: - "@typescript-eslint/utils" "5.32.0" + "@typescript-eslint/utils" "5.34.0" debug "^4.3.4" tsutils "^3.21.0" @@ -2630,10 +2635,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.31.0.tgz#7aa389122b64b18e473c1672fb3b8310e5f07a9a" integrity sha512-/f/rMaEseux+I4wmR6mfpM2wvtNZb1p9hAV77hWfuKc3pmaANp5dLAZSiE3/8oXTYTt3uV9KW5yZKJsMievp6g== -"@typescript-eslint/types@5.32.0": - version "5.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.32.0.tgz#484273021eeeae87ddb288f39586ef5efeb6dcd8" - integrity sha512-EBUKs68DOcT/EjGfzywp+f8wG9Zw6gj6BjWu7KV/IYllqKJFPlZlLSYw/PTvVyiRw50t6wVbgv4p9uE2h6sZrQ== +"@typescript-eslint/types@5.34.0": + version "5.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.34.0.tgz#217bf08049e9e7b86694d982e88a2c1566330c78" + integrity sha512-49fm3xbbUPuzBIOcy2CDpYWqy/X7VBkxVN+DC21e0zIm3+61Z0NZi6J9mqPmSW1BDVk9FIOvuCFyUPjXz93sjA== "@typescript-eslint/typescript-estree@5.31.0": version "5.31.0" @@ -2648,28 +2653,28 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@5.32.0": - version "5.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.32.0.tgz#282943f34babf07a4afa7b0ff347a8e7b6030d12" - integrity sha512-ZVAUkvPk3ITGtCLU5J4atCw9RTxK+SRc6hXqLtllC2sGSeMFWN+YwbiJR9CFrSFJ3w4SJfcWtDwNb/DmUIHdhg== +"@typescript-eslint/typescript-estree@5.34.0": + version "5.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.34.0.tgz#ba7b83f4bf8ccbabf074bbf1baca7a58de3ccb9a" + integrity sha512-mXHAqapJJDVzxauEkfJI96j3D10sd567LlqroyCeJaHnu42sDbjxotGb3XFtGPYKPD9IyLjhsoULML1oI3M86A== dependencies: - "@typescript-eslint/types" "5.32.0" - "@typescript-eslint/visitor-keys" "5.32.0" + "@typescript-eslint/types" "5.34.0" + "@typescript-eslint/visitor-keys" "5.34.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.32.0": - version "5.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.32.0.tgz#eccb6b672b94516f1afc6508d05173c45924840c" - integrity sha512-W7lYIAI5Zlc5K082dGR27Fczjb3Q57ECcXefKU/f0ajM5ToM0P+N9NmJWip8GmGu/g6QISNT+K6KYB+iSHjXCQ== +"@typescript-eslint/utils@5.34.0": + version "5.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.34.0.tgz#0cae98f48d8f9e292e5caa9343611b6faf49e743" + integrity sha512-kWRYybU4Rn++7lm9yu8pbuydRyQsHRoBDIo11k7eqBWTldN4xUdVUMCsHBiE7aoEkFzrUEaZy3iH477vr4xHAQ== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.32.0" - "@typescript-eslint/types" "5.32.0" - "@typescript-eslint/typescript-estree" "5.32.0" + "@typescript-eslint/scope-manager" "5.34.0" + "@typescript-eslint/types" "5.34.0" + "@typescript-eslint/typescript-estree" "5.34.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" @@ -2681,12 +2686,12 @@ "@typescript-eslint/types" "5.31.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@5.32.0": - version "5.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.32.0.tgz#b9715d0b11fdb5dd10fd0c42ff13987470525394" - integrity sha512-S54xOHZgfThiZ38/ZGTgB2rqx51CMJ5MCfVT2IplK4Q7hgzGfe0nLzLCcenDnc/cSjP568hdeKfeDcBgqNHD/g== +"@typescript-eslint/visitor-keys@5.34.0": + version "5.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.34.0.tgz#d0fb3e31033e82ddd5de048371ad39eb342b2d40" + integrity sha512-O1moYjOSrab0a2fUvFpsJe0QHtvTC+cR+ovYpgKrAVXzqQyc74mv76TgY6z+aEtjQE2vgZux3CQVtGryqdcOAw== dependencies: - "@typescript-eslint/types" "5.32.0" + "@typescript-eslint/types" "5.34.0" eslint-visitor-keys "^3.3.0" "@webassemblyjs/ast@1.11.1": @@ -2855,6 +2860,13 @@ abbrev@1, abbrev@~1.1.1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + accepts@~1.3.4, accepts@~1.3.5: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -4695,15 +4707,6 @@ css@^2.0.0: source-map-resolve "^0.5.2" urix "^0.1.0" -css@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" - integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== - dependencies: - inherits "^2.0.4" - source-map "^0.6.1" - source-map-resolve "^0.6.0" - cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -5348,10 +5351,10 @@ electron-window-state@^5.0.3: jsonfile "^4.0.0" mkdirp "^0.5.1" -electron@^19.0.4: - version "19.0.4" - resolved "https://registry.yarnpkg.com/electron/-/electron-19.0.4.tgz#a88d5e542868c4abd7704228ec62c553605605a0" - integrity sha512-roRYr1VNAWIhjD9n8qZdmhROtrzsFpuZEXrjWAw+GqPbZlrUInmvFCviRDC2Lt+VBsTNRpTfPpfzXSlLL4reEw== +electron@^19.0.13: + version "19.0.13" + resolved "https://registry.yarnpkg.com/electron/-/electron-19.0.13.tgz#68bcf7d94f249dbae9a3d4d1794d45a24db666dc" + integrity sha512-11Ne0VJy8L1GU7sGcbJHhkAz73szR27uP4vmfUVGlppC/ipA39AUkdzqiQoPC/F1EJdjEOBvHySG8K8Xe9yETA== dependencies: "@electron/get" "^1.14.1" "@types/node" "^16.11.26" @@ -5537,71 +5540,141 @@ esbuild-android-64@0.14.53: resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.53.tgz#259bc3ef1399a3cad8f4f67c40ee20779c4de675" integrity sha512-fIL93sOTnEU+NrTAVMIKiAw0YH22HWCAgg4N4Z6zov2t0kY9RAJ50zY9ZMCQ+RT6bnOfDt8gCTnt/RaSNA2yRA== +esbuild-android-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.5.tgz#3c7b2f2a59017dab3f2c0356188a8dd9cbdc91c8" + integrity sha512-dYPPkiGNskvZqmIK29OPxolyY3tp+c47+Fsc2WYSOVjEPWNCHNyqhtFqQadcXMJDQt8eN0NMDukbyQgFcHquXg== + esbuild-android-arm64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.53.tgz#2158253d4e8f9fdd2a081bbb4f73b8806178841e" integrity sha512-PC7KaF1v0h/nWpvlU1UMN7dzB54cBH8qSsm7S9mkwFA1BXpaEOufCg8hdoEI1jep0KeO/rjZVWrsH8+q28T77A== +esbuild-android-arm64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.5.tgz#e301db818c5a67b786bf3bb7320e414ac0fcf193" + integrity sha512-YyEkaQl08ze3cBzI/4Cm1S+rVh8HMOpCdq8B78JLbNFHhzi4NixVN93xDrHZLztlocEYqi45rHHCgA8kZFidFg== + esbuild-darwin-64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.53.tgz#b4681831fd8f8d06feb5048acbe90d742074cc2a" integrity sha512-gE7P5wlnkX4d4PKvLBUgmhZXvL7lzGRLri17/+CmmCzfncIgq8lOBvxGMiQ4xazplhxq+72TEohyFMZLFxuWvg== +esbuild-darwin-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.5.tgz#11726de5d0bf5960b92421ef433e35871c091f8d" + integrity sha512-Cr0iIqnWKx3ZTvDUAzG0H/u9dWjLE4c2gTtRLz4pqOBGjfjqdcZSfAObFzKTInLLSmD0ZV1I/mshhPoYSBMMCQ== + esbuild-darwin-arm64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.53.tgz#d267d957852d121b261b3f76ead86e5b5463acc9" integrity sha512-otJwDU3hnI15Q98PX4MJbknSZ/WSR1I45il7gcxcECXzfN4Mrpft5hBDHXNRnCh+5858uPXBXA1Vaz2jVWLaIA== +esbuild-darwin-arm64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.5.tgz#ad89dafebb3613fd374f5a245bb0ce4132413997" + integrity sha512-WIfQkocGtFrz7vCu44ypY5YmiFXpsxvz2xqwe688jFfSVCnUsCn2qkEVDo7gT8EpsLOz1J/OmqjExePL1dr1Kg== + esbuild-freebsd-64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.53.tgz#aca2af6d72b537fe66a38eb8f374fb66d4c98ca0" integrity sha512-WkdJa8iyrGHyKiPF4lk0MiOF87Q2SkE+i+8D4Cazq3/iqmGPJ6u49je300MFi5I2eUsQCkaOWhpCVQMTKGww2w== +esbuild-freebsd-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.5.tgz#6bfb52b4a0d29c965aa833e04126e95173289c8a" + integrity sha512-M5/EfzV2RsMd/wqwR18CELcenZ8+fFxQAAEO7TJKDmP3knhWSbD72ILzrXFMMwshlPAS1ShCZ90jsxkm+8FlaA== + esbuild-freebsd-arm64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.53.tgz#76282e19312d914c34343c8a7da6cc5f051580b9" integrity sha512-9T7WwCuV30NAx0SyQpw8edbKvbKELnnm1FHg7gbSYaatH+c8WJW10g/OdM7JYnv7qkimw2ZTtSA+NokOLd2ydQ== +esbuild-freebsd-arm64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.5.tgz#38a3fed8c6398072f9914856c7c3e3444f9ef4dd" + integrity sha512-2JQQ5Qs9J0440F/n/aUBNvY6lTo4XP/4lt1TwDfHuo0DY3w5++anw+jTjfouLzbJmFFiwmX7SmUhMnysocx96w== + esbuild-linux-32@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.53.tgz#1045d34cf7c5faaf2af3b29cc1573b06580c37e5" integrity sha512-VGanLBg5en2LfGDgLEUxQko2lqsOS7MTEWUi8x91YmsHNyzJVT/WApbFFx3MQGhkf+XdimVhpyo5/G0PBY91zg== +esbuild-linux-32@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.5.tgz#942dc70127f0c0a7ea91111baf2806e61fc81b32" + integrity sha512-gO9vNnIN0FTUGjvTFucIXtBSr1Woymmx/aHQtuU+2OllGU6YFLs99960UD4Dib1kFovVgs59MTXwpFdVoSMZoQ== + esbuild-linux-64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.53.tgz#ab3f2ee2ebb5a6930c72d9539cb34b428808cbe4" integrity sha512-pP/FA55j/fzAV7N9DF31meAyjOH6Bjuo3aSKPh26+RW85ZEtbJv9nhoxmGTd9FOqjx59Tc1ZbrJabuiXlMwuZQ== +esbuild-linux-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.5.tgz#6d748564492d5daaa7e62420862c31ac3a44aed9" + integrity sha512-ne0GFdNLsm4veXbTnYAWjbx3shpNKZJUd6XpNbKNUZaNllDZfYQt0/zRqOg0sc7O8GQ+PjSMv9IpIEULXVTVmg== + esbuild-linux-arm64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.53.tgz#1f5530412f6690949e78297122350488d3266cfe" integrity sha512-GDmWITT+PMsjCA6/lByYk7NyFssW4Q6in32iPkpjZ/ytSyH+xeEx8q7HG3AhWH6heemEYEWpTll/eui3jwlSnw== +esbuild-linux-arm64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.5.tgz#28cd899beb2d2b0a3870fd44f4526835089a318d" + integrity sha512-7EgFyP2zjO065XTfdCxiXVEk+f83RQ1JsryN1X/VSX2li9rnHAt2swRbpoz5Vlrl6qjHrCmq5b6yxD13z6RheA== + esbuild-linux-arm@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.53.tgz#a44ec9b5b42007ab6c0d65a224ccc6bbd97c54cf" integrity sha512-/u81NGAVZMopbmzd21Nu/wvnKQK3pT4CrvQ8BTje1STXcQAGnfyKgQlj3m0j2BzYbvQxSy+TMck4TNV2onvoPA== +esbuild-linux-arm@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.5.tgz#6441c256225564d8794fdef5b0a69bc1a43051b5" + integrity sha512-wvAoHEN+gJ/22gnvhZnS/+2H14HyAxM07m59RSLn3iXrQsdS518jnEWRBnJz3fR6BJa+VUTo0NxYjGaNt7RA7Q== + esbuild-linux-mips64le@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.53.tgz#a4d0b6b17cfdeea4e41b0b085a5f73d99311be9f" integrity sha512-d6/XHIQW714gSSp6tOOX2UscedVobELvQlPMkInhx1NPz4ThZI9uNLQ4qQJHGBGKGfu+rtJsxM4NVHLhnNRdWQ== +esbuild-linux-mips64le@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.5.tgz#d4927f817290eaffc062446896b2a553f0e11981" + integrity sha512-KdnSkHxWrJ6Y40ABu+ipTZeRhFtc8dowGyFsZY5prsmMSr1ZTG9zQawguN4/tunJ0wy3+kD54GaGwdcpwWAvZQ== + esbuild-linux-ppc64le@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.53.tgz#8c331822c85465434e086e3e6065863770c38139" integrity sha512-ndnJmniKPCB52m+r6BtHHLAOXw+xBCWIxNnedbIpuREOcbSU/AlyM/2dA3BmUQhsHdb4w3amD5U2s91TJ3MzzA== +esbuild-linux-ppc64le@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.5.tgz#b6d660dc6d5295f89ac51c675f1a2f639e2fb474" + integrity sha512-QdRHGeZ2ykl5P0KRmfGBZIHmqcwIsUKWmmpZTOq573jRWwmpfRmS7xOhmDHBj9pxv+6qRMH8tLr2fe+ZKQvCYw== + esbuild-linux-riscv64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.53.tgz#36fd75543401304bea8a2d63bf8ea18aaa508e00" integrity sha512-yG2sVH+QSix6ct4lIzJj329iJF3MhloLE6/vKMQAAd26UVPVkhMFqFopY+9kCgYsdeWvXdPgmyOuKa48Y7+/EQ== +esbuild-linux-riscv64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.5.tgz#2801bf18414dc3d3ad58d1ea83084f00d9d84896" + integrity sha512-p+WE6RX+jNILsf+exR29DwgV6B73khEQV0qWUbzxaycxawZ8NE0wA6HnnTxbiw5f4Gx9sJDUBemh9v49lKOORA== + esbuild-linux-s390x@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.53.tgz#1622677ab6824123f48f75d3afc031cd41936129" integrity sha512-OCJlgdkB+XPYndHmw6uZT7jcYgzmx9K+28PVdOa/eLjdoYkeAFvH5hTwX4AXGLZLH09tpl4bVsEtvuyUldaNCg== +esbuild-linux-s390x@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.5.tgz#12a634ae6d3384cacc2b8f4201047deafe596eae" + integrity sha512-J2ngOB4cNzmqLHh6TYMM/ips8aoZIuzxJnDdWutBw5482jGXiOzsPoEF4j2WJ2mGnm7FBCO4StGcwzOgic70JQ== + esbuild-loader@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/esbuild-loader/-/esbuild-loader-2.19.0.tgz#54f62d1da8262acfc3c5883c24da35af8324f116" @@ -5619,32 +5692,62 @@ esbuild-netbsd-64@0.14.53: resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.53.tgz#e86d0efd0116658be335492ed12e66b26b4baf52" integrity sha512-gp2SB+Efc7MhMdWV2+pmIs/Ja/Mi5rjw+wlDmmbIn68VGXBleNgiEZG+eV2SRS0kJEUyHNedDtwRIMzaohWedQ== +esbuild-netbsd-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.5.tgz#951bbf87600512dfcfbe3b8d9d117d684d26c1b8" + integrity sha512-MmKUYGDizYjFia0Rwt8oOgmiFH7zaYlsoQ3tIOfPxOqLssAsEgG0MUdRDm5lliqjiuoog8LyDu9srQk5YwWF3w== + esbuild-openbsd-64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.53.tgz#9bcbbe6f86304872c6e91f64c8eb73fc29c3588b" integrity sha512-eKQ30ZWe+WTZmteDYg8S+YjHV5s4iTxeSGhJKJajFfQx9TLZJvsJX0/paqwP51GicOUruFpSUAs2NCc0a4ivQQ== +esbuild-openbsd-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.5.tgz#26705b61961d525d79a772232e8b8f211fdbb035" + integrity sha512-2mMFfkLk3oPWfopA9Plj4hyhqHNuGyp5KQyTT9Rc8hFd8wAn5ZrbJg+gNcLMo2yzf8Uiu0RT6G9B15YN9WQyMA== + esbuild-sunos-64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.53.tgz#f7a872f7460bfb7b131f7188a95fbce3d1c577e8" integrity sha512-OWLpS7a2FrIRukQqcgQqR1XKn0jSJoOdT+RlhAxUoEQM/IpytS3FXzCJM6xjUYtpO5GMY0EdZJp+ur2pYdm39g== +esbuild-sunos-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.5.tgz#d794da1ae60e6e2f6194c44d7b3c66bf66c7a141" + integrity sha512-2sIzhMUfLNoD+rdmV6AacilCHSxZIoGAU2oT7XmJ0lXcZWnCvCtObvO6D4puxX9YRE97GodciRGDLBaiC6x1SA== + esbuild-windows-32@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.53.tgz#c5e3ca50e2d1439cc2c9fe4defa63bcd474ce709" integrity sha512-m14XyWQP5rwGW0tbEfp95U6A0wY0DYPInWBB7D69FAXUpBpBObRoGTKRv36lf2RWOdE4YO3TNvj37zhXjVL5xg== +esbuild-windows-32@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.5.tgz#0670326903f421424be86bc03b7f7b3ff86a9db7" + integrity sha512-e+duNED9UBop7Vnlap6XKedA/53lIi12xv2ebeNS4gFmu7aKyTrok7DPIZyU5w/ftHD4MUDs5PJUkQPP9xJRzg== + esbuild-windows-64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.53.tgz#ec2ab4a60c5215f092ffe1eab6d01319e88238af" integrity sha512-s9skQFF0I7zqnQ2K8S1xdLSfZFsPLuOGmSx57h2btSEswv0N0YodYvqLcJMrNMXh6EynOmWD7rz+0rWWbFpIHQ== +esbuild-windows-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.5.tgz#64f32acb7341f3f0a4d10e8ff1998c2d1ebfc0a9" + integrity sha512-v+PjvNtSASHOjPDMIai9Yi+aP+Vwox+3WVdg2JB8N9aivJ7lyhp4NVU+J0MV2OkWFPnVO8AE/7xH+72ibUUEnw== + esbuild-windows-arm64@0.14.53: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.53.tgz#f71d403806bdf9f4a1f9d097db9aec949bd675c8" integrity sha512-E+5Gvb+ZWts+00T9II6wp2L3KG2r3iGxByqd/a1RmLmYWVsSVUjkvIxZuJ3hYTIbhLkH5PRwpldGTKYqVz0nzQ== -esbuild@^0.14.39, esbuild@^0.14.53: +esbuild-windows-arm64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.5.tgz#4fe7f333ce22a922906b10233c62171673a3854b" + integrity sha512-Yz8w/D8CUPYstvVQujByu6mlf48lKmXkq6bkeSZZxTA626efQOJb26aDGLzmFWx6eg/FwrXgt6SZs9V8Pwy/aA== + +esbuild@^0.14.39: version "0.14.53" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.53.tgz#20b1007f686e8584f2a01a1bec5a37aac9498ce4" integrity sha512-ohO33pUBQ64q6mmheX1mZ8mIXj8ivQY/L4oVuAshr+aJI+zLl+amrp3EodrUNDNYVrKJXGPfIHFGhO8slGRjuw== @@ -5671,6 +5774,33 @@ esbuild@^0.14.39, esbuild@^0.14.53: esbuild-windows-64 "0.14.53" esbuild-windows-arm64 "0.14.53" +esbuild@^0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.5.tgz#5effd05666f621d4ff2fe2c76a67c198292193ff" + integrity sha512-VSf6S1QVqvxfIsSKb3UKr3VhUCis7wgDbtF4Vd9z84UJr05/Sp2fRKmzC+CSPG/dNAPPJZ0BTBLTT1Fhd6N9Gg== + optionalDependencies: + "@esbuild/linux-loong64" "0.15.5" + esbuild-android-64 "0.15.5" + esbuild-android-arm64 "0.15.5" + esbuild-darwin-64 "0.15.5" + esbuild-darwin-arm64 "0.15.5" + esbuild-freebsd-64 "0.15.5" + esbuild-freebsd-arm64 "0.15.5" + esbuild-linux-32 "0.15.5" + esbuild-linux-64 "0.15.5" + esbuild-linux-arm "0.15.5" + esbuild-linux-arm64 "0.15.5" + esbuild-linux-mips64le "0.15.5" + esbuild-linux-ppc64le "0.15.5" + esbuild-linux-riscv64 "0.15.5" + esbuild-linux-s390x "0.15.5" + esbuild-netbsd-64 "0.15.5" + esbuild-openbsd-64 "0.15.5" + esbuild-sunos-64 "0.15.5" + esbuild-windows-32 "0.15.5" + esbuild-windows-64 "0.15.5" + esbuild-windows-arm64 "0.15.5" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -5933,6 +6063,11 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + eventemitter3@^4.0.0: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -11961,10 +12096,10 @@ sass-loader@^12.6.0: klona "^2.0.4" neo-async "^2.6.2" -sass@^1.32.13, sass@^1.54.2: - version "1.54.2" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.2.tgz#574cad83814c930ef2475921b9cb5d8203ae8867" - integrity sha512-wbVV26sejsCIbBScZZtNkvnrB/bVCQ8hSlZ01D9nzsVh9zLqCkWrlpvTb3YEb6xsuNi9cx75hncqwikHFSz7tw== +sass@^1.32.13, sass@^1.54.5: + version "1.54.5" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.5.tgz#93708f5560784f6ff2eab8542ade021a4a947b3a" + integrity sha512-p7DTOzxkUPa/63FU0R3KApkRHwcVZYC0PLnLm5iyZACyp15qSi32x7zVUhRdABAATmkALqgGrjCJAcWvobmhHw== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -12394,14 +12529,6 @@ source-map-resolve@^0.5.2: source-map-url "^0.4.0" urix "^0.1.0" -source-map-resolve@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" - integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -13835,10 +13962,10 @@ webpack-dev-middleware@^5.3.1: range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@*, webpack-dev-server@^4.9.3: - version "4.9.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz#2360a5d6d532acb5410a668417ad549ee3b8a3c9" - integrity sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw== +webpack-dev-server@*, webpack-dev-server@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.10.0.tgz#de270d0009eba050546912be90116e7fd740a9ca" + integrity sha512-7dezwAs+k6yXVFZ+MaL8VnE+APobiO3zvpp3rBHe/HmWQ+avwh0Q3d0xxacOiBybZZ3syTZw9HXzpa3YNbAZDQ== dependencies: "@types/bonjour" "^3.5.9" "@types/connect-history-api-fallback" "^1.3.5"