1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge branch 'master' into react-table-with-resizable-columns

This commit is contained in:
Alex Andreev 2022-08-25 08:15:46 +03:00
commit d1e2f0cae1
68 changed files with 1131 additions and 796 deletions

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-18.04, macos-11, windows-2019] os: [ubuntu-20.04, macos-11, windows-2019]
node-version: [16.x] node-version: [16.x]
steps: steps:
- name: Checkout Release from lens - name: Checkout Release from lens

View File

@ -227,6 +227,7 @@
"@tanstack/react-table": "^8.5.11", "@tanstack/react-table": "^8.5.11",
"@tanstack/react-virtual": "3.0.0-beta.18", "@tanstack/react-virtual": "3.0.0-beta.18",
"@types/circular-dependency-plugin": "5.0.5", "@types/circular-dependency-plugin": "5.0.5",
"abort-controller": "^3.0.0",
"auto-bind": "^4.0.0", "auto-bind": "^4.0.0",
"await-lock": "^2.2.2", "await-lock": "^2.2.2",
"byline": "^5.0.0", "byline": "^5.0.0",
@ -300,10 +301,10 @@
"@material-ui/lab": "^4.0.0-alpha.60", "@material-ui/lab": "^4.0.0-alpha.60",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
"@sentry/types": "^6.19.7", "@sentry/types": "^6.19.7",
"@swc/core": "^1.2.223", "@swc/core": "^1.2.242",
"@swc/jest": "^0.2.22", "@swc/jest": "^0.2.22",
"@testing-library/dom": "^7.31.2", "@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/react": "^12.1.5",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/byline": "^4.2.33", "@types/byline": "^4.2.33",
@ -325,12 +326,12 @@
"@types/jest": "^28.1.6", "@types/jest": "^28.1.6",
"@types/js-yaml": "^4.0.5", "@types/js-yaml": "^4.0.5",
"@types/jsdom": "^16.2.14", "@types/jsdom": "^16.2.14",
"@types/lodash": "^4.14.181", "@types/lodash": "^4.14.184",
"@types/marked": "^4.0.3", "@types/marked": "^4.0.3",
"@types/md5-file": "^4.0.2", "@types/md5-file": "^4.0.2",
"@types/mini-css-extract-plugin": "^2.4.0", "@types/mini-css-extract-plugin": "^2.4.0",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "^16.11.47", "@types/node": "^16.11.55",
"@types/node-fetch": "^2.6.2", "@types/node-fetch": "^2.6.2",
"@types/npm": "^2.0.32", "@types/npm": "^2.0.32",
"@types/proper-lockfile": "^4.1.2", "@types/proper-lockfile": "^4.1.2",
@ -357,9 +358,9 @@
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.0",
"@types/webpack-dev-server": "^4.7.2", "@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", "@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", "@typescript-eslint/parser": "^5.31.0",
"adr": "^1.4.1", "adr": "^1.4.1",
"ansi_up": "^5.1.0", "ansi_up": "^5.1.0",
@ -372,10 +373,10 @@
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"deepdash": "^5.3.9", "deepdash": "^5.3.9",
"dompurify": "^2.3.10", "dompurify": "^2.3.10",
"electron": "^19.0.4", "electron": "^19.0.13",
"electron-builder": "^23.3.3", "electron-builder": "^23.3.3",
"electron-notarize": "^0.3.0", "electron-notarize": "^0.3.0",
"esbuild": "^0.14.53", "esbuild": "^0.15.5",
"esbuild-loader": "^2.19.0", "esbuild-loader": "^2.19.0",
"eslint": "^8.21.0", "eslint": "^8.21.0",
"eslint-plugin-header": "^3.1.1", "eslint-plugin-header": "^3.1.1",
@ -412,7 +413,7 @@
"react-select": "^5.4.0", "react-select": "^5.4.0",
"react-select-event": "^5.5.1", "react-select-event": "^5.5.1",
"react-window": "^1.8.7", "react-window": "^1.8.7",
"sass": "^1.54.2", "sass": "^1.54.5",
"sass-loader": "^12.6.0", "sass-loader": "^12.6.0",
"sharp": "^0.30.7", "sharp": "^0.30.7",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
@ -428,7 +429,7 @@
"typescript-plugin-css-modules": "^3.4.0", "typescript-plugin-css-modules": "^3.4.0",
"webpack": "^5.74.0", "webpack": "^5.74.0",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.3", "webpack-dev-server": "^4.10.0",
"webpack-node-externals": "^3.0.0", "webpack-node-externals": "^3.0.0",
"xterm": "^4.19.0", "xterm": "^4.19.0",
"xterm-addon-fit": "^0.5.0" "xterm-addon-fit": "^0.5.0"

View File

@ -4,7 +4,6 @@
*/ */
import { action, comparer, computed, makeObservable, observable, reaction, when } from "mobx"; 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 { ClusterContextHandler } from "../../main/context-handler/context-handler";
import type { KubeConfig } from "@kubernetes/client-node"; import type { KubeConfig } from "@kubernetes/client-node";
import { HttpError } 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 type { ListNamespaces } from "./list-namespaces.injectable";
import assert from "assert"; import assert from "assert";
import type { Logger } from "../logger"; import type { Logger } from "../logger";
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
export interface ClusterDependencies { export interface ClusterDependencies {
readonly directoryForKubeConfigs: string; readonly directoryForKubeConfigs: string;
@ -36,6 +36,7 @@ export interface ClusterDependencies {
createAuthorizationReview: (config: KubeConfig) => CanI; createAuthorizationReview: (config: KubeConfig) => CanI;
createListNamespaces: (config: KubeConfig) => ListNamespaces; createListNamespaces: (config: KubeConfig) => ListNamespaces;
createVersionDetector: (cluster: Cluster) => VersionDetector; createVersionDetector: (cluster: Cluster) => VersionDetector;
broadcastMessage: BroadcastMessage;
} }
/** /**
@ -602,7 +603,7 @@ export class Cluster implements ClusterModel, ClusterState {
*/ */
pushState(state = this.getState()) { pushState(state = this.getState()) {
this.dependencies.logger.silly(`[CLUSTER]: push-state`, state); 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" // get cluster system meta, e.g. use in "logger"
@ -625,7 +626,7 @@ export class Cluster implements ClusterModel, ClusterState {
const update: KubeAuthUpdate = { message, isError }; const update: KubeAuthUpdate = { message, isError };
this.dependencies.logger.debug(`[CLUSTER]: broadcasting connection update`, { ...update, meta: this.getMeta() }); 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) { protected async getAllowedNamespaces(proxyConfig: KubeConfig) {
@ -645,7 +646,7 @@ export class Cluster implements ClusterModel, ClusterState {
const { response } = error as HttpError & { response: Response }; const { response } = error as HttpError & { response: Response };
this.dependencies.logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id, error: response.body }); 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; return namespaceList;

View File

@ -0,0 +1,10 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import 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");
});

View File

@ -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;

View File

@ -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<AsyncResult<undefined>>;
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<string, string> = {
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;

View File

@ -0,0 +1,10 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { 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");
});

View File

@ -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;

View File

@ -5,9 +5,11 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { broadcastMessage } from "./ipc"; import { broadcastMessage } from "./ipc";
export type BroadcastMessage = (channel: string, ...args: any[]) => Promise<void>;
const broadcastMessageInjectable = getInjectable({ const broadcastMessageInjectable = getInjectable({
id: "broadcast-message", id: "broadcast-message",
instantiate: () => broadcastMessage, instantiate: (): BroadcastMessage => broadcastMessage,
causesSideEffects: true, causesSideEffects: true,
}); });

View File

@ -13,10 +13,17 @@ import logger from "../../main/logger";
import type { ClusterFrameInfo } from "../cluster-frames"; import type { ClusterFrameInfo } from "../cluster-frames";
import { clusterFrameMap } from "../cluster-frames"; import { clusterFrameMap } from "../cluster-frames";
import type { Disposer } from "../utils"; 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 const broadcastMainChannel = "ipc:broadcast-main";
export function ipcMainHandle(channel: string, listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any) { 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) => { ipcMain.handle(channel, async (event, ...args) => {
return sanitizePayload(await listener(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 { 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); ipcMain.on(channel, listener);
return () => ipcMain.off(channel, listener); return () => ipcMain.off(channel, listener);
} }
export function ipcRendererOn(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): Disposer { 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); ipcRenderer.on(channel, listener);
return () => ipcRenderer.off(channel, listener); return () => ipcRenderer.off(channel, listener);

View File

@ -14,6 +14,7 @@ import { DeploymentApi, Ingress, IngressApi, Pod, PodApi } from "../endpoints";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
import apiManagerInjectable from "../api-manager/manager.injectable"; import apiManagerInjectable from "../api-manager/manager.injectable";
import autoRegistrationInjectable from "../api-manager/auto-registration.injectable"; import autoRegistrationInjectable from "../api-manager/auto-registration.injectable";
import AbortController from "abort-controller";
jest.mock("../api-manager"); jest.mock("../api-manager");

View File

@ -840,6 +840,10 @@ export class Pod extends KubeObject<
return this.spec?.priorityClassName || ""; return this.spec?.priorityClassName || "";
} }
getServiceAccountName() {
return this.spec?.serviceAccountName || "";
}
getStatus(): PodStatusPhase { getStatus(): PodStatusPhase {
const phase = this.getStatusPhase(); const phase = this.getStatusPhase();
const reason = this.getReason(); const reason = this.getReason();

View File

@ -27,9 +27,7 @@ import type { PartialDeep } from "type-fest";
import logger from "../logger"; import logger from "../logger";
import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; 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"; import autoRegistrationEmitterInjectable from "./api-manager/auto-registration-emitter.injectable";
import type AbortController from "abort-controller";
// TODO: upgrade node-fetch once we are starting to use ES modules
type LegacyAbortSignal = NonNullable<RequestInit["signal"]>;
/** /**
* The options used for creating a `KubeApi` * The options used for creating a `KubeApi`
@ -719,7 +717,7 @@ export class KubeApi<
const requestParams = timeout ? { query: { timeoutSeconds: timeout }} : {}; const requestParams = timeout ? { query: { timeoutSeconds: timeout }} : {};
const watchUrl = this.getWatchUrl(namespace); const watchUrl = this.getWatchUrl(namespace);
const responsePromise = this.request.getResponse(watchUrl, requestParams, { const responsePromise = this.request.getResponse(watchUrl, requestParams, {
signal: abortController.signal as LegacyAbortSignal, signal: abortController.signal,
timeout: 600_000, timeout: 600_000,
}); });

View File

@ -20,9 +20,7 @@ import logger from "../logger";
import assert from "assert"; import assert from "assert";
import type { PartialDeep } from "type-fest"; import type { PartialDeep } from "type-fest";
import { entries } from "../utils/objects"; import { entries } from "../utils/objects";
import AbortController from "abort-controller";
// TODO: upgrade node-fetch once we are starting to use ES modules
type LegacyAbortSignal = NonNullable<RequestInit["signal"]>;
export type OnLoadFailure = (error: unknown) => void; 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 // 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<D> = (data, error) => { const callback: KubeApiWatchCallback<D> = (data, error) => {
if (!this.isLoaded || error?.type === "aborted") return; if (!this.isLoaded || error?.type === "aborted") return;

View File

@ -8,7 +8,6 @@ import { matchPath } from "react-router";
import { countBy } from "lodash"; import { countBy } from "lodash";
import { isDefined, iter } from "../utils"; import { isDefined, iter } from "../utils";
import { pathToRegexp } from "path-to-regexp"; import { pathToRegexp } from "path-to-regexp";
import logger from "../../main/logger";
import type Url from "url-parse"; import type Url from "url-parse";
import { RoutingError, RoutingErrorType } from "./error"; import { RoutingError, RoutingErrorType } from "./error";
import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store"; 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 type { RouteHandler, RouteParams } from "../../extensions/registries/protocol-handler";
import { when } from "mobx"; import { when } from "mobx";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import type { Logger } from "../logger";
// IPC channel for protocol actions. Main broadcasts the open-url events to this channel. // IPC channel for protocol actions. Main broadcasts the open-url events to this channel.
export const ProtocolHandlerIpcPrefix = "protocol-handler"; export const ProtocolHandlerIpcPrefix = "protocol-handler";
@ -66,6 +66,7 @@ export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: R
export interface LensProtocolRouterDependencies { export interface LensProtocolRouterDependencies {
readonly extensionLoader: ExtensionLoader; readonly extensionLoader: ExtensionLoader;
readonly extensionsStore: ExtensionsStore; readonly extensionsStore: ExtensionsStore;
readonly logger: Logger;
} }
export abstract class LensProtocolRouter { export abstract class LensProtocolRouter {
@ -130,7 +131,7 @@ export abstract class LensProtocolRouter {
data.extensionName = extensionName; data.extensionName = extensionName;
} }
logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data); this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data);
return RouteAttempt.MISSING; return RouteAttempt.MISSING;
} }
@ -183,7 +184,7 @@ export abstract class LensProtocolRouter {
timeout: 5_000, timeout: 5_000,
}); });
} catch (error) { } catch (error) {
logger.info( this.dependencies.logger.info(
`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not installed (${error})`, `${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not installed (${error})`,
); );
@ -193,18 +194,18 @@ export abstract class LensProtocolRouter {
const extension = extensionLoader.getInstanceByName(name); const extension = extensionLoader.getInstanceByName(name);
if (!extension) { 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; return name;
} }
if (!this.dependencies.extensionsStore.isEnabled(extension)) { 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; return name;
} }
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched`); this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched`);
return extension; return extension;
} }
@ -250,7 +251,7 @@ export abstract class LensProtocolRouter {
*/ */
public addInternalHandler(urlSchema: string, handler: RouteHandler): this { public addInternalHandler(urlSchema: string, handler: RouteHandler): this {
pathToRegexp(urlSchema); // verify now that the schema is valid 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); this.internalRoutes.set(urlSchema, handler);
return this; return this;

View File

@ -3,6 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import AbortController from "abort-controller";
/** /**
* This is like an `AbortController` but will also abort if the parent aborts, * 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) * but won't make the parent abort if this aborts (single direction)

View File

@ -3,6 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type AbortController from "abort-controller";
/** /**
* Return a promise that will be resolved after at least `timeout` ms have * 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 * passed. If `failFast` is provided then the promise is also resolved if it has

View File

@ -3,6 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { AbortSignal } from "abort-controller";
/** /**
* Creates a new promise that will be rejected when the signal rejects. * Creates a new promise that will be rejected when the signal rejects.
* *

View File

@ -10,8 +10,10 @@ import extensionLoaderInjectable from "../extension-loader/extension-loader.inje
import { runInAction } from "mobx"; import { runInAction } from "mobx";
import updateExtensionsStateInjectable from "../extension-loader/update-extensions-state/update-extensions-state.injectable"; import updateExtensionsStateInjectable from "../extension-loader/update-extensions-state/update-extensions-state.injectable";
import mockFs from "mock-fs"; import mockFs from "mock-fs";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import { delay } from "../../renderer/utils"; 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); console = new Console(stdout, stderr);
@ -19,10 +21,14 @@ const manifestPath = "manifest/path";
const manifestPath2 = "manifest/path2"; const manifestPath2 = "manifest/path2";
const manifestPath3 = "manifest/path3"; const manifestPath3 = "manifest/path3";
jest.mock( describe("ExtensionLoader", () => {
"electron", let extensionLoader: ExtensionLoader;
() => ({ let updateExtensionStateMock: jest.Mock;
ipcRenderer: {
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(ipcRendererInjectable, () => ({
invoke: jest.fn(async (channel: string) => { invoke: jest.fn(async (channel: string) => {
if (channel === "extension-loader:main:state") { if (channel === "extension-loader:main:state") {
return [ return [
@ -59,8 +65,8 @@ jest.mock(
return []; return [];
}), }),
on: jest.fn(
(channel: string, listener: (event: any, ...args: any[]) => void) => { on: (channel: string, listener: (event: any, ...args: any[]) => void) => {
if (channel === "extension-loader:main:state") { if (channel === "extension-loader:main:state") {
// First initialize with extensions 1 and 2 // First initialize with extensions 1 and 2
// and then broadcast event to remove extension 2 and add extension number 3 // and then broadcast event to remove extension 2 and add extension number 3
@ -98,20 +104,7 @@ jest.mock(
}, 10); }, 10);
} }
}, },
), }) as unknown as IpcRenderer);
},
}),
{
virtual: true,
},
);
describe("ExtensionLoader", () => {
let extensionLoader: ExtensionLoader;
let updateExtensionStateMock: jest.Mock;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
mockFs(); mockFs();

View File

@ -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 extensionPackageRootDirectoryInjectable from "../extension-installer/extension-package-root-directory/extension-package-root-directory.injectable";
import installExtensionsInjectable from "../extension-installer/install-extensions/install-extensions.injectable"; import installExtensionsInjectable from "../extension-installer/install-extensions/install-extensions.injectable";
import staticFilesDirectoryInjectable from "../../common/vars/static-files-directory.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({ const extensionDiscoveryInjectable = getInjectable({
id: "extension-discovery", id: "extension-discovery",
@ -40,6 +44,10 @@ const extensionDiscoveryInjectable = getInjectable({
), ),
staticFilesDirectory: di.inject(staticFilesDirectoryInjectable), staticFilesDirectory: di.inject(staticFilesDirectoryInjectable),
readJsonFile: di.inject(readJsonFileInjectable),
pathExists: di.inject(pathExistsInjectable),
watch: di.inject(watchInjectable),
logger: di.inject(loggerInjectable),
}), }),
}); });

View File

@ -4,53 +4,29 @@
*/ */
import type { FSWatcher } from "chokidar"; import type { FSWatcher } from "chokidar";
import { watch } from "chokidar";
import path from "path"; import path from "path";
import os from "os"; import os from "os";
import { Console } from "console"; import { Console } from "console";
import * as fse from "fs-extra";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import extensionDiscoveryInjectable from "../extension-discovery/extension-discovery.injectable"; import extensionDiscoveryInjectable from "../extension-discovery/extension-discovery.injectable";
import type { ExtensionDiscovery } from "../extension-discovery/extension-discovery"; import type { ExtensionDiscovery } from "../extension-discovery/extension-discovery";
import installExtensionInjectable import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable";
from "../extension-installer/install-extension/install-extension.injectable"; import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForUserDataInjectable
from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import mockFs from "mock-fs"; import mockFs from "mock-fs";
import { delay } from "../../renderer/utils"; import { delay } from "../../renderer/utils";
import { observable, when } from "mobx"; import { observable, when } from "mobx";
import appVersionInjectable from "../../common/vars/app-version.injectable"; import appVersionInjectable from "../../common/vars/app-version.injectable";
import readJsonFileInjectable from "../../common/fs/read-json-file.injectable";
jest.setTimeout(60_000); import pathExistsInjectable from "../../common/fs/path-exists.injectable";
import watchInjectable from "../../common/fs/watch/watch.injectable";
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(),
},
}));
console = new Console(process.stdout, process.stderr); // fix mockFS console = new Console(process.stdout, process.stderr); // fix mockFS
const mockedWatch = watch as jest.MockedFunction<typeof watch>;
const mockedFse = fse as jest.Mocked<typeof fse>;
describe("ExtensionDiscovery", () => { describe("ExtensionDiscovery", () => {
let extensionDiscovery: ExtensionDiscovery; let extensionDiscovery: ExtensionDiscovery;
let readJsonFileMock: jest.Mock;
let pathExistsMock: jest.Mock;
let watchMock: jest.Mock;
beforeEach(() => { beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true }); const di = getDiForUnitTesting({ doGeneralOverrides: true });
@ -59,6 +35,15 @@ describe("ExtensionDiscovery", () => {
di.override(installExtensionInjectable, () => () => Promise.resolve()); di.override(installExtensionInjectable, () => () => Promise.resolve());
di.override(appVersionInjectable, () => "5.0.0"); 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(); mockFs();
extensionDiscovery = di.inject(extensionDiscoveryInjectable); extensionDiscovery = di.inject(extensionDiscoveryInjectable);
@ -72,7 +57,7 @@ describe("ExtensionDiscovery", () => {
const letTestFinish = observable.box(false); const letTestFinish = observable.box(false);
let addHandler!: (filePath: string) => void; 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")); expect(p).toBe(path.join(os.homedir(), ".k8slens/extensions/my-extension/package.json"));
return { return {
@ -84,8 +69,6 @@ describe("ExtensionDiscovery", () => {
}; };
}); });
mockedFse.pathExists.mockImplementation(() => true);
const mockWatchInstance = { const mockWatchInstance = {
on: jest.fn((event: string, handler: typeof addHandler) => { on: jest.fn((event: string, handler: typeof addHandler) => {
if (event === "add") { if (event === "add") {
@ -96,7 +79,7 @@ describe("ExtensionDiscovery", () => {
}), }),
} as unknown as FSWatcher; } as unknown as FSWatcher;
mockedWatch.mockImplementationOnce(() => mockWatchInstance); watchMock.mockImplementationOnce(() => mockWatchInstance);
// Need to force isLoaded to be true so that the file watching is started // Need to force isLoaded to be true so that the file watching is started
extensionDiscovery.isLoaded = true; extensionDiscovery.isLoaded = true;
@ -139,7 +122,7 @@ describe("ExtensionDiscovery", () => {
}), }),
} as unknown as FSWatcher; } as unknown as FSWatcher;
mockedWatch.mockImplementationOnce(() => mockWatchInstance); watchMock.mockImplementationOnce(() => mockWatchInstance);
// Need to force isLoaded to be true so that the file watching is started // Need to force isLoaded to be true so that the file watching is started
extensionDiscovery.isLoaded = true; extensionDiscovery.isLoaded = true;

View File

@ -3,7 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { watch } from "chokidar";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import fse from "fs-extra"; import fse from "fs-extra";
@ -12,7 +11,6 @@ import os from "os";
import path from "path"; import path from "path";
import { broadcastMessage, ipcMainHandle, ipcRendererOn } from "../../common/ipc"; import { broadcastMessage, ipcMainHandle, ipcRendererOn } from "../../common/ipc";
import { isErrnoException, toJS } from "../../common/utils"; import { isErrnoException, toJS } from "../../common/utils";
import logger from "../../main/logger";
import type { ExtensionsStore } from "../extensions-store/extensions-store"; import type { ExtensionsStore } from "../extensions-store/extensions-store";
import type { ExtensionLoader } from "../extension-loader"; import type { ExtensionLoader } from "../extension-loader";
import type { LensExtensionId, LensExtensionManifest } from "../lens-extension"; import type { LensExtensionId, LensExtensionManifest } from "../lens-extension";
@ -21,6 +19,10 @@ import type { ExtensionInstallationStateStore } from "../extension-installation-
import type { PackageJson } from "type-fest"; import type { PackageJson } from "type-fest";
import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling"; import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling";
import { requestInitialExtensionDiscovery } from "../../renderer/ipc"; import { requestInitialExtensionDiscovery } from "../../renderer/ipc";
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 { interface Dependencies {
extensionLoader: ExtensionLoader; extensionLoader: ExtensionLoader;
@ -35,6 +37,10 @@ interface Dependencies {
installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>; installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>;
extensionPackageRootDirectory: string; extensionPackageRootDirectory: string;
staticFilesDirectory: string; staticFilesDirectory: string;
readJsonFile: ReadJson;
pathExists: PathExists;
watch: Watch;
logger: Logger;
} }
export interface InstalledExtension { export interface InstalledExtension {
@ -155,13 +161,12 @@ export class ExtensionDiscovery {
* Dependencies are installed automatically after an extension folder is copied. * Dependencies are installed automatically after an extension folder is copied.
*/ */
async watchExtensions(): Promise<void> { async watchExtensions(): Promise<void> {
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 // Wait until .load() has been called and has been resolved
await this.whenLoaded; await this.whenLoaded;
// chokidar works better than fs.watch this.dependencies.watch(this.localFolderPath, {
watch(this.localFolderPath, {
// For adding and removing symlinks to work, the depth has to be 1. // For adding and removing symlinks to work, the depth has to be 1.
depth: 1, depth: 1,
ignoreInitial: true, ignoreInitial: true,
@ -206,11 +211,11 @@ export class ExtensionDiscovery {
await this.dependencies.installExtension(extension.absolutePath); await this.dependencies.installExtension(extension.absolutePath);
this.extensions.set(extension.id, extension); 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); this.events.emit("add", extension);
} }
} catch (error) { } catch (error) {
logger.error(`${logModule}: failed to add extension: ${error}`, { error }); this.dependencies.logger.error(`${logModule}: failed to add extension: ${error}`, { error });
} finally { } finally {
this.dependencies.extensionInstallationStateStore.clearInstallingFromMain(manifestPath); this.dependencies.extensionInstallationStateStore.clearInstallingFromMain(manifestPath);
} }
@ -247,13 +252,13 @@ export class ExtensionDiscovery {
const lensExtensionId = extension.manifestPath; const lensExtensionId = extension.manifestPath;
this.extensions.delete(extension.id); this.extensions.delete(extension.id);
logger.info(`${logModule} removed extension ${extensionName}`); this.dependencies.logger.info(`${logModule} removed extension ${extensionName}`);
this.events.emit("remove", lensExtensionId); this.events.emit("remove", lensExtensionId);
return; 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); const extension = this.extensions.get(extensionId) ?? this.dependencies.extensionLoader.getExtension(extensionId);
if (!extension) { 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; const { manifest, absolutePath } = extension;
logger.info(`${logModule} Uninstalling ${manifest.name}`); this.dependencies.logger.info(`${logModule} Uninstalling ${manifest.name}`);
await this.removeSymlinkByPackageName(manifest.name); await this.removeSymlinkByPackageName(manifest.name);
@ -296,7 +301,7 @@ export class ExtensionDiscovery {
this.loadStarted = true; this.loadStarted = true;
logger.info( this.dependencies.logger.info(
`${logModule} loading extensions from ${this.dependencies.extensionPackageRootDirectory}`, `${logModule} loading extensions from ${this.dependencies.extensionPackageRootDirectory}`,
); );
@ -358,12 +363,12 @@ export class ExtensionDiscovery {
*/ */
protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise<InstalledExtension | null> { protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise<InstalledExtension | null> {
try { 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 id = this.getInstalledManifestPath(manifest.name);
const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled }); const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled });
const extensionDir = path.dirname(manifestPath); const extensionDir = path.dirname(manifestPath);
const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`); 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); const isCompatible = (isBundled && this.dependencies.isCompatibleBundledExtension(manifest)) || this.dependencies.isCompatibleExtension(manifest);
return { return {
@ -378,9 +383,9 @@ export class ExtensionDiscovery {
} catch (error) { } catch (error) {
if (isErrnoException(error) && error.code === "ENOTDIR") { if (isErrnoException(error) && error.code === "ENOTDIR") {
// ignore this error, probably from .DS_Store file // 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 { } 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; return null;
@ -395,7 +400,7 @@ export class ExtensionDiscovery {
const userExtensions = await this.loadFromFolder(this.localFolderPath, bundledExtensions.map((extension) => extension.manifest.name)); const userExtensions = await this.loadFromFolder(this.localFolderPath, bundledExtensions.map((extension) => extension.manifest.name));
for (const extension of userExtensions) { for (const extension of userExtensions) {
if ((await fse.pathExists(extension.manifestPath)) === false) { if (!(await this.dependencies.pathExists(extension.manifestPath))) {
try { try {
await this.dependencies.installExtension(extension.absolutePath); await this.dependencies.installExtension(extension.absolutePath);
} catch (error) { } catch (error) {
@ -404,7 +409,7 @@ export class ExtensionDiscovery {
: String(error || "unknown error"); : String(error || "unknown error");
const { name, version } = extension.manifest; 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); 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; 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; return extensions;
} }

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { ipcRenderer } from "electron"; import { ipcMain, ipcRenderer } from "electron";
import { isEqual } from "lodash"; import { isEqual } from "lodash";
import type { ObservableMap } from "mobx"; import type { ObservableMap } from "mobx";
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx"; import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
@ -127,10 +127,10 @@ export class ExtensionLoader {
@action @action
async init() { async init() {
if (ipcRenderer) { if (ipcMain) {
await this.initRenderer();
} else {
await this.initMain(); await this.initMain();
} else {
await this.initRenderer();
} }
await Promise.all([this.whenLoaded]); await Promise.all([this.whenLoaded]);

View File

@ -2,11 +2,7 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
jest.mock("../../common/ipc");
jest.mock("request");
jest.mock("request-promise-native");
import { Console } from "console"; import { Console } from "console";
import type { Cluster } from "../../common/cluster/cluster"; import type { Cluster } from "../../common/cluster/cluster";
import { Kubectl } from "../kubectl/kubectl"; import { Kubectl } from "../kubectl/kubectl";
@ -41,6 +37,7 @@ describe("create clusters", () => {
di.override(kubectlBinaryNameInjectable, () => "kubectl"); di.override(kubectlBinaryNameInjectable, () => "kubectl");
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
di.override(normalizedPlatformInjectable, () => "darwin"); di.override(normalizedPlatformInjectable, () => "darwin");
di.override(broadcastMessageInjectable, () => async () => {});
di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true)); di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true));
di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ])); di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ]));
di.override(createContextHandlerInjectable, () => (cluster) => ({ di.override(createContextHandlerInjectable, () => (cluster) => ({

View File

@ -3,45 +3,13 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
jest.mock("winston", () => ({ import waitUntilPortIsUsedInjectable from "../kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.injectable";
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 type { Cluster } from "../../common/cluster/cluster"; import type { Cluster } from "../../common/cluster/cluster";
import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy"; import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
import { broadcastMessage } from "../../common/ipc";
import type { ChildProcess } from "child_process"; import type { ChildProcess } from "child_process";
import { spawn } from "child_process";
import { Kubectl } from "../kubectl/kubectl"; import { Kubectl } from "../kubectl/kubectl";
import type { DeepMockProxy } from "jest-mock-extended"; import type { DeepMockProxy } from "jest-mock-extended";
import { mockDeep, mock } from "jest-mock-extended"; import { mockDeep, mock } from "jest-mock-extended";
import { waitUntilUsed } from "tcp-port-used";
import type { Readable } from "stream"; import type { Readable } from "stream";
import { EventEmitter } from "stream"; import { EventEmitter } from "stream";
import { Console } from "console"; 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 normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable"; import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable";
import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable"; import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable";
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
console = new Console(stdout, stderr); console = new Console(stdout, stderr);
const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcastMessage>;
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction<typeof waitUntilUsed>;
const clusterServerUrl = "https://192.168.64.3:8443"; const clusterServerUrl = "https://192.168.64.3:8443";
describe("kube auth proxy tests", () => { describe("kube auth proxy tests", () => {
let createCluster: CreateCluster; let createCluster: CreateCluster;
let createKubeAuthProxy: (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy; let createKubeAuthProxy: (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
let spawnMock: jest.Mock;
let waitUntilPortIsUsedMock: jest.Mock;
let broadcastMessageMock: jest.Mock;
beforeEach(async () => { beforeEach(async () => {
jest.clearAllMocks(); jest.clearAllMocks();
@ -105,7 +74,15 @@ describe("kube auth proxy tests", () => {
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "some-directory-for-temp"); 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(kubectlBinaryNameInjectable, () => "kubectl");
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
di.override(normalizedPlatformInjectable, () => "darwin"); di.override(normalizedPlatformInjectable, () => "darwin");
@ -211,12 +188,12 @@ describe("kube auth proxy tests", () => {
return stdout; return stdout;
}); });
mockSpawn.mockImplementationOnce((command: string): ChildProcess => { spawnMock.mockImplementationOnce((command: string): ChildProcess => {
expect(path.basename(command).split(".")[0]).toBe("lens-k8s-proxy"); expect(path.basename(command).split(".")[0]).toBe("lens-k8s-proxy");
return mockedCP; return mockedCP;
}); });
mockWaitUntilUsed.mockReturnValueOnce(Promise.resolve()); waitUntilPortIsUsedMock.mockReturnValueOnce(Promise.resolve());
const cluster = createCluster({ const cluster = createCluster({
id: "foobar", id: "foobar",
@ -233,34 +210,34 @@ describe("kube auth proxy tests", () => {
await proxy.run(); await proxy.run();
listeners.emit("error", { message: "foobarbat" }); 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 () => { it("should call spawn and broadcast exit", async () => {
await proxy.run(); await proxy.run();
listeners.emit("exit", 0); 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 () => { it("should call spawn and broadcast errors from stderr", async () => {
await proxy.run(); await proxy.run();
listeners.emit("stderr/data", "an error"); 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 () => { it("should call spawn and broadcast stdout serving info", async () => {
await proxy.run(); 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 () => { it("should call spawn and broadcast stdout other info", async () => {
await proxy.run(); await proxy.run();
listeners.emit("stdout/data", "some info"); 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 });
}); });
}); });
}); });

View File

@ -15,6 +15,7 @@ import listNamespacesInjectable from "../../common/cluster/list-namespaces.injec
import loggerInjectable from "../../common/logger.injectable"; import loggerInjectable from "../../common/logger.injectable";
import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable"; import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable";
import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable"; import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable";
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
const createClusterInjectable = getInjectable({ const createClusterInjectable = getInjectable({
id: "create-cluster", id: "create-cluster",
@ -30,6 +31,7 @@ const createClusterInjectable = getInjectable({
logger: di.inject(loggerInjectable), logger: di.inject(loggerInjectable),
detectorRegistry: di.inject(detectorRegistryInjectable), detectorRegistry: di.inject(detectorRegistryInjectable),
createVersionDetector: di.inject(createVersionDetectorInjectable), createVersionDetector: di.inject(createVersionDetectorInjectable),
broadcastMessage: di.inject(broadcastMessageInjectable),
}; };
return (model, configData) => new Cluster(dependencies, model, configData); return (model, configData) => new Cluster(dependencies, model, configData);

View File

@ -58,7 +58,6 @@ import type { ClusterFrameInfo } from "../common/cluster-frames";
import { observable } from "mobx"; import { observable } from "mobx";
import waitForElectronToBeReadyInjectable from "./electron-app/features/wait-for-electron-to-be-ready.injectable"; 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 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 setupRunnablesAfterWindowIsOpenedInjectable from "./electron-app/runnables/setup-runnables-after-window-is-opened.injectable";
import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable"; import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable";
import getElectronThemeInjectable from "./electron-app/features/get-electron-theme.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(shouldStartHiddenInjectable, () => false);
di.override(showMessagePopupInjectable, () => () => {}); di.override(showMessagePopupInjectable, () => () => {});
di.override(waitForElectronToBeReadyInjectable, () => () => Promise.resolve()); di.override(waitForElectronToBeReadyInjectable, () => () => Promise.resolve());
di.override(ipcMainInjectable, () => ({}));
di.override(getElectronThemeInjectable, () => () => "dark"); di.override(getElectronThemeInjectable, () => () => "dark");
di.override(syncThemeFromOperatingSystemInjectable, () => ({ start: () => {}, stop: () => {} })); di.override(syncThemeFromOperatingSystemInjectable, () => ({ start: () => {}, stop: () => {} }));
di.override(electronQuitAndInstallUpdateInjectable, () => () => {}); di.override(electronQuitAndInstallUpdateInjectable, () => () => {});

View File

@ -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<any> {
return charts.get(this.repo.name) ?? {};
}
}

View File

@ -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 getActiveHelmRepositoriesInjectable from "../repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
import type { AsyncResult } from "../../../common/utils/async-result"; import type { AsyncResult } from "../../../common/utils/async-result";
import type { HelmRepo } from "../../../common/helm/helm-repo"; import type { HelmRepo } from "../../../common/helm/helm-repo";
import { sortCharts } from "../../../common/utils";
jest.mock("../helm-chart-manager"); import helmChartManagerInjectable from "../helm-chart-manager.injectable";
describe("Helm Service tests", () => { describe("Helm Service tests", () => {
let listHelmCharts: () => Promise<any>; let listHelmCharts: () => Promise<any>;
@ -20,6 +20,11 @@ describe("Helm Service tests", () => {
getActiveHelmRepositoriesMock = jest.fn(); getActiveHelmRepositoriesMock = jest.fn();
di.override(
helmChartManagerInjectable,
(di, repo) => new HelmChartManagerFake(repo) as unknown,
);
di.override(getActiveHelmRepositoriesInjectable, () => getActiveHelmRepositoriesMock); di.override(getActiveHelmRepositoriesInjectable, () => getActiveHelmRepositoriesMock);
di.unoverride(listHelmChartsInjectable); 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<any> {
return charts.get(this.repo.name) ?? {};
}
}

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
export interface ChartCacheEntry {
data: string; // serialized JSON
mtimeMs: number;
}
export type HelmChartManagerCache = Map<string, ChartCacheEntry>;
const helmChartManagerCacheInjectable = getInjectable({
id: "helm-chart-manager-cache",
instantiate: (): HelmChartManagerCache => new Map(),
});
export default helmChartManagerCacheInjectable;

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import 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;

View File

@ -5,39 +5,34 @@
import fs from "fs"; import fs from "fs";
import * as yaml from "js-yaml"; import * as yaml from "js-yaml";
import logger from "../logger";
import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api"; import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api";
import { iter, put, sortCharts } from "../../common/utils"; import { iter, put, sortCharts } from "../../common/utils";
import { execHelm } from "./exec"; import { execHelm } from "./exec";
import type { SetRequired } from "type-fest"; import type { SetRequired } from "type-fest";
import { assert } from "console"; import { assert } from "console";
import type { HelmRepo } from "../../common/helm/helm-repo"; import type { HelmRepo } from "../../common/helm/helm-repo";
import type { HelmChartManagerCache } from "./helm-chart-manager-cache.injectable";
interface ChartCacheEntry { import type { Logger } from "../../common/logger";
data: string; // serialized JSON
mtimeMs: number;
}
export interface HelmCacheFile { export interface HelmCacheFile {
apiVersion: string; apiVersion: string;
entries: RepoHelmChartList; entries: RepoHelmChartList;
} }
export class HelmChartManager { interface Dependencies {
static readonly #cache = new Map<string, ChartCacheEntry>(); cache: HelmChartManagerCache;
logger: Logger;
}
export class HelmChartManager {
protected readonly repo: SetRequired<HelmRepo, "cacheFilePath">; protected readonly repo: SetRequired<HelmRepo, "cacheFilePath">;
private constructor(repo: HelmRepo) { constructor(repo: HelmRepo, private dependencies: Dependencies) {
assert(repo.cacheFilePath, "CacheFilePath must be provided on the helm repo"); assert(repo.cacheFilePath, "CacheFilePath must be provided on the helm repo");
this.repo = repo as SetRequired<HelmRepo, "cacheFilePath">; this.repo = repo as SetRequired<HelmRepo, "cacheFilePath">;
} }
static forRepo(repo: HelmRepo) {
return new this(repo);
}
public async chartVersions(name: string) { public async chartVersions(name: string) {
const charts = await this.charts(); const charts = await this.charts();
@ -48,7 +43,7 @@ export class HelmChartManager {
try { try {
return await this.cachedYaml(); return await this.cachedYaml();
} catch(error) { } 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 {}; return {};
} }
@ -84,7 +79,7 @@ export class HelmChartManager {
const normalized = normalizeHelmCharts(this.repo.name, data.entries); const normalized = normalizeHelmCharts(this.repo.name, data.entries);
return put( return put(
HelmChartManager.#cache, this.dependencies.cache,
this.repo.name, this.repo.name,
{ {
data: JSON.stringify(normalized), data: JSON.stringify(normalized),
@ -94,7 +89,7 @@ export class HelmChartManager {
} }
protected async cachedYaml(): Promise<RepoHelmChartList> { protected async cachedYaml(): Promise<RepoHelmChartList> {
let cacheEntry = HelmChartManager.#cache.get(this.repo.name); let cacheEntry = this.dependencies.cache.get(this.repo.name);
if (!cacheEntry) { if (!cacheEntry) {
cacheEntry = await this.updateYamlCache(); cacheEntry = await this.updateYamlCache();

View File

@ -3,14 +3,16 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { HelmChartManager } from "../helm-chart-manager";
import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable"; 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({ const getHelmChartValuesInjectable = getInjectable({
id: "get-helm-chart-values", id: "get-helm-chart-values",
instantiate: (di) => { instantiate: (di) => {
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable); const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
return async (repoName: string, chartName: string, version = "") => { return async (repoName: string, chartName: string, version = "") => {
const repo = await getActiveHelmRepository(repoName); const repo = await getActiveHelmRepository(repoName);
@ -19,7 +21,7 @@ const getHelmChartValuesInjectable = getInjectable({
return undefined; return undefined;
} }
return HelmChartManager.forRepo(repo).getValues(chartName, version); return getChartManager(repo).getValues(chartName, version);
}; };
}, },

View File

@ -3,14 +3,16 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { HelmChartManager } from "../helm-chart-manager";
import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable"; 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({ const getHelmChartInjectable = getInjectable({
id: "get-helm-chart", id: "get-helm-chart",
instantiate: (di) => { instantiate: (di) => {
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable); const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
return async (repoName: string, chartName: string, version = "") => { return async (repoName: string, chartName: string, version = "") => {
const repo = await getActiveHelmRepository(repoName); const repo = await getActiveHelmRepository(repoName);
@ -19,7 +21,7 @@ const getHelmChartInjectable = getInjectable({
return undefined; return undefined;
} }
const chartManager = HelmChartManager.forRepo(repo); const chartManager = getChartManager(repo);
return { return {
readme: await chartManager.getReadme(chartName, version), readme: await chartManager.getReadme(chartName, version),

View File

@ -5,14 +5,16 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert"; import assert from "assert";
import { object } from "../../../common/utils"; 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 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({ const listHelmChartsInjectable = getInjectable({
id: "list-helm-charts", id: "list-helm-charts",
instantiate: (di) => { instantiate: (di) => {
const getActiveHelmRepositories = di.inject(getActiveHelmRepositoriesInjectable); const getActiveHelmRepositories = di.inject(getActiveHelmRepositoriesInjectable);
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
return async () => { return async () => {
const result = await getActiveHelmRepositories(); const result = await getActiveHelmRepositories();
@ -27,7 +29,7 @@ const listHelmChartsInjectable = getInjectable({
async (repo) => async (repo) =>
[ [
repo.name, repo.name,
await HelmChartManager.forRepo(repo).charts(), await getChartManager(repo).charts(),
] as const, ] as const,
), ),
), ),

View File

@ -13,6 +13,7 @@ import spawnInjectable from "../child-process/spawn.injectable";
import { getKubeAuthProxyCertificate } from "./get-kube-auth-proxy-certificate"; import { getKubeAuthProxyCertificate } from "./get-kube-auth-proxy-certificate";
import loggerInjectable from "../../common/logger.injectable"; import loggerInjectable from "../../common/logger.injectable";
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.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; export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
@ -29,6 +30,7 @@ const createKubeAuthProxyInjectable = getInjectable({
proxyCert: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate), proxyCert: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate),
spawn: di.inject(spawnInjectable), spawn: di.inject(spawnInjectable),
logger: di.inject(loggerInjectable), logger: di.inject(loggerInjectable),
waitUntilPortIsUsed: di.inject(waitUntilPortIsUsedInjectable),
}; };
return new KubeAuthProxy(dependencies, cluster, environmentVariables); return new KubeAuthProxy(dependencies, cluster, environmentVariables);

View File

@ -4,10 +4,8 @@
*/ */
import type { ChildProcess } from "child_process"; import type { ChildProcess } from "child_process";
import { waitUntilUsed } from "tcp-port-used";
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
import type { Cluster } from "../../common/cluster/cluster"; import type { Cluster } from "../../common/cluster/cluster";
import logger from "../logger";
import { getPortFrom } from "../utils/get-port"; import { getPortFrom } from "../utils/get-port";
import { makeObservable, observable, when } from "mobx"; import { makeObservable, observable, when } from "mobx";
import type { SelfSignedCert } from "selfsigned"; import type { SelfSignedCert } from "selfsigned";
@ -15,6 +13,7 @@ import assert from "assert";
import { TypedRegEx } from "typed-regex"; import { TypedRegEx } from "typed-regex";
import type { Spawn } from "../child-process/spawn.injectable"; import type { Spawn } from "../child-process/spawn.injectable";
import type { Logger } from "../../common/logger"; 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 (?<address>.+)"; const startingServeMatcher = "starting to serve on (?<address>.+)";
const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"), { const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"), {
@ -24,8 +23,9 @@ const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"),
export interface KubeAuthProxyDependencies { export interface KubeAuthProxyDependencies {
readonly proxyBinPath: string; readonly proxyBinPath: string;
readonly proxyCert: SelfSignedCert; readonly proxyCert: SelfSignedCert;
spawn: Spawn; readonly spawn: Spawn;
readonly logger: Logger; readonly logger: Logger;
readonly waitUntilPortIsUsed: WaitUntilPortIsUsed;
} }
export class KubeAuthProxy { export class KubeAuthProxy {
@ -106,13 +106,13 @@ export class KubeAuthProxy {
onFind: () => this.cluster.broadcastConnectUpdate("Authentication proxy started"), 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 { try {
await waitUntilUsed(this.port, 500, 10000); await this.dependencies.waitUntilPortIsUsed(this.port, 500, 10000);
this.ready = true; this.ready = true;
} catch (error) { } 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.cluster.broadcastConnectUpdate("Proxy port failed to be used within timelimit, restarting...", true);
this.exit(); this.exit();
@ -124,7 +124,7 @@ export class KubeAuthProxy {
this.ready = false; this.ready = false;
if (this.proxyProcess) { 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.removeAllListeners();
this.proxyProcess.stderr?.removeAllListeners(); this.proxyProcess.stderr?.removeAllListeners();
this.proxyProcess.stdout?.removeAllListeners(); this.proxyProcess.stdout?.removeAllListeners();

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { 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.",
);
},
);

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { waitUntilUsed } from "tcp-port-used";
export type WaitUntilPortIsUsed = (
port: number,
retryAfterMs: number,
timeoutAfterMs: number
) => Promise<void>;
const waitUntilPortIsUsedInjectable = getInjectable({
id: "wait-until-port-is-used",
instantiate: (): WaitUntilPortIsUsed => waitUntilUsed,
causesSideEffects: true,
});
export default waitUntilPortIsUsedInjectable;

View File

@ -5,7 +5,6 @@
import * as uuid from "uuid"; import * as uuid from "uuid";
import { broadcastMessage } from "../../../common/ipc";
import { ProtocolHandlerExtension, ProtocolHandlerInternal, ProtocolHandlerInvalid } from "../../../common/protocol-handler"; import { ProtocolHandlerExtension, ProtocolHandlerInternal, ProtocolHandlerInvalid } from "../../../common/protocol-handler";
import { delay, noop } from "../../../common/utils"; import { delay, noop } from "../../../common/utils";
import type { ExtensionsStore, IsEnabledExtensionDescriptor } from "../../../extensions/extensions-store/extensions-store"; 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 type { ObservableMap } from "mobx";
import extensionInstancesInjectable from "../../../extensions/extension-loader/extension-instances.injectable"; import extensionInstancesInjectable from "../../../extensions/extension-loader/extension-instances.injectable";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
jest.mock("../../../common/ipc");
function throwIfDefined(val: any): void { function throwIfDefined(val: any): void {
if (val != null) { if (val != null) {
@ -32,6 +30,7 @@ describe("protocol router tests", () => {
let extensionInstances: ObservableMap<LensExtensionId, LensExtension>; let extensionInstances: ObservableMap<LensExtensionId, LensExtension>;
let lpr: LensProtocolRouterMain; let lpr: LensProtocolRouterMain;
let enabledExtensions: Set<string>; let enabledExtensions: Set<string>;
let broadcastMessageMock: jest.Mock;
beforeEach(async () => { beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true }); const di = getDiForUnitTesting({ doGeneralOverrides: true });
@ -46,6 +45,9 @@ describe("protocol router tests", () => {
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
broadcastMessageMock = jest.fn();
di.override(broadcastMessageInjectable, () => broadcastMessageMock);
extensionInstances = di.inject(extensionInstancesInjectable); extensionInstances = di.inject(extensionInstancesInjectable);
lpr = di.inject(lensProtocolRouterMainInjectable); lpr = di.inject(lensProtocolRouterMainInjectable);
@ -54,12 +56,12 @@ describe("protocol router tests", () => {
it("should broadcast invalid protocol on non-lens URLs", async () => { it("should broadcast invalid protocol on non-lens URLs", async () => {
await lpr.route("https://google.ca"); 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 () => { it("should broadcast invalid host on non internal or non extension URLs", async () => {
await lpr.route("lens://foobar"); 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 () => { it("should not throw when has valid host", async () => {
@ -101,8 +103,8 @@ describe("protocol router tests", () => {
} }
await delay(50); await delay(50);
expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app", "matched"); expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app", "matched");
expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched"); expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched");
}); });
it("should call handler if matches", async () => { it("should call handler if matches", async () => {
@ -117,7 +119,7 @@ describe("protocol router tests", () => {
} }
expect(called).toBe(true); 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 () => { it("should call most exact handler", async () => {
@ -133,7 +135,7 @@ describe("protocol router tests", () => {
} }
expect(called).toBe("foo"); 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 () => { it("should call most exact handler for an extension", async () => {
@ -174,7 +176,7 @@ describe("protocol router tests", () => {
await delay(50); await delay(50);
expect(called).toBe("foob"); 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 () => { it("should work with non-org extensions", async () => {
@ -244,7 +246,7 @@ describe("protocol router tests", () => {
await delay(50); await delay(50);
expect(called).toBe(1); 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", () => { it("should throw if urlSchema is invalid", () => {
@ -266,7 +268,7 @@ describe("protocol router tests", () => {
} }
expect(called).toBe(3); 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 () => { it("should call most exact handler with 2 found handlers", async () => {
@ -283,6 +285,6 @@ describe("protocol router tests", () => {
} }
expect(called).toBe(1); 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");
}); });
}); });

View File

@ -7,6 +7,8 @@ import extensionLoaderInjectable from "../../../extensions/extension-loader/exte
import { LensProtocolRouterMain } from "./lens-protocol-router-main"; import { LensProtocolRouterMain } from "./lens-protocol-router-main";
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable"; import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.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({ const lensProtocolRouterMainInjectable = getInjectable({
id: "lens-protocol-router-main", id: "lens-protocol-router-main",
@ -16,6 +18,8 @@ const lensProtocolRouterMainInjectable = getInjectable({
extensionLoader: di.inject(extensionLoaderInjectable), extensionLoader: di.inject(extensionLoaderInjectable),
extensionsStore: di.inject(extensionsStoreInjectable), extensionsStore: di.inject(extensionsStoreInjectable),
showApplicationWindow: di.inject(showApplicationWindowInjectable), showApplicationWindow: di.inject(showApplicationWindowInjectable),
broadcastMessage: di.inject(broadcastMessageInjectable),
logger: di.inject(loggerInjectable),
}), }),
}); });

View File

@ -3,15 +3,14 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import logger from "../../logger";
import * as proto from "../../../common/protocol-handler"; import * as proto from "../../../common/protocol-handler";
import URLParse from "url-parse"; import URLParse from "url-parse";
import type { LensExtension } from "../../../extensions/lens-extension"; import type { LensExtension } from "../../../extensions/lens-extension";
import { broadcastMessage } from "../../../common/ipc";
import { observable, when, makeObservable } from "mobx"; import { observable, when, makeObservable } from "mobx";
import type { LensProtocolRouterDependencies, RouteAttempt } from "../../../common/protocol-handler"; import type { LensProtocolRouterDependencies, RouteAttempt } from "../../../common/protocol-handler";
import { ProtocolHandlerInvalid } from "../../../common/protocol-handler"; import { ProtocolHandlerInvalid } from "../../../common/protocol-handler";
import { disposer, noop } from "../../../common/utils"; import { disposer, noop } from "../../../common/utils";
import type { BroadcastMessage } from "../../../common/ipc/broadcast-message.injectable";
export interface FallbackHandler { export interface FallbackHandler {
(name: string): Promise<boolean>; (name: string): Promise<boolean>;
@ -36,6 +35,7 @@ function checkHost<Query>(url: URLParse<Query>): boolean {
export interface LensProtocolRouterMainDependencies extends LensProtocolRouterDependencies { export interface LensProtocolRouterMainDependencies extends LensProtocolRouterDependencies {
showApplicationWindow: () => Promise<void>; showApplicationWindow: () => Promise<void>;
broadcastMessage: BroadcastMessage;
} }
export class LensProtocolRouterMain extends proto.LensProtocolRouter { export class LensProtocolRouterMain extends proto.LensProtocolRouter {
@ -73,7 +73,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
this.dependencies.showApplicationWindow().catch(noop); this.dependencies.showApplicationWindow().catch(noop);
const routeInternally = checkHost(url); const routeInternally = checkHost(url);
logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`); this.dependencies.logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`);
if (routeInternally) { if (routeInternally) {
this._routeToInternal(url); this._routeToInternal(url);
@ -81,12 +81,12 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
await this._routeToExtension(url); await this._routeToExtension(url);
} }
} catch (error) { } 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) { 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 { } 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 rawUrl = url.toString(); // for sending to renderer
const attempt = super._routeToInternal(url); 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; return attempt;
} }
@ -136,7 +136,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
*/ */
const attempt = await super._routeToExtension(new URLParse(url.toString(), true)); 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; return attempt;
} }

View File

@ -11,7 +11,6 @@ import { beforeApplicationIsLoadingInjectionToken } from "./runnable-tokens/befo
import { onLoadOfApplicationInjectionToken } from "./runnable-tokens/on-load-of-application-injection-token"; import { onLoadOfApplicationInjectionToken } from "./runnable-tokens/on-load-of-application-injection-token";
import { afterApplicationIsLoadedInjectionToken } from "./runnable-tokens/after-application-is-loaded-injection-token"; import { afterApplicationIsLoadedInjectionToken } from "./runnable-tokens/after-application-is-loaded-injection-token";
import splashWindowInjectable from "./lens-window/splash-window/splash-window.injectable"; 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 openDeepLinkInjectable from "../protocol-handler/lens-protocol-router-main/open-deep-link-for-url/open-deep-link.injectable";
import { pipeline } from "@ogre-tools/fp"; import { pipeline } from "@ogre-tools/fp";
import { find, map, startsWith, toLower } from "lodash/fp"; import { find, map, startsWith, toLower } from "lodash/fp";

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import 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);

View File

@ -6,12 +6,14 @@ import { getInjectable } from "@ogre-tools/injectable";
import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable"; import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable";
import navigateInjectable from "../../../navigation/navigate.injectable"; import navigateInjectable from "../../../navigation/navigate.injectable";
import { CatalogEntityRegistry } from "./registry"; import { CatalogEntityRegistry } from "./registry";
import loggerInjectable from "../../../../common/logger.injectable";
const catalogEntityRegistryInjectable = getInjectable({ const catalogEntityRegistryInjectable = getInjectable({
id: "catalog-entity-registry", id: "catalog-entity-registry",
instantiate: (di) => new CatalogEntityRegistry({ instantiate: (di) => new CatalogEntityRegistry({
categoryRegistry: di.inject(catalogCategoryRegistryInjectable), categoryRegistry: di.inject(catalogCategoryRegistryInjectable),
navigate: di.inject(navigateInjectable), navigate: di.inject(navigateInjectable),
logger: di.inject(loggerInjectable),
}), }),
}); });

View File

@ -10,12 +10,12 @@ import "../../../../common/catalog-entities";
import { iter } from "../../../utils"; import { iter } from "../../../utils";
import type { Disposer } from "../../../utils"; import type { Disposer } from "../../../utils";
import { once } from "lodash"; import { once } from "lodash";
import logger from "../../../../common/logger";
import { CatalogRunEvent } from "../../../../common/catalog/catalog-run-event"; import { CatalogRunEvent } from "../../../../common/catalog/catalog-run-event";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { catalogInitChannel, catalogItemsChannel, catalogEntityRunListener } from "../../../../common/ipc/catalog"; import { catalogInitChannel, catalogItemsChannel, catalogEntityRunListener } from "../../../../common/ipc/catalog";
import { isMainFrame } from "process"; import { isMainFrame } from "process";
import type { Navigate } from "../../../navigation/navigate.injectable"; import type { Navigate } from "../../../navigation/navigate.injectable";
import type { Logger } from "../../../../common/logger";
export type EntityFilter = (entity: CatalogEntity) => any; export type EntityFilter = (entity: CatalogEntity) => any;
export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promise<void>; export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promise<void>;
@ -23,6 +23,7 @@ export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promis
interface Dependencies { interface Dependencies {
navigate: Navigate; navigate: Navigate;
readonly categoryRegistry: CatalogCategoryRegistry; readonly categoryRegistry: CatalogCategoryRegistry;
logger: Logger;
} }
export class CatalogEntityRegistry { export class CatalogEntityRegistry {
@ -219,7 +220,7 @@ export class CatalogEntityRegistry {
* @returns Whether the entities `onRun` method should be executed * @returns Whether the entities `onRun` method should be executed
*/ */
async onBeforeRun(entity: CatalogEntity): Promise<boolean> { async onBeforeRun(entity: CatalogEntity): Promise<boolean> {
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 }); const runEvent = new CatalogRunEvent({ target: entity });
@ -227,7 +228,7 @@ export class CatalogEntityRegistry {
try { try {
await onBeforeRun(runEvent); await onBeforeRun(runEvent);
} catch (error) { } 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) { if (runEvent.defaultPrevented) {
@ -253,9 +254,9 @@ export class CatalogEntityRegistry {
}, },
}); });
} else { } 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));
} }
} }

View File

@ -7,10 +7,9 @@ import React from "react";
import { screen } from "@testing-library/react"; import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import { Catalog } from "./catalog"; import { Catalog } from "./catalog";
import { mockWindow } from "../../../../__mocks__/windowMock";
import type { CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog"; import type { CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog";
import { CatalogEntity } 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 { CatalogEntityDetailRegistry } from "../../../extensions/registries";
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store"; import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
import { getDiForUnitTesting } from "../../getDiForUnitTesting"; 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 catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
import type { DiRender } from "../test-utils/renderFor"; import type { DiRender } from "../test-utils/renderFor";
import { renderFor } 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 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 getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
import type { AppEvent } from "../../../common/app-event-bus/event-bus"; import type { AppEvent } from "../../../common/app-event-bus/event-bus";
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable"; import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
import { computed } from "mobx"; import { computed } from "mobx";
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable"; import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
import type { AsyncFnMock } from "@async-fn/jest";
mockWindow(); import asyncFn from "@async-fn/jest";
jest.mock("electron", () => ({ import { flushPromises } from "../../../common/test-utils/flush-promises";
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: () => <div>menu item</div>,
}));
class MockCatalogEntity extends CatalogEntity { class MockCatalogEntity extends CatalogEntity {
public apiVersion = "api"; public apiVersion = "api";
@ -95,7 +71,6 @@ describe("<Catalog />", () => {
di.permitSideEffects(getConfigurationFileModelInjectable); di.permitSideEffects(getConfigurationFileModelInjectable);
mockFs();
CatalogEntityDetailRegistry.createInstance(); CatalogEntityDetailRegistry.createInstance();
render = renderFor(di); render = renderFor(di);
@ -103,8 +78,6 @@ describe("<Catalog />", () => {
catalogEntityItem = createMockCatalogEntity(onRun); catalogEntityItem = createMockCatalogEntity(onRun);
catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
di.override(catalogEntityRegistryInjectable, () => catalogEntityRegistry);
emitEvent = jest.fn(); emitEvent = jest.fn();
di.override(appEventBusInjectable, () => ({ di.override(appEventBusInjectable, () => ({
@ -119,60 +92,71 @@ describe("<Catalog />", () => {
afterEach(() => { afterEach(() => {
CatalogEntityDetailRegistry.resetInstance(); CatalogEntityDetailRegistry.resetInstance();
jest.clearAllMocks();
jest.restoreAllMocks();
mockFs.restore();
}); });
it("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", (done) => { describe("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", () => {
catalogEntityRegistry.addOnBeforeRun( let onBeforeRunMock: AsyncFnMock<CatalogEntityOnBeforeRun>;
(event) => {
expect(event.target.getId()).toBe("a_catalogEntity_uid");
expect(event.target.getName()).toBe("a catalog entity");
setTimeout(() => { beforeEach(() => {
expect(onRun).toHaveBeenCalled(); onBeforeRunMock = asyncFn();
done();
}, 500); catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
},
);
render(<Catalog />); render(<Catalog />);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
}); });
it("onBeforeRun prevents event => onRun wont be triggered", (done) => { it("calls on before run event", () => {
catalogEntityRegistry.addOnBeforeRun( const target = onBeforeRunMock.mock.calls[0][0].target;
(e) => {
setTimeout(() => { 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(); expect(onRun).not.toHaveBeenCalled();
done();
}, 500);
e.preventDefault();
},
);
render(<Catalog />);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
}); });
it("addOnBeforeRun throw an exception => onRun will be triggered", (done) => { it("when before run event resolves, calls onRun", async () => {
catalogEntityRegistry.addOnBeforeRun( await onBeforeRunMock.resolve();
() => {
setTimeout(() => {
expect(onRun).toHaveBeenCalled();
done();
}, 500);
throw new Error("error!"); expect(onRun).toHaveBeenCalled();
}, });
); });
it("onBeforeRun prevents event => onRun wont be triggered", async () => {
const onBeforeRunMock = jest.fn((event) => event.preventDefault());
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
render(<Catalog />); render(<Catalog />);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
await flushPromises();
expect(onRun).not.toHaveBeenCalled();
});
it("addOnBeforeRun throw an exception => onRun will be triggered", async () => {
const onBeforeRunMock = jest.fn(() => {
throw new Error("some error");
});
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
render(<Catalog />);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
await flushPromises();
expect(onRun).toHaveBeenCalled();
}); });
it("addOnRunHook return a promise and does not prevent run event => onRun()", (done) => { it("addOnRunHook return a promise and does not prevent run event => onRun()", (done) => {
@ -189,40 +173,34 @@ describe("<Catalog />", () => {
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
}); });
it("addOnRunHook return a promise and prevents event wont be triggered", (done) => { it("addOnRunHook return a promise and prevents event wont be triggered", async () => {
catalogEntityRegistry.addOnBeforeRun( const onBeforeRunMock = asyncFn();
async (e) => {
expect(onRun).not.toBeCalled();
setTimeout(() => { catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
expect(onRun).not.toBeCalled();
done();
}, 500);
e.preventDefault();
},
);
render(<Catalog />); render(<Catalog />);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); 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) => { it("addOnRunHook return a promise and reject => onRun will be triggered", async () => {
catalogEntityRegistry.addOnBeforeRun( const onBeforeRunMock = asyncFn();
async () => {
setTimeout(() => {
expect(onRun).toHaveBeenCalled();
done();
}, 500);
throw new Error("rejection!"); catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
},
);
render(<Catalog />); render(<Catalog />);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
await onBeforeRunMock.reject();
expect(onRun).toHaveBeenCalled();
}); });
it("emits catalog open AppEvent", () => { it("emits catalog open AppEvent", () => {

View File

@ -4,17 +4,20 @@
*/ */
import React from "react"; import React from "react";
import { render } from "@testing-library/react";
import { SecretDetails } from "../secret-details"; import { SecretDetails } from "../secret-details";
import { Secret, SecretType } from "../../../../common/k8s-api/endpoints"; 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", () => ({ jest.mock("../../kube-object-meta/kube-object-meta", () => ({
KubeObjectMeta: () => null, KubeObjectMeta: () => null,
})); }));
describe("SecretDetails tests", () => { describe("SecretDetails tests", () => {
it("should show the visibility toggle when the secret value is ''", () => { it("should show the visibility toggle when the secret value is ''", () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
const render = renderFor(di);
const secret = new Secret({ const secret = new Secret({
apiVersion: "v1", apiVersion: "v1",
kind: "secret", kind: "secret",

View File

@ -20,6 +20,7 @@
padding-left: 8px; padding-left: 8px;
margin-right: -8px; margin-right: -8px;
padding-right: 8px; padding-right: 8px;
line-height: 1.1;
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;

View File

@ -4,15 +4,22 @@
*/ */
import React from "react"; 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 { NetworkPolicy } from "../../../../common/k8s-api/endpoints";
import { NetworkPolicyDetails } from "../network-policy-details"; import { NetworkPolicyDetails } from "../network-policy-details";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
jest.mock("../../kube-object-meta/kube-object-meta", () => ({ import type { DiRender } from "../../test-utils/renderFor";
KubeObjectMeta: () => null, import { renderFor } from "../../test-utils/renderFor";
}));
describe("NetworkPolicyDetails", () => { describe("NetworkPolicyDetails", () => {
let render: DiRender;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
render = renderFor(di);
});
it("should render w/o errors", () => { it("should render w/o errors", () => {
const policy = new NetworkPolicy({ const policy = new NetworkPolicy({
metadata: {} as never, metadata: {} as never,

View File

@ -31,6 +31,10 @@
flex-grow: 2; flex-grow: 2;
} }
&.node {
flex-grow: 2;
}
&.namespace { &.namespace {
flex-grow: 1.2; flex-grow: 1.2;
} }

View File

@ -22,6 +22,7 @@ import { showDetails } from "../kube-detail-params";
enum sortBy { enum sortBy {
name = "name", name = "name",
node = "node",
namespace = "namespace", namespace = "namespace",
cpu = "cpu", cpu = "cpu",
memory = "memory", memory = "memory",
@ -124,6 +125,7 @@ export class PodDetailsList extends React.Component<PodDetailsListProps> {
> >
<TableCell className="name">{pod.getName()}</TableCell> <TableCell className="name">{pod.getName()}</TableCell>
<TableCell className="warning"><KubeObjectStatusIcon key="icon" object={pod}/></TableCell> <TableCell className="warning"><KubeObjectStatusIcon key="icon" object={pod}/></TableCell>
<TableCell className="node">{pod.getNodeName()}</TableCell>
<TableCell className="namespace">{pod.getNs()}</TableCell> <TableCell className="namespace">{pod.getNs()}</TableCell>
<TableCell className="ready"> <TableCell className="ready">
{`${pod.getRunningContainers().length} / ${pod.getContainers().length}`} {`${pod.getRunningContainers().length} / ${pod.getContainers().length}`}
@ -165,6 +167,7 @@ export class PodDetailsList extends React.Component<PodDetailsListProps> {
virtualHeight={660} virtualHeight={660}
sortable={{ sortable={{
[sortBy.name]: pod => pod.getName(), [sortBy.name]: pod => pod.getName(),
[sortBy.node]: pod => pod.getNodeName(),
[sortBy.namespace]: pod => pod.getNs(), [sortBy.namespace]: pod => pod.getNs(),
[sortBy.cpu]: pod => podStore.getPodKubeMetrics(pod).cpu, [sortBy.cpu]: pod => podStore.getPodKubeMetrics(pod).cpu,
[sortBy.memory]: pod => podStore.getPodKubeMetrics(pod).memory, [sortBy.memory]: pod => podStore.getPodKubeMetrics(pod).memory,
@ -182,6 +185,7 @@ export class PodDetailsList extends React.Component<PodDetailsListProps> {
<TableHead> <TableHead>
<TableCell className="name" sortBy={sortBy.name}>Name</TableCell> <TableCell className="name" sortBy={sortBy.name}>Name</TableCell>
<TableCell className="warning"/> <TableCell className="warning"/>
<TableCell className="node" sortBy={sortBy.node}>Node</TableCell>
<TableCell className="namespace" sortBy={sortBy.namespace}>Namespace</TableCell> <TableCell className="namespace" sortBy={sortBy.namespace}>Namespace</TableCell>
<TableCell className="ready">Ready</TableCell> <TableCell className="ready">Ready</TableCell>
<TableCell className="cpu" sortBy={sortBy.cpu}>CPU</TableCell> <TableCell className="cpu" sortBy={sortBy.cpu}>CPU</TableCell>

View File

@ -116,6 +116,9 @@ export class PodDetails extends React.Component<PodDetailsProps> {
> >
{podIPs.map(label => <Badge key={label} label={label} />)} {podIPs.map(label => <Badge key={label} label={label} />)}
</DrawerItem> </DrawerItem>
<DrawerItem name="Service Account">
{pod.getServiceAccountName()}
</DrawerItem>
<DrawerItem name="Priority Class"> <DrawerItem name="Priority Class">
{pod.getPriorityClassName()} {pod.getPriorityClassName()}
</DrawerItem> </DrawerItem>

View File

@ -104,7 +104,7 @@ class NonInjectedReplicaSetScaleDialog extends Component<ReplicaSetScaleDialogPr
<Wizard <Wizard
header={( header={(
<h5> <h5>
Scale Replica Set {"Scale Replica Set "}
<span>{replicaSet.getName()}</span> <span>{replicaSet.getName()}</span>
</h5> </h5>
)} )}
@ -131,16 +131,16 @@ class NonInjectedReplicaSetScaleDialog extends Component<ReplicaSetScaleDialogPr
/> />
</div> </div>
<div className="plus-minus-container flex gaps"> <div className="plus-minus-container flex gaps">
<Icon
material="add_circle_outline"
onClick={this.desiredReplicasUp}
data-testid="desired-replicas-up"
/>
<Icon <Icon
material="remove_circle_outline" material="remove_circle_outline"
onClick={this.desiredReplicasDown} onClick={this.desiredReplicasDown}
data-testid="desired-replicas-down" data-testid="desired-replicas-down"
/> />
<Icon
material="add_circle_outline"
onClick={this.desiredReplicasUp}
data-testid="desired-replicas-up"
/>
</div> </div>
</div> </div>
{warning && ( {warning && (

View File

@ -180,6 +180,10 @@ a {
} }
} }
iframe {
color-scheme: auto;
}
// colors // colors
.success { .success {
color: var(--colorSuccess); color: var(--colorSuccess);

View File

@ -4,28 +4,38 @@
*/ */
import React from "react"; 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 { ClusterLocalTerminalSetting } from "../cluster-local-terminal-settings";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import { stat } from "fs/promises";
import { Notifications } from "../../../notifications";
import type { Stats } from "fs"; import type { Stats } from "fs";
import type { Cluster } from "../../../../../common/cluster/cluster"; import type { Cluster } from "../../../../../common/cluster/cluster";
import { getDiForUnitTesting } from "../../../../getDiForUnitTesting";
const mockStat = stat as jest.MockedFunction<typeof stat>; import type { DiRender } from "../../../test-utils/renderFor";
import { renderFor } from "../../../test-utils/renderFor";
jest.mock("fs", () => { import showErrorNotificationInjectable from "../../../notifications/show-error-notification.injectable";
const actual = jest.requireActual("fs"); import statInjectable from "../../../../../common/fs/stat/stat.injectable";
actual.promises.stat = jest.fn();
return actual;
});
jest.mock("../../../notifications");
describe("ClusterLocalTerminalSettings", () => { describe("ClusterLocalTerminalSettings", () => {
let render: DiRender;
let showErrorNotificationMock: jest.Mock;
let statMock: jest.Mock;
beforeEach(() => { 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(); jest.resetAllMocks();
}); });
@ -89,7 +99,7 @@ describe("ClusterLocalTerminalSettings", () => {
}); });
it("should save the new CWD if path is a directory", async () => { it("should save the new CWD if path is a directory", async () => {
mockStat.mockImplementation(async (path) => { statMock.mockImplementation(async (path) => {
expect(path).toBe("/foobar"); expect(path).toBe("/foobar");
return { return {
@ -114,7 +124,7 @@ describe("ClusterLocalTerminalSettings", () => {
}); });
it("should not save the new CWD if path is a file", async () => { 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"); expect(path).toBe("/foobar");
return { return {
@ -136,6 +146,6 @@ describe("ClusterLocalTerminalSettings", () => {
userEvent.type(dn, "/foobar"); userEvent.type(dn, "/foobar");
userEvent.click(dom.baseElement); userEvent.click(dom.baseElement);
await waitFor(() => expect(Notifications.error).toBeCalled()); await waitFor(() => expect(showErrorNotificationMock).toHaveBeenCalled());
}); });
}); });

View File

@ -8,79 +8,25 @@ import { observer } from "mobx-react";
import type { Cluster } from "../../../../common/cluster/cluster"; import type { Cluster } from "../../../../common/cluster/cluster";
import { Input } from "../../input"; import { Input } from "../../input";
import { SubTitle } from "../../layout/sub-title"; import { SubTitle } from "../../layout/sub-title";
import { stat } from "fs/promises"; import type { ShowNotification } from "../../notifications";
import { Notifications } from "../../notifications"; import { resolveTilde } from "../../../utils";
import { isErrnoException, resolveTilde } from "../../../utils";
import { Icon } from "../../icon"; import { Icon } from "../../icon";
import { PathPicker } from "../../path-picker"; import { PathPicker } from "../../path-picker";
import { isWindows } from "../../../../common/vars"; import { isWindows } from "../../../../common/vars";
import type { Stats } from "fs"; import { withInjectables } from "@ogre-tools/injectable-react";
import logger from "../../../../common/logger"; import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
import { lowerFirst } from "lodash"; import type { ValidateDirectory } from "../../../../common/fs/validate-directory.injectable";
import validateDirectoryInjectable from "../../../../common/fs/validate-directory.injectable";
export interface ClusterLocalTerminalSettingProps { export interface ClusterLocalTerminalSettingProps {
cluster: Cluster; cluster: Cluster;
} }
interface Dependencies {
function getUserReadableFileType(stats: Stats): string { showErrorNotification: ShowNotification;
if (stats.isFile()) { validateDirectory: ValidateDirectory;
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 NonInjectedClusterLocalTerminalSetting = observer(({ cluster, showErrorNotification, validateDirectory }: Dependencies & ClusterLocalTerminalSettingProps) => {
* 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<string | false> {
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) => {
if (!cluster) { if (!cluster) {
return null; return null;
} }
@ -109,15 +55,15 @@ export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTe
cluster.preferences.terminalCWD = undefined; cluster.preferences.terminalCWD = undefined;
} else { } else {
const dir = resolveTilde(directory); const dir = resolveTilde(directory);
const errorMessage = await validateDirectory(dir); const result = await validateDirectory(dir);
if (errorMessage) { if (!result.callWasSuccessful) {
Notifications.error( showErrorNotification(
<> <>
<b>Terminal Working Directory</b> <b>Terminal Working Directory</b>
<p> <p>
{"Your changes were not saved because "} {"Your changes were not saved because "}
{errorMessage} {result.error}
</p> </p>
</>, </>,
); );
@ -166,6 +112,8 @@ export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTe
material="close" material="close"
title="Clear" title="Clear"
onClick={() => setAndCommitDirectory("")} onClick={() => setAndCommitDirectory("")}
smallest
style={{ marginRight: "var(--margin)" }}
/> />
) )
} }
@ -173,6 +121,7 @@ export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTe
material="folder" material="folder"
title="Pick from filesystem" title="Pick from filesystem"
onClick={openFilePicker} onClick={openFilePicker}
smallest
/> />
</> </>
)} )}
@ -200,3 +149,16 @@ export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTe
</> </>
); );
}); });
export const ClusterLocalTerminalSetting = withInjectables<Dependencies, ClusterLocalTerminalSettingProps>(
NonInjectedClusterLocalTerminalSetting,
{
getProps: (di, props) => ({
showErrorNotification: di.inject(showErrorNotificationInjectable),
validateDirectory: di.inject(validateDirectoryInjectable),
...props,
}),
},
);

View File

@ -19,7 +19,6 @@ exports[`kube-object-menu given kube object renders 1`] = `
<i <i
class="Icon material interactive focusable" class="Icon material interactive focusable"
tabindex="0" tabindex="0"
tooltip="Delete"
> >
<span <span
class="icon" class="icon"
@ -28,6 +27,9 @@ exports[`kube-object-menu given kube object renders 1`] = `
delete delete
</span> </span>
</i> </i>
<div>
Delete
</div>
<span <span
class="title" class="title"
> >
@ -59,7 +61,6 @@ exports[`kube-object-menu given kube object when removing kube object renders 1`
<i <i
class="Icon material interactive focusable" class="Icon material interactive focusable"
tabindex="0" tabindex="0"
tooltip="Delete"
> >
<span <span
class="icon" class="icon"
@ -68,6 +69,9 @@ exports[`kube-object-menu given kube object when removing kube object renders 1`
delete delete
</span> </span>
</i> </i>
<div>
Delete
</div>
<span <span
class="title" class="title"
> >
@ -148,7 +152,6 @@ exports[`kube-object-menu given kube object when rerendered with different kube
<i <i
class="Icon material interactive focusable" class="Icon material interactive focusable"
tabindex="0" tabindex="0"
tooltip="Delete"
> >
<span <span
class="icon" class="icon"
@ -157,6 +160,9 @@ exports[`kube-object-menu given kube object when rerendered with different kube
delete delete
</span> </span>
</i> </i>
<div>
Delete
</div>
<span <span
class="title" class="title"
> >
@ -185,7 +191,6 @@ exports[`kube-object-menu given kube object when rerendered with different kube
<i <i
class="Icon material interactive focusable" class="Icon material interactive focusable"
tabindex="0" tabindex="0"
tooltip="Delete"
> >
<span <span
class="icon" class="icon"
@ -194,6 +199,9 @@ exports[`kube-object-menu given kube object when rerendered with different kube
delete delete
</span> </span>
</i> </i>
<div>
Delete
</div>
<span <span
class="title" class="title"
> >
@ -277,7 +285,6 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob
<i <i
class="Icon material interactive focusable" class="Icon material interactive focusable"
tabindex="0" tabindex="0"
tooltip="Delete"
> >
<span <span
class="icon" class="icon"
@ -286,6 +293,9 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob
delete delete
</span> </span>
</i> </i>
<div>
Delete
</div>
<span <span
class="title" class="title"
> >
@ -369,7 +379,6 @@ exports[`kube-object-menu given kube object without namespace when removing kube
<i <i
class="Icon material interactive focusable" class="Icon material interactive focusable"
tabindex="0" tabindex="0"
tooltip="Delete"
> >
<span <span
class="icon" class="icon"
@ -378,6 +387,9 @@ exports[`kube-object-menu given kube object without namespace when removing kube
delete delete
</span> </span>
</i> </i>
<div>
Delete
</div>
<span <span
class="title" class="title"
> >

View File

@ -26,12 +26,6 @@ import createEditResourceTabInjectable from "../dock/edit-resource/edit-resource
import hideDetailsInjectable from "../kube-detail-params/hide-details.injectable"; import hideDetailsInjectable from "../kube-detail-params/hide-details.injectable";
import { kubeObjectMenuItemInjectionToken } from "./kube-object-menu-item-injection-token"; 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 // TODO: make `animated={false}` not required to make tests deterministic
describe("kube-object-menu", () => { describe("kube-object-menu", () => {
let di: DiContainer; let di: DiContainer;

View File

@ -49,6 +49,7 @@ html {
&__single-value { &__single-value {
color: var(--textColorSecondary); color: var(--textColorSecondary);
overflow: visible;
} }
&__indicator { &__indicator {

View File

@ -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 directoryForKubeConfigsInjectable from "../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token"; import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
import loggerInjectable from "../../common/logger.injectable"; import loggerInjectable from "../../common/logger.injectable";
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
const createClusterInjectable = getInjectable({ const createClusterInjectable = getInjectable({
id: "create-cluster", id: "create-cluster",
@ -16,6 +17,7 @@ const createClusterInjectable = getInjectable({
const dependencies: ClusterDependencies = { const dependencies: ClusterDependencies = {
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable), directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
logger: di.inject(loggerInjectable), logger: di.inject(loggerInjectable),
broadcastMessage: di.inject(broadcastMessageInjectable),
// TODO: Dismantle wrong abstraction // TODO: Dismantle wrong abstraction
// Note: "as never" to get around strictness in unnatural scenario // Note: "as never" to get around strictness in unnatural scenario

View File

@ -32,8 +32,6 @@ import { ApiManager } from "../common/k8s-api/api-manager";
import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable"; import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable";
import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable"; import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable";
import apiManagerInjectable from "../common/k8s-api/api-manager/manager.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 setupOnApiErrorListenersInjectable from "./api/setup-on-api-errors.injectable";
import { observable, computed } from "mobx"; import { observable, computed } from "mobx";
import defaultShellInjectable from "./components/+preferences/default-shell.injectable"; import defaultShellInjectable from "./components/+preferences/default-shell.injectable";
@ -164,11 +162,6 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
di.override(maximizeWindowInjectable, () => () => {}); di.override(maximizeWindowInjectable, () => () => {});
di.override(toggleMaximizeWindowInjectable, () => () => {}); di.override(toggleMaximizeWindowInjectable, () => () => {});
di.override(ipcRendererInjectable, () => ({
invoke: () => {},
on: () => {},
}) as unknown as IpcRenderer);
overrideFunctionalInjectables(di, [ overrideFunctionalInjectables(di, [
broadcastMessageInjectable, broadcastMessageInjectable,
getFilePathsInjectable, getFilePathsInjectable,

View File

@ -4,7 +4,6 @@
*/ */
import type { OpenDialogOptions } from "electron"; import type { OpenDialogOptions } from "electron";
import { ipcRenderer } from "electron";
import { clusterActivateHandler, clusterClearDeletingHandler, clusterDeleteHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterSetDeletingHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster"; import { clusterActivateHandler, clusterClearDeletingHandler, clusterDeleteHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterSetDeletingHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster";
import type { ClusterId, ClusterState } from "../../common/cluster-types"; import type { ClusterId, ClusterState } from "../../common/cluster-types";
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel, type WindowAction } from "../../common/ipc/window"; 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 type { LensExtensionId } from "../../extensions/lens-extension";
import { toJS } from "../utils"; import { toJS } from "../utils";
import type { Location } from "history"; 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[]) { function requestMain(channel: string, ...args: any[]) {
const di = getLegacyGlobalDiForExtensionApi();
const ipcRenderer = di.inject(ipcRendererInjectable);
return ipcRenderer.invoke(channel, ...args.map(toJS)); return ipcRenderer.invoke(channel, ...args.map(toJS));
} }
function emitToMain(channel: string, ...args: any[]) { function emitToMain(channel: string, ...args: any[]) {
const di = getLegacyGlobalDiForExtensionApi();
const ipcRenderer = di.inject(ipcRendererInjectable);
return ipcRenderer.send(channel, ...args.map(toJS)); return ipcRenderer.send(channel, ...args.map(toJS));
} }

View File

@ -9,10 +9,7 @@ import { once } from "lodash";
import type { ClusterFrameContext } from "../cluster-frame-context/cluster-frame-context"; import type { ClusterFrameContext } from "../cluster-frame-context/cluster-frame-context";
import logger from "../../common/logger"; import logger from "../../common/logger";
import type { KubeObjectStoreLoadAllParams, KubeObjectStoreSubscribeParams } from "../../common/k8s-api/kube-object.store"; import type { KubeObjectStoreLoadAllParams, KubeObjectStoreSubscribeParams } from "../../common/k8s-api/kube-object.store";
import type { RequestInit } from "node-fetch"; import AbortController from "abort-controller";
// TODO: upgrade node-fetch once we are starting to use ES modules
type LegacyAbortSignal = NonNullable<RequestInit["signal"]>;
// Kubernetes watch-api client // Kubernetes watch-api client
// API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams // 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) => { const loadThenSubscribe = async (namespaces: string[] | undefined) => {
try { 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 })); unsubscribe.push(store.subscribe({ onLoadFailure, abortController: childController }));
} catch (error) { } catch (error) {
if (!(error instanceof DOMException)) { if (!(error instanceof DOMException)) {

View File

@ -5,8 +5,8 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable"; import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
import { LensProtocolRouterRenderer } from "./lens-protocol-router-renderer"; import { LensProtocolRouterRenderer } from "./lens-protocol-router-renderer";
import extensionsStoreInjectable import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
from "../../../extensions/extensions-store/extensions-store.injectable"; import loggerInjectable from "../../../common/logger.injectable";
const lensProtocolRouterRendererInjectable = getInjectable({ const lensProtocolRouterRendererInjectable = getInjectable({
id: "lens-protocol-router-renderer", id: "lens-protocol-router-renderer",
@ -15,6 +15,7 @@ const lensProtocolRouterRendererInjectable = getInjectable({
new LensProtocolRouterRenderer({ new LensProtocolRouterRenderer({
extensionLoader: di.inject(extensionLoaderInjectable), extensionLoader: di.inject(extensionLoaderInjectable),
extensionsStore: di.inject(extensionsStoreInjectable), extensionsStore: di.inject(extensionsStoreInjectable),
logger: di.inject(loggerInjectable),
}), }),
}); });

View File

@ -8,10 +8,9 @@ import { ipcRenderer } from "electron";
import * as proto from "../../../common/protocol-handler"; import * as proto from "../../../common/protocol-handler";
import Url from "url-parse"; import Url from "url-parse";
import { onCorrect } from "../../../common/ipc"; import { onCorrect } from "../../../common/ipc";
import type { LensProtocolRouterDependencies } from "../../../common/protocol-handler";
import { foldAttemptResults, ProtocolHandlerInvalid, RouteAttempt } from "../../../common/protocol-handler"; import { foldAttemptResults, ProtocolHandlerInvalid, RouteAttempt } from "../../../common/protocol-handler";
import { Notifications } from "../../components/notifications"; 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] { function verifyIpcArgs(args: unknown[]): args is [string, RouteAttempt] {
if (args.length !== 2) { if (args.length !== 2) {
@ -32,11 +31,7 @@ function verifyIpcArgs(args: unknown[]): args is [string, RouteAttempt] {
} }
} }
interface Dependencies { interface Dependencies extends LensProtocolRouterDependencies {}
extensionLoader: ExtensionLoader;
extensionsStore: ExtensionsStore;
}
export class LensProtocolRouterRenderer extends proto.LensProtocolRouter { export class LensProtocolRouterRenderer extends proto.LensProtocolRouter {
constructor(protected dependencies: Dependencies) { constructor(protected dependencies: Dependencies) {

View File

@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import 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);

449
yarn.lock
View File

@ -7,6 +7,11 @@
resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876" resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876"
integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== 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": "@ampproject/remapping@^2.1.0":
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" 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" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.53.tgz#251b4cd6760fadb4d68a05815e6dc5e432d69cd6"
integrity sha512-W2dAL6Bnyn4xa/QRSU3ilIK4EzD5wgYXKXJiS1HDF5vU3675qc2bvFyLwbUcdmssDveyndy7FbitrCoiV/eMLg== 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": "@eslint/eslintrc@^1.3.0":
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f"
@ -1442,101 +1452,101 @@
dependencies: dependencies:
"@sinonjs/commons" "^1.7.0" "@sinonjs/commons" "^1.7.0"
"@swc/core-android-arm-eabi@1.2.223": "@swc/core-android-arm-eabi@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-android-arm-eabi/-/core-android-arm-eabi-1.2.223.tgz#5d5ae572c90eb2e19f46c1ef56aab2e3fa7101fe" resolved "https://registry.yarnpkg.com/@swc/core-android-arm-eabi/-/core-android-arm-eabi-1.2.242.tgz#3ae5d8b178a0835ae0878094175d943f2d894bec"
integrity sha512-Hy/ya4oy80Ay70H9vhA8W0/FU9aQ/oQjvZ/on+wcNMATAiU9tk47i73LtPM01GruNiYJOwFcf2XWjlTpq5a0BQ== integrity sha512-Ukx1LQAUbPRJdREF9FMgeUwIuRtWJNpPyPF7BWl4hIkw024q75mohMbp3S2wgrF1TsSsEGW37q0DkFxPJ2uJbQ==
dependencies: dependencies:
"@swc/wasm" "1.2.122" "@swc/wasm" "1.2.122"
"@swc/core-android-arm64@1.2.223": "@swc/core-android-arm64@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-android-arm64/-/core-android-arm64-1.2.223.tgz#13c1751f9af36525adbb420098a4c74765afa7c4" resolved "https://registry.yarnpkg.com/@swc/core-android-arm64/-/core-android-arm64-1.2.242.tgz#2c1885c08dd5720991a6fa7585d39a93df98e773"
integrity sha512-qujrIXDBMWcPcdtTG/r+RNVBU5rg2Sk9Vg+U4FybX3c34rIyX2QYu5sxwM/HIGfd6wCbt5lyFZOvgSY000MTNw== integrity sha512-4E/y+reQWHVCV/0Sn174gsLQyqIKlBWKnwUfPa7MA53VBacp8HTYoPY+iwKPrngsH16gEOC7iByiTJHR/4kirg==
dependencies: dependencies:
"@swc/wasm" "1.2.130" "@swc/wasm" "1.2.130"
"@swc/core-darwin-arm64@1.2.223": "@swc/core-darwin-arm64@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.223.tgz#44ee2d1fbf9350c6aeda1983a384514272a9b2b7" resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.242.tgz#1b8b16a132cc354ea3b31d26c46908dae2fe41ed"
integrity sha512-CX32sRhAnFj3fJI6V4vdu5IUV5frEZNZM6hIPUs1UuVpxyuto9IZwd2y7/ACItB5RipA3VDL/c7jrFdSmfrgzg== integrity sha512-nIqtjxdbz0Fe0gFZwCygBwUrGEXj3c4mjHjNeveidVX/6U0HE/EAj+0iXuw8zjJLof8HCMnxq8CzzvhA6gd3ZA==
"@swc/core-darwin-x64@1.2.223": "@swc/core-darwin-x64@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.2.223.tgz#a157820e158d22c9b443821d15258bc58978e154" resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.2.242.tgz#cde041d520fcfb0865f49b395bb2c76af8ec3f3a"
integrity sha512-5FVQgWtqMmpOtky0JLTIF4a1WiAkuDOe5mwgzpi8nZ7nCxNo/DNThBbnIDlNhrR4M/1M6wzPshn1wNivvD7MQw== integrity sha512-iZKzI76vYYHD/t8wkQ/uIVuIyxN1eift2nLvUU7/jtmoa6b8DH/45ykB/C3vkuvYVNMiGA8HIjJIzw7RJz5XIQ==
"@swc/core-freebsd-x64@1.2.223": "@swc/core-freebsd-x64@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.223.tgz#ec9b300860f378aeb183084d89ba65009910d9f9" resolved "https://registry.yarnpkg.com/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.242.tgz#a95827311424dd86190fdb73bec996d24188c6d3"
integrity sha512-5oumS+YZyOMMKc5D3Bvf/541SF8n4b8LQ5x4WFA2CdAzD/jCgphE0IoAZ0u3bHz9S6Tl6Emu11V+/ALHE1oUew== integrity sha512-6JNi5/6JDvcTQzBkndELiIlJufWowoI2ZEmXlGIJpiGoj28PEDPwy5LO7KkXa4DnY5L4CSh15idFO/DxV0rGAQ==
dependencies: dependencies:
"@swc/wasm" "1.2.130" "@swc/wasm" "1.2.130"
"@swc/core-linux-arm-gnueabihf@1.2.223": "@swc/core-linux-arm-gnueabihf@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.223.tgz#3403de3c9402e1eeca68b1c134ea574739c7c49c" resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.242.tgz#1b0bd0ba96c59a9c4b87668521ce8ba55c8c7f55"
integrity sha512-osYjVijq101xZjwPUKR6AUib1LZU9laaM3aEOyElAi8cHolsZEp8D9ynr7cSWFUZJuzpTlY7iuJeY3FszdWrJA== integrity sha512-NGL9A3cv8PCbeQ1SvPfApNlHvFbf7Jn305sCAy3iZYsmwm+EU4JNlOWXGgRioP7ABhz2kwLhfYs8UMYCDIVq8Q==
dependencies: dependencies:
"@swc/wasm" "1.2.130" "@swc/wasm" "1.2.130"
"@swc/core-linux-arm64-gnu@1.2.223": "@swc/core-linux-arm64-gnu@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.223.tgz#d0a2d5dd01c5744950e57ec59aa4696bbabf02e4" resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.242.tgz#f97c1b655779788fff9a035f6a80180b1545423b"
integrity sha512-JZdPZIZzkJ6R+XB0lCnL0eD9VK/JfpZgKBqR3Gur9Fxs8Ea9p1HhZHSEAJ2T2YwV629dYjXwKqraOkLQrEMzCg== integrity sha512-OJ0kAjgeoRDJlo6Rvd2GnJ92tiIndmC/8krD9gfnQEyAgpR+jajOxbKhyBN/QZPyD2q/TG2LPqxhGYZ79q5mWQ==
"@swc/core-linux-arm64-musl@1.2.223": "@swc/core-linux-arm64-musl@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.223.tgz#667bd6583f93909736600acdd9d5cc4c6141d577" resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.242.tgz#ad77a5c7fc79d42d64970ca1886eb9d626388ca2"
integrity sha512-9BVDH5Cq+VlAuJrshCgxWgziLEGzShZ2OVZ7SEA/+md1y69x2VdMR9lMSfD/EXqb6AJAaFODRe20Irtppeqr2Q== integrity sha512-VqnHSYb1a6xW5ARUx9kq88s1S3XvCw9TvQXsPcN4e5qsugrLzxWLnqIM6VnWW06prxN7pYlWo9QtrtdPfbppmA==
"@swc/core-linux-x64-gnu@1.2.223": "@swc/core-linux-x64-gnu@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.223.tgz#c29788110fe1aa7fb86332178dcd1606368fd2c2" resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.242.tgz#4c7f2c483876a4b0755a263133c25413fa69df88"
integrity sha512-Z+KAxSpUDNEPfjOKX/tZk67StvzIyAhTc5FPWoVhx5CBlkGQaDBRl1TNmb1wdne/WF9xVkx6wz22pvNevX5fig== integrity sha512-DDqVJh0KpgHb+E0563+6PqAYDzYTSwgZXF/fOULwlHC7Yt50a9+ecisTFSHkWc74zPMtq27kMTuZyyLeD3gu7A==
"@swc/core-linux-x64-musl@1.2.223": "@swc/core-linux-x64-musl@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.223.tgz#24b7001530d4e09d7bae58c704ac79a6593bb9c1" resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.242.tgz#48f8769094cfde9d78dc32936666575470583b2a"
integrity sha512-3EkAOA0KQdm7Rw/0L5odtDKAtmzhgF7FKTL+zZb+s0ju5oMwFGN+XIIwUQdPSf11Ej3ezjHjHTFTlv0xqutfuA== integrity sha512-P+9sWgd5eZ6kS1WxOJbCeSgWY7mLP742PhwAzpFrJqCq5nx8Q4FYo4L5mOVNAheYDWldsxR1nKXR1RIMK3S2Lw==
"@swc/core-win32-arm64-msvc@1.2.223": "@swc/core-win32-arm64-msvc@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.223.tgz#8902c20d3d7ed2c9741ff0e227581d26ce8e0497" resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.242.tgz#e421a5a7a49d1effa71a22fea22a65256b447108"
integrity sha512-n8LWkej30hvfvazrJgwS6kwBZXMFCevLiRsZmP8O4hpC9b1wfAa+KLm4nHOR+J8jwF7LEjiERdU6tbIWZz0Tnw== integrity sha512-W5cevrf5aDJzdE++XeQi1BJKuigC3dlG2NaBUyt3inmep7nli6eoBJdj9Vyg5EPfFOdeI6wQiwOpFvQRoAle8Q==
dependencies: dependencies:
"@swc/wasm" "1.2.130" "@swc/wasm" "1.2.130"
"@swc/core-win32-ia32-msvc@1.2.223": "@swc/core-win32-ia32-msvc@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.223.tgz#591405f9fa07915e9d0b94a4174a5745621fe580" resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.242.tgz#1b836f5872195fef506a094eae7734b5fd28a1b5"
integrity sha512-kEDGFFUC6xPqCom03QtR+76Ptwtf8RABI4FqRdvrvbasw9zj0xkuLSDCvqL72zdOZCWRciiFijQVHfndLByMAQ== integrity sha512-XRQcgChvY9333hBre9F53EbiVfVu5MkSH4+XIiNMK14Jg8EqQ1nOcd+jvv2sEdEVbufCmBbWNjofUrCoQey60w==
dependencies: dependencies:
"@swc/wasm" "1.2.130" "@swc/wasm" "1.2.130"
"@swc/core-win32-x64-msvc@1.2.223": "@swc/core-win32-x64-msvc@1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.223.tgz#7a4549cedb7c0e7a3d4861c2b5230224194e0f04" resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.242.tgz#61a6c7d4da1dec1188b912785bd8e0bb16ff8440"
integrity sha512-nzL8rwzMFA9cBK2s+QBMPcNnoGSPMfgY9ypRw/nTp0hQDgdLOXHy9moGFJg8dbdQD39kC5s8yQ0BmyKvePILgg== integrity sha512-Cz1hZOxcfEVgzEr2sYIW9MxT+wEEbYz7aB87ZDmTUpr7vuvBiLMwsYItm8qG847wZeJfa+J7CC+tty5GJOBOOQ==
"@swc/core@^1.2.223": "@swc/core@^1.2.242":
version "1.2.223" version "1.2.242"
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.2.223.tgz#e44a3f6971a1f5c22d1037cf60510ef32c93d2a5" resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.2.242.tgz#4392ef0012fe9667440c6eb5a419b6cc86a0a786"
integrity sha512-LcKX1frJ1iJDSYlY9Bg0vm0rYsXloITh6PdEYM5amT73J9mC1c2YpWLnWQiH2QpcyblyMhX1pk1eZ2JZjaynrQ== integrity sha512-JQqSYVoLtHtztCNBgeCKyxmqw6AksHsC4WvVSSErLXJx6JXKaog1HFVuzd6rwx2lLCV+zBnbqJFug5OX0g2knw==
optionalDependencies: optionalDependencies:
"@swc/core-android-arm-eabi" "1.2.223" "@swc/core-android-arm-eabi" "1.2.242"
"@swc/core-android-arm64" "1.2.223" "@swc/core-android-arm64" "1.2.242"
"@swc/core-darwin-arm64" "1.2.223" "@swc/core-darwin-arm64" "1.2.242"
"@swc/core-darwin-x64" "1.2.223" "@swc/core-darwin-x64" "1.2.242"
"@swc/core-freebsd-x64" "1.2.223" "@swc/core-freebsd-x64" "1.2.242"
"@swc/core-linux-arm-gnueabihf" "1.2.223" "@swc/core-linux-arm-gnueabihf" "1.2.242"
"@swc/core-linux-arm64-gnu" "1.2.223" "@swc/core-linux-arm64-gnu" "1.2.242"
"@swc/core-linux-arm64-musl" "1.2.223" "@swc/core-linux-arm64-musl" "1.2.242"
"@swc/core-linux-x64-gnu" "1.2.223" "@swc/core-linux-x64-gnu" "1.2.242"
"@swc/core-linux-x64-musl" "1.2.223" "@swc/core-linux-x64-musl" "1.2.242"
"@swc/core-win32-arm64-msvc" "1.2.223" "@swc/core-win32-arm64-msvc" "1.2.242"
"@swc/core-win32-ia32-msvc" "1.2.223" "@swc/core-win32-ia32-msvc" "1.2.242"
"@swc/core-win32-x64-msvc" "1.2.223" "@swc/core-win32-x64-msvc" "1.2.242"
"@swc/jest@^0.2.22": "@swc/jest@^0.2.22":
version "0.2.22" version "0.2.22"
@ -1621,16 +1631,16 @@
lz-string "^1.4.4" lz-string "^1.4.4"
pretty-format "^26.6.2" pretty-format "^26.6.2"
"@testing-library/jest-dom@^5.16.4": "@testing-library/jest-dom@^5.16.5":
version "5.16.4" version "5.16.5"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e"
integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA== integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==
dependencies: dependencies:
"@adobe/css-tools" "^4.0.1"
"@babel/runtime" "^7.9.2" "@babel/runtime" "^7.9.2"
"@types/testing-library__jest-dom" "^5.9.1" "@types/testing-library__jest-dom" "^5.9.1"
aria-query "^5.0.0" aria-query "^5.0.0"
chalk "^3.0.0" chalk "^3.0.0"
css "^3.0.0"
css.escape "^1.5.1" css.escape "^1.5.1"
dom-accessibility-api "^0.5.6" dom-accessibility-api "^0.5.6"
lodash "^4.17.15" lodash "^4.17.15"
@ -2063,10 +2073,10 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/lodash@^4.14.181": "@types/lodash@^4.14.184":
version "4.14.182" version "4.14.184"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.184.tgz#23f96cd2a21a28e106dc24d825d4aa966de7a9fe"
integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== integrity sha512-RoZphVtHbxPZizt4IcILciSWiC6dcn+eZ8oX9IWEYfDMcocdd42f7NPI6fQj+6zI8y4E0L7gu2pcZKLGTRaV9Q==
"@types/marked@^4.0.3": "@types/marked@^4.0.3":
version "4.0.3" version "4.0.3"
@ -2139,15 +2149,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
"@types/node@^16.11.26": "@types/node@^16.11.26", "@types/node@^16.11.55":
version "16.11.34" version "16.11.55"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.34.tgz#520224e4be4448c279ecad09639ab460cc441a50" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.55.tgz#4b1e4fa4238b083cf0d0b4ad9077629123950caa"
integrity sha512-UrWGDyLAlQ2Z8bNOGWTsqbP9ZcBeTYBVuTRNxXTztBy5KhWUFI3BaeDWoCP/CzV/EVGgO1NTYzv9ZytBI9GAEw== integrity sha512-ZZepNkqPNCBy6PlCjeOY0gI1q91v7l5MUhVc5RMAUV39OxRO8lF8fqGnhY2j8FWz8fxcN8HvAUWoccWpOzl/Ug==
"@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/npm@^2.0.32": "@types/npm@^2.0.32":
version "2.0.32" version "2.0.32"
@ -2492,10 +2497,10 @@
dependencies: dependencies:
webpack-dev-server "*" webpack-dev-server "*"
"@types/webpack-env@^1.17.0": "@types/webpack-env@^1.18.0":
version "1.17.0" version "1.18.0"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.17.0.tgz#f99ce359f1bfd87da90cc4a57cab0a18f34a48d0" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.18.0.tgz#ed6ecaa8e5ed5dfe8b2b3d00181702c9925f13fb"
integrity sha512-eHSaNYEyxRA5IAG0Ym/yCyf86niZUIF/TpWKofQI/CVfh5HsMEUyfE2kwFxha4ow0s5g0LfISQxpDKjbRDrizw== integrity sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg==
"@types/webpack-node-externals@^2.5.3": "@types/webpack-node-externals@^2.5.3":
version "2.5.3" version "2.5.3"
@ -2575,14 +2580,14 @@
dependencies: dependencies:
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^5.32.0": "@typescript-eslint/eslint-plugin@^5.34.0":
version "5.32.0" version "5.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.32.0.tgz#e27e38cffa4a61226327c874a7be965e9a861624" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.34.0.tgz#d690f60e335596f38b01792e8f4b361d9bd0cb35"
integrity sha512-CHLuz5Uz7bHP2WgVlvoZGhf0BvFakBJKAD/43Ty0emn4wXWv5k01ND0C0fHcl/Im8Td2y/7h44E9pca9qAu2ew== integrity sha512-eRfPPcasO39iwjlUAMtjeueRGuIrW3TQ9WseIDl7i5UWuFbf83yYaU7YPs4j8+4CxUMIsj1k+4kV+E+G+6ypDQ==
dependencies: dependencies:
"@typescript-eslint/scope-manager" "5.32.0" "@typescript-eslint/scope-manager" "5.34.0"
"@typescript-eslint/type-utils" "5.32.0" "@typescript-eslint/type-utils" "5.34.0"
"@typescript-eslint/utils" "5.32.0" "@typescript-eslint/utils" "5.34.0"
debug "^4.3.4" debug "^4.3.4"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
ignore "^5.2.0" ignore "^5.2.0"
@ -2608,20 +2613,20 @@
"@typescript-eslint/types" "5.31.0" "@typescript-eslint/types" "5.31.0"
"@typescript-eslint/visitor-keys" "5.31.0" "@typescript-eslint/visitor-keys" "5.31.0"
"@typescript-eslint/scope-manager@5.32.0": "@typescript-eslint/scope-manager@5.34.0":
version "5.32.0" version "5.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.32.0.tgz#763386e963a8def470580cc36cf9228864190b95" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.34.0.tgz#14efd13dc57602937e25f188fd911f118781e527"
integrity sha512-KyAE+tUON0D7tNz92p1uetRqVJiiAkeluvwvZOqBmW9z2XApmk5WSMV9FrzOroAcVxJZB3GfUwVKr98Dr/OjOg== integrity sha512-HNvASMQlah5RsBW6L6c7IJ0vsm+8Sope/wu5sEAf7joJYWNb1LDbJipzmdhdUOnfrDFE6LR1j57x1EYVxrY4ow==
dependencies: dependencies:
"@typescript-eslint/types" "5.32.0" "@typescript-eslint/types" "5.34.0"
"@typescript-eslint/visitor-keys" "5.32.0" "@typescript-eslint/visitor-keys" "5.34.0"
"@typescript-eslint/type-utils@5.32.0": "@typescript-eslint/type-utils@5.34.0":
version "5.32.0" version "5.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.32.0.tgz#45a14506fe3fb908600b4cef2f70778f7b5cdc79" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.34.0.tgz#7a324ab9ddd102cd5e1beefc94eea6f3eb32d32d"
integrity sha512-0gSsIhFDduBz3QcHJIp3qRCvVYbqzHg8D6bHFsDMrm0rURYDj+skBK2zmYebdCp+4nrd9VWd13egvhYFJj/wZg== integrity sha512-Pxlno9bjsQ7hs1pdWRUv9aJijGYPYsHpwMeCQ/Inavhym3/XaKt1ZKAA8FIw4odTBfowBdZJDMxf2aavyMDkLg==
dependencies: dependencies:
"@typescript-eslint/utils" "5.32.0" "@typescript-eslint/utils" "5.34.0"
debug "^4.3.4" debug "^4.3.4"
tsutils "^3.21.0" tsutils "^3.21.0"
@ -2630,10 +2635,10 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.31.0.tgz#7aa389122b64b18e473c1672fb3b8310e5f07a9a" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.31.0.tgz#7aa389122b64b18e473c1672fb3b8310e5f07a9a"
integrity sha512-/f/rMaEseux+I4wmR6mfpM2wvtNZb1p9hAV77hWfuKc3pmaANp5dLAZSiE3/8oXTYTt3uV9KW5yZKJsMievp6g== integrity sha512-/f/rMaEseux+I4wmR6mfpM2wvtNZb1p9hAV77hWfuKc3pmaANp5dLAZSiE3/8oXTYTt3uV9KW5yZKJsMievp6g==
"@typescript-eslint/types@5.32.0": "@typescript-eslint/types@5.34.0":
version "5.32.0" version "5.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.32.0.tgz#484273021eeeae87ddb288f39586ef5efeb6dcd8" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.34.0.tgz#217bf08049e9e7b86694d982e88a2c1566330c78"
integrity sha512-EBUKs68DOcT/EjGfzywp+f8wG9Zw6gj6BjWu7KV/IYllqKJFPlZlLSYw/PTvVyiRw50t6wVbgv4p9uE2h6sZrQ== integrity sha512-49fm3xbbUPuzBIOcy2CDpYWqy/X7VBkxVN+DC21e0zIm3+61Z0NZi6J9mqPmSW1BDVk9FIOvuCFyUPjXz93sjA==
"@typescript-eslint/typescript-estree@5.31.0": "@typescript-eslint/typescript-estree@5.31.0":
version "5.31.0" version "5.31.0"
@ -2648,28 +2653,28 @@
semver "^7.3.7" semver "^7.3.7"
tsutils "^3.21.0" tsutils "^3.21.0"
"@typescript-eslint/typescript-estree@5.32.0": "@typescript-eslint/typescript-estree@5.34.0":
version "5.32.0" version "5.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.32.0.tgz#282943f34babf07a4afa7b0ff347a8e7b6030d12" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.34.0.tgz#ba7b83f4bf8ccbabf074bbf1baca7a58de3ccb9a"
integrity sha512-ZVAUkvPk3ITGtCLU5J4atCw9RTxK+SRc6hXqLtllC2sGSeMFWN+YwbiJR9CFrSFJ3w4SJfcWtDwNb/DmUIHdhg== integrity sha512-mXHAqapJJDVzxauEkfJI96j3D10sd567LlqroyCeJaHnu42sDbjxotGb3XFtGPYKPD9IyLjhsoULML1oI3M86A==
dependencies: dependencies:
"@typescript-eslint/types" "5.32.0" "@typescript-eslint/types" "5.34.0"
"@typescript-eslint/visitor-keys" "5.32.0" "@typescript-eslint/visitor-keys" "5.34.0"
debug "^4.3.4" debug "^4.3.4"
globby "^11.1.0" globby "^11.1.0"
is-glob "^4.0.3" is-glob "^4.0.3"
semver "^7.3.7" semver "^7.3.7"
tsutils "^3.21.0" tsutils "^3.21.0"
"@typescript-eslint/utils@5.32.0": "@typescript-eslint/utils@5.34.0":
version "5.32.0" version "5.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.32.0.tgz#eccb6b672b94516f1afc6508d05173c45924840c" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.34.0.tgz#0cae98f48d8f9e292e5caa9343611b6faf49e743"
integrity sha512-W7lYIAI5Zlc5K082dGR27Fczjb3Q57ECcXefKU/f0ajM5ToM0P+N9NmJWip8GmGu/g6QISNT+K6KYB+iSHjXCQ== integrity sha512-kWRYybU4Rn++7lm9yu8pbuydRyQsHRoBDIo11k7eqBWTldN4xUdVUMCsHBiE7aoEkFzrUEaZy3iH477vr4xHAQ==
dependencies: dependencies:
"@types/json-schema" "^7.0.9" "@types/json-schema" "^7.0.9"
"@typescript-eslint/scope-manager" "5.32.0" "@typescript-eslint/scope-manager" "5.34.0"
"@typescript-eslint/types" "5.32.0" "@typescript-eslint/types" "5.34.0"
"@typescript-eslint/typescript-estree" "5.32.0" "@typescript-eslint/typescript-estree" "5.34.0"
eslint-scope "^5.1.1" eslint-scope "^5.1.1"
eslint-utils "^3.0.0" eslint-utils "^3.0.0"
@ -2681,12 +2686,12 @@
"@typescript-eslint/types" "5.31.0" "@typescript-eslint/types" "5.31.0"
eslint-visitor-keys "^3.3.0" eslint-visitor-keys "^3.3.0"
"@typescript-eslint/visitor-keys@5.32.0": "@typescript-eslint/visitor-keys@5.34.0":
version "5.32.0" version "5.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.32.0.tgz#b9715d0b11fdb5dd10fd0c42ff13987470525394" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.34.0.tgz#d0fb3e31033e82ddd5de048371ad39eb342b2d40"
integrity sha512-S54xOHZgfThiZ38/ZGTgB2rqx51CMJ5MCfVT2IplK4Q7hgzGfe0nLzLCcenDnc/cSjP568hdeKfeDcBgqNHD/g== integrity sha512-O1moYjOSrab0a2fUvFpsJe0QHtvTC+cR+ovYpgKrAVXzqQyc74mv76TgY6z+aEtjQE2vgZux3CQVtGryqdcOAw==
dependencies: dependencies:
"@typescript-eslint/types" "5.32.0" "@typescript-eslint/types" "5.34.0"
eslint-visitor-keys "^3.3.0" eslint-visitor-keys "^3.3.0"
"@webassemblyjs/ast@1.11.1": "@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" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== 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: accepts@~1.3.4, accepts@~1.3.5:
version "1.3.7" version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 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" source-map-resolve "^0.5.2"
urix "^0.1.0" 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: cssesc@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" 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" jsonfile "^4.0.0"
mkdirp "^0.5.1" mkdirp "^0.5.1"
electron@^19.0.4: electron@^19.0.13:
version "19.0.4" version "19.0.13"
resolved "https://registry.yarnpkg.com/electron/-/electron-19.0.4.tgz#a88d5e542868c4abd7704228ec62c553605605a0" resolved "https://registry.yarnpkg.com/electron/-/electron-19.0.13.tgz#68bcf7d94f249dbae9a3d4d1794d45a24db666dc"
integrity sha512-roRYr1VNAWIhjD9n8qZdmhROtrzsFpuZEXrjWAw+GqPbZlrUInmvFCviRDC2Lt+VBsTNRpTfPpfzXSlLL4reEw== integrity sha512-11Ne0VJy8L1GU7sGcbJHhkAz73szR27uP4vmfUVGlppC/ipA39AUkdzqiQoPC/F1EJdjEOBvHySG8K8Xe9yETA==
dependencies: dependencies:
"@electron/get" "^1.14.1" "@electron/get" "^1.14.1"
"@types/node" "^16.11.26" "@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" resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.53.tgz#259bc3ef1399a3cad8f4f67c40ee20779c4de675"
integrity sha512-fIL93sOTnEU+NrTAVMIKiAw0YH22HWCAgg4N4Z6zov2t0kY9RAJ50zY9ZMCQ+RT6bnOfDt8gCTnt/RaSNA2yRA== 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: esbuild-android-arm64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.53.tgz#2158253d4e8f9fdd2a081bbb4f73b8806178841e" resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.53.tgz#2158253d4e8f9fdd2a081bbb4f73b8806178841e"
integrity sha512-PC7KaF1v0h/nWpvlU1UMN7dzB54cBH8qSsm7S9mkwFA1BXpaEOufCg8hdoEI1jep0KeO/rjZVWrsH8+q28T77A== 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: esbuild-darwin-64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.53.tgz#b4681831fd8f8d06feb5048acbe90d742074cc2a" resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.53.tgz#b4681831fd8f8d06feb5048acbe90d742074cc2a"
integrity sha512-gE7P5wlnkX4d4PKvLBUgmhZXvL7lzGRLri17/+CmmCzfncIgq8lOBvxGMiQ4xazplhxq+72TEohyFMZLFxuWvg== 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: esbuild-darwin-arm64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.53.tgz#d267d957852d121b261b3f76ead86e5b5463acc9" resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.53.tgz#d267d957852d121b261b3f76ead86e5b5463acc9"
integrity sha512-otJwDU3hnI15Q98PX4MJbknSZ/WSR1I45il7gcxcECXzfN4Mrpft5hBDHXNRnCh+5858uPXBXA1Vaz2jVWLaIA== 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: esbuild-freebsd-64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.53.tgz#aca2af6d72b537fe66a38eb8f374fb66d4c98ca0" resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.53.tgz#aca2af6d72b537fe66a38eb8f374fb66d4c98ca0"
integrity sha512-WkdJa8iyrGHyKiPF4lk0MiOF87Q2SkE+i+8D4Cazq3/iqmGPJ6u49je300MFi5I2eUsQCkaOWhpCVQMTKGww2w== 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: esbuild-freebsd-arm64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.53.tgz#76282e19312d914c34343c8a7da6cc5f051580b9" resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.53.tgz#76282e19312d914c34343c8a7da6cc5f051580b9"
integrity sha512-9T7WwCuV30NAx0SyQpw8edbKvbKELnnm1FHg7gbSYaatH+c8WJW10g/OdM7JYnv7qkimw2ZTtSA+NokOLd2ydQ== 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: esbuild-linux-32@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.53.tgz#1045d34cf7c5faaf2af3b29cc1573b06580c37e5" resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.53.tgz#1045d34cf7c5faaf2af3b29cc1573b06580c37e5"
integrity sha512-VGanLBg5en2LfGDgLEUxQko2lqsOS7MTEWUi8x91YmsHNyzJVT/WApbFFx3MQGhkf+XdimVhpyo5/G0PBY91zg== 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: esbuild-linux-64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.53.tgz#ab3f2ee2ebb5a6930c72d9539cb34b428808cbe4" resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.53.tgz#ab3f2ee2ebb5a6930c72d9539cb34b428808cbe4"
integrity sha512-pP/FA55j/fzAV7N9DF31meAyjOH6Bjuo3aSKPh26+RW85ZEtbJv9nhoxmGTd9FOqjx59Tc1ZbrJabuiXlMwuZQ== 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: esbuild-linux-arm64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.53.tgz#1f5530412f6690949e78297122350488d3266cfe" resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.53.tgz#1f5530412f6690949e78297122350488d3266cfe"
integrity sha512-GDmWITT+PMsjCA6/lByYk7NyFssW4Q6in32iPkpjZ/ytSyH+xeEx8q7HG3AhWH6heemEYEWpTll/eui3jwlSnw== 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: esbuild-linux-arm@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.53.tgz#a44ec9b5b42007ab6c0d65a224ccc6bbd97c54cf" resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.53.tgz#a44ec9b5b42007ab6c0d65a224ccc6bbd97c54cf"
integrity sha512-/u81NGAVZMopbmzd21Nu/wvnKQK3pT4CrvQ8BTje1STXcQAGnfyKgQlj3m0j2BzYbvQxSy+TMck4TNV2onvoPA== 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: esbuild-linux-mips64le@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.53.tgz#a4d0b6b17cfdeea4e41b0b085a5f73d99311be9f" resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.53.tgz#a4d0b6b17cfdeea4e41b0b085a5f73d99311be9f"
integrity sha512-d6/XHIQW714gSSp6tOOX2UscedVobELvQlPMkInhx1NPz4ThZI9uNLQ4qQJHGBGKGfu+rtJsxM4NVHLhnNRdWQ== 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: esbuild-linux-ppc64le@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.53.tgz#8c331822c85465434e086e3e6065863770c38139" resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.53.tgz#8c331822c85465434e086e3e6065863770c38139"
integrity sha512-ndnJmniKPCB52m+r6BtHHLAOXw+xBCWIxNnedbIpuREOcbSU/AlyM/2dA3BmUQhsHdb4w3amD5U2s91TJ3MzzA== 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: esbuild-linux-riscv64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.53.tgz#36fd75543401304bea8a2d63bf8ea18aaa508e00" resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.53.tgz#36fd75543401304bea8a2d63bf8ea18aaa508e00"
integrity sha512-yG2sVH+QSix6ct4lIzJj329iJF3MhloLE6/vKMQAAd26UVPVkhMFqFopY+9kCgYsdeWvXdPgmyOuKa48Y7+/EQ== 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: esbuild-linux-s390x@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.53.tgz#1622677ab6824123f48f75d3afc031cd41936129" resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.53.tgz#1622677ab6824123f48f75d3afc031cd41936129"
integrity sha512-OCJlgdkB+XPYndHmw6uZT7jcYgzmx9K+28PVdOa/eLjdoYkeAFvH5hTwX4AXGLZLH09tpl4bVsEtvuyUldaNCg== 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: esbuild-loader@^2.19.0:
version "2.19.0" version "2.19.0"
resolved "https://registry.yarnpkg.com/esbuild-loader/-/esbuild-loader-2.19.0.tgz#54f62d1da8262acfc3c5883c24da35af8324f116" 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" 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== 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: esbuild-openbsd-64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.53.tgz#9bcbbe6f86304872c6e91f64c8eb73fc29c3588b" resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.53.tgz#9bcbbe6f86304872c6e91f64c8eb73fc29c3588b"
integrity sha512-eKQ30ZWe+WTZmteDYg8S+YjHV5s4iTxeSGhJKJajFfQx9TLZJvsJX0/paqwP51GicOUruFpSUAs2NCc0a4ivQQ== 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: esbuild-sunos-64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.53.tgz#f7a872f7460bfb7b131f7188a95fbce3d1c577e8" resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.53.tgz#f7a872f7460bfb7b131f7188a95fbce3d1c577e8"
integrity sha512-OWLpS7a2FrIRukQqcgQqR1XKn0jSJoOdT+RlhAxUoEQM/IpytS3FXzCJM6xjUYtpO5GMY0EdZJp+ur2pYdm39g== 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: esbuild-windows-32@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.53.tgz#c5e3ca50e2d1439cc2c9fe4defa63bcd474ce709" resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.53.tgz#c5e3ca50e2d1439cc2c9fe4defa63bcd474ce709"
integrity sha512-m14XyWQP5rwGW0tbEfp95U6A0wY0DYPInWBB7D69FAXUpBpBObRoGTKRv36lf2RWOdE4YO3TNvj37zhXjVL5xg== 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: esbuild-windows-64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.53.tgz#ec2ab4a60c5215f092ffe1eab6d01319e88238af" resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.53.tgz#ec2ab4a60c5215f092ffe1eab6d01319e88238af"
integrity sha512-s9skQFF0I7zqnQ2K8S1xdLSfZFsPLuOGmSx57h2btSEswv0N0YodYvqLcJMrNMXh6EynOmWD7rz+0rWWbFpIHQ== 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: esbuild-windows-arm64@0.14.53:
version "0.14.53" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.53.tgz#f71d403806bdf9f4a1f9d097db9aec949bd675c8" resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.53.tgz#f71d403806bdf9f4a1f9d097db9aec949bd675c8"
integrity sha512-E+5Gvb+ZWts+00T9II6wp2L3KG2r3iGxByqd/a1RmLmYWVsSVUjkvIxZuJ3hYTIbhLkH5PRwpldGTKYqVz0nzQ== 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" version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.53.tgz#20b1007f686e8584f2a01a1bec5a37aac9498ce4" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.53.tgz#20b1007f686e8584f2a01a1bec5a37aac9498ce4"
integrity sha512-ohO33pUBQ64q6mmheX1mZ8mIXj8ivQY/L4oVuAshr+aJI+zLl+amrp3EodrUNDNYVrKJXGPfIHFGhO8slGRjuw== 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-64 "0.14.53"
esbuild-windows-arm64 "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: escalade@^3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 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" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 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: eventemitter3@^4.0.0:
version "4.0.7" version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" 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" klona "^2.0.4"
neo-async "^2.6.2" neo-async "^2.6.2"
sass@^1.32.13, sass@^1.54.2: sass@^1.32.13, sass@^1.54.5:
version "1.54.2" version "1.54.5"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.2.tgz#574cad83814c930ef2475921b9cb5d8203ae8867" resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.5.tgz#93708f5560784f6ff2eab8542ade021a4a947b3a"
integrity sha512-wbVV26sejsCIbBScZZtNkvnrB/bVCQ8hSlZ01D9nzsVh9zLqCkWrlpvTb3YEb6xsuNi9cx75hncqwikHFSz7tw== integrity sha512-p7DTOzxkUPa/63FU0R3KApkRHwcVZYC0PLnLm5iyZACyp15qSi32x7zVUhRdABAATmkALqgGrjCJAcWvobmhHw==
dependencies: dependencies:
chokidar ">=3.0.0 <4.0.0" chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0" immutable "^4.0.0"
@ -12394,14 +12529,6 @@ source-map-resolve@^0.5.2:
source-map-url "^0.4.0" source-map-url "^0.4.0"
urix "^0.1.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: source-map-support@0.5.13:
version "0.5.13" version "0.5.13"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" 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" range-parser "^1.2.1"
schema-utils "^4.0.0" schema-utils "^4.0.0"
webpack-dev-server@*, webpack-dev-server@^4.9.3: webpack-dev-server@*, webpack-dev-server@^4.10.0:
version "4.9.3" version "4.10.0"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz#2360a5d6d532acb5410a668417ad549ee3b8a3c9" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.10.0.tgz#de270d0009eba050546912be90116e7fd740a9ca"
integrity sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw== integrity sha512-7dezwAs+k6yXVFZ+MaL8VnE+APobiO3zvpp3rBHe/HmWQ+avwh0Q3d0xxacOiBybZZ3syTZw9HXzpa3YNbAZDQ==
dependencies: dependencies:
"@types/bonjour" "^3.5.9" "@types/bonjour" "^3.5.9"
"@types/connect-history-api-fallback" "^1.3.5" "@types/connect-history-api-fallback" "^1.3.5"