mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
make more injectable for testing
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
62af94bb2d
commit
28227c923d
@ -74,8 +74,7 @@
|
||||
"<rootDir>/src/extensions/npm"
|
||||
],
|
||||
"setupFiles": [
|
||||
"<rootDir>/src/jest.setup.ts",
|
||||
"jest-canvas-mock"
|
||||
"<rootDir>/src/jest.setup.ts"
|
||||
],
|
||||
"globalSetup": "<rootDir>/src/jest.timezone.ts",
|
||||
"setupFilesAfterEnv": [
|
||||
@ -354,6 +353,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
||||
"@typescript-eslint/parser": "^5.29.0",
|
||||
"ansi_up": "^5.1.0",
|
||||
"canvas": "^2.9.3",
|
||||
"chart.js": "^2.9.4",
|
||||
"circular-dependency-plugin": "^5.2.2",
|
||||
"cli-progress": "^3.11.2",
|
||||
@ -382,9 +382,9 @@
|
||||
"ignore-loader": "^0.1.2",
|
||||
"include-media": "^1.4.9",
|
||||
"jest": "^28.1.2",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"jest-environment-jsdom": "^28.1.1",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jest-image-snapshot": "^5.1.0",
|
||||
"jest-mock-extended": "^2.0.6",
|
||||
"make-plural": "^6.2.2",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
@ -404,6 +404,7 @@
|
||||
"react-select-event": "^5.5.0",
|
||||
"react-table": "^7.8.0",
|
||||
"react-window": "^1.8.7",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"sass": "^1.53.0",
|
||||
"sass-loader": "^12.6.0",
|
||||
"sharp": "^0.30.7",
|
||||
|
||||
17
src/common/fs/stat.injectable.ts
Normal file
17
src/common/fs/stat.injectable.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Stats } from "fs";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type Stat = (path: string) => Promise<Stats>;
|
||||
|
||||
const statInjectable = getInjectable({
|
||||
id: "stat",
|
||||
instantiate: (di): Stat => di.inject(fsInjectable).stat,
|
||||
});
|
||||
|
||||
export default statInjectable;
|
||||
18
src/common/user-store/resolved-shell.injectable.ts
Normal file
18
src/common/user-store/resolved-shell.injectable.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import userStoreInjectable from "./user-store.injectable";
|
||||
|
||||
const resolvedShellInjectable = getInjectable({
|
||||
id: "resolved-shell",
|
||||
instantiate: (di) => {
|
||||
const store = di.inject(userStoreInjectable);
|
||||
|
||||
return computed(() => store.shell || process.env.SHELL || process.env.PTYSHELL);
|
||||
},
|
||||
});
|
||||
|
||||
export default resolvedShellInjectable;
|
||||
@ -102,10 +102,6 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
|
||||
return semver.gt(getAppVersion(), this.lastSeenAppVersion);
|
||||
}
|
||||
|
||||
@computed get resolvedShell(): string | undefined {
|
||||
return this.shell || process.env.SHELL || process.env.PTYSHELL;
|
||||
}
|
||||
|
||||
startMainReactions() {
|
||||
// open at system start-up
|
||||
reaction(() => this.openAtLogin, openAtLogin => {
|
||||
|
||||
@ -4,22 +4,26 @@
|
||||
*/
|
||||
|
||||
export type Disposer = () => void;
|
||||
|
||||
interface Extendable<T> {
|
||||
push(...vals: T[]): void;
|
||||
export interface LibraryDisposers {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export type ExtendableDisposer = Disposer & Extendable<Disposer>;
|
||||
export interface ExtendableDisposer {
|
||||
(): void;
|
||||
push(...disposers: (Disposer | LibraryDisposers)[]): void;
|
||||
}
|
||||
|
||||
export function disposer(...args: (Disposer | undefined | null)[]): ExtendableDisposer {
|
||||
const res = () => {
|
||||
args.forEach(dispose => dispose?.());
|
||||
export function disposer(...args: (Disposer | LibraryDisposers | undefined | null)[]): ExtendableDisposer {
|
||||
return Object.assign(() => {
|
||||
args.forEach(d => {
|
||||
if (typeof d === "function") {
|
||||
d();
|
||||
} else if (d) {
|
||||
d.dispose();
|
||||
}
|
||||
});
|
||||
args.length = 0;
|
||||
};
|
||||
|
||||
res.push = (...vals: Disposer[]) => {
|
||||
args.push(...vals);
|
||||
};
|
||||
|
||||
return res;
|
||||
}, {
|
||||
push: (...vals) => args.push(...vals),
|
||||
} as Pick<ExtendableDisposer, "push">);
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import configurePackages from "./common/configure-packages";
|
||||
import { configure } from "mobx";
|
||||
import { setImmediate } from "timers";
|
||||
import { TextEncoder, TextDecoder as TextDecoderNode } from "util";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
|
||||
// setup default configuration for external npm-packages
|
||||
configurePackages();
|
||||
@ -36,3 +37,18 @@ process.on("unhandledRejection", (err: any) => {
|
||||
|
||||
global.TextEncoder = TextEncoder;
|
||||
global.TextDecoder = TextDecoderNode as unknown as typeof TextDecoder;
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation(query => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(), // Deprecated
|
||||
removeListener: jest.fn(), // Deprecated
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@ -101,6 +101,9 @@ import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
import electronInjectable from "./utils/resolve-system-proxy/electron.injectable";
|
||||
import type { HotbarStore } from "../common/hotbars/store";
|
||||
import focusApplicationInjectable from "./electron-app/features/focus-application.injectable";
|
||||
import getValidCwdInjectable from "./shell-session/get-valid-cwd.injectable";
|
||||
import getCachedShellEnvInjectable from "./shell-session/get-cached-shell-env.injectable";
|
||||
import spawnPtyInjectable from "./shell-session/spawn-pty.injectable";
|
||||
|
||||
export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) {
|
||||
const {
|
||||
@ -158,6 +161,16 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
di.override(applicationMenuInjectable, () => ({ start: () => {}, stop: () => {} }));
|
||||
|
||||
di.override(periodicalCheckForUpdatesInjectable, () => ({ start: () => {}, stop: () => {}, started: false }));
|
||||
di.override(getValidCwdInjectable, () => () => Promise.resolve("/some/valid/cwd"));
|
||||
di.override(getCachedShellEnvInjectable, (di) => ({ cluster }) => Promise.resolve({
|
||||
NO_PROXY: "localhost,127.0.0.1",
|
||||
TERM_PROGRAM: di.inject(appNameInjectable),
|
||||
TERM_PROGRAM_VERSION: di.inject(appVersionInjectable),
|
||||
KUBECONFIG: `/some/proxy/kubeconfig/${cluster.id}`,
|
||||
PTYPID: "12345",
|
||||
PTYSHELL: "zsh",
|
||||
PATH: process.env.PATH,
|
||||
}));
|
||||
|
||||
overrideFunctionalInjectables(di, [
|
||||
getHelmChartInjectable,
|
||||
@ -175,6 +188,7 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
readJsonFileInjectable,
|
||||
readFileInjectable,
|
||||
execFileInjectable,
|
||||
spawnPtyInjectable,
|
||||
]);
|
||||
|
||||
// TODO: Remove usages of globally exported appEventBus to get rid of this
|
||||
|
||||
12
src/main/shell-session/cached-shell-env.injectable.ts
Normal file
12
src/main/shell-session/cached-shell-env.injectable.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
const cachedShellEnvInjectable = getInjectable({
|
||||
id: "cached-shell-env",
|
||||
instantiate: () => new Map<string, Record<string, string | undefined>>(),
|
||||
});
|
||||
|
||||
export default cachedShellEnvInjectable;
|
||||
61
src/main/shell-session/ensure-shell-process.injectable.ts
Normal file
61
src/main/shell-session/ensure-shell-process.injectable.ts
Normal file
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 { IPty } from "node-pty";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import { getOrInsertWith } from "../../renderer/utils";
|
||||
import shellProcessesInjectable from "./shell-processes.injectable";
|
||||
import spawnPtyInjectable from "./spawn-pty.injectable";
|
||||
|
||||
export interface EnsuredShellProcess {
|
||||
shellProcess: IPty;
|
||||
resume: boolean;
|
||||
cleanup: () => void;
|
||||
}
|
||||
export interface EnsureShellProcessArgs {
|
||||
shell: string;
|
||||
args: string[];
|
||||
env: Record<string, string | undefined>;
|
||||
cwd: string;
|
||||
terminalId: string;
|
||||
}
|
||||
export type EnsureShellProcess = (args: EnsureShellProcessArgs) => EnsuredShellProcess;
|
||||
|
||||
const ensureShellProcessInjectable = getInjectable({
|
||||
id: "ensure-shell-process",
|
||||
instantiate: (di): EnsureShellProcess => {
|
||||
const shellProcesses = di.inject(shellProcessesInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const spawnPty = di.inject(spawnPtyInjectable);
|
||||
|
||||
return ({ shell, args, env, cwd, terminalId } ) => {
|
||||
const resume = shellProcesses.has(terminalId);
|
||||
const shellProcess = getOrInsertWith(shellProcesses, terminalId, () => (
|
||||
spawnPty(shell, args, {
|
||||
rows: 30,
|
||||
cols: 80,
|
||||
cwd,
|
||||
env: env as Record<string, string>,
|
||||
name: "xterm-256color",
|
||||
// TODO: Something else is broken here so we need to force the use of winPty on windows
|
||||
useConpty: false,
|
||||
})
|
||||
));
|
||||
|
||||
logger.info(`[SHELL-SESSION]: PTY for ${terminalId} is ${resume ? "resumed" : "started"} with PID=${shellProcess.pid}`);
|
||||
|
||||
return {
|
||||
shellProcess,
|
||||
resume,
|
||||
cleanup: () => {
|
||||
shellProcess.kill();
|
||||
shellProcesses.delete(terminalId);
|
||||
},
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default ensureShellProcessInjectable;
|
||||
37
src/main/shell-session/get-cached-shell-env.injectable.ts
Normal file
37
src/main/shell-session/get-cached-shell-env.injectable.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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 cachedShellEnvInjectable from "./cached-shell-env.injectable";
|
||||
import type { GetShellEnv } from "./get-shell-env.injectable";
|
||||
import getShellEnvInjectable from "./get-shell-env.injectable";
|
||||
|
||||
const getCachedShellEnvInjectable = getInjectable({
|
||||
id: "get-cached-shell-env",
|
||||
instantiate: (di): GetShellEnv => {
|
||||
const cachedShellEnv = di.inject(cachedShellEnvInjectable);
|
||||
const getShellEnv = di.inject(getShellEnvInjectable);
|
||||
|
||||
return async (args) => {
|
||||
const { id: clusterId } = args.cluster;
|
||||
|
||||
let env = cachedShellEnv.get(clusterId);
|
||||
|
||||
if (!env) {
|
||||
env = await getShellEnv(args);
|
||||
cachedShellEnv.set(clusterId, env);
|
||||
} else {
|
||||
// refresh env in the background
|
||||
getShellEnv(args).then((shellEnv) => {
|
||||
cachedShellEnv.set(clusterId, shellEnv);
|
||||
});
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getCachedShellEnvInjectable;
|
||||
86
src/main/shell-session/get-shell-env.injectable.ts
Normal file
86
src/main/shell-session/get-shell-env.injectable.ts
Normal file
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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 path from "path";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable";
|
||||
import resolvedShellInjectable from "../../common/user-store/resolved-shell.injectable";
|
||||
import isWindowsInjectable from "../../common/vars/is-windows.injectable";
|
||||
import appNameInjectable from "../app-paths/app-name/app-name.injectable";
|
||||
import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars";
|
||||
import shellEnvInjectable from "../utils/shell-env.injectable";
|
||||
|
||||
export interface GetShellEnvArgs {
|
||||
cluster: Cluster;
|
||||
initialPathEntries: string[];
|
||||
kubectlBinDirP: Promise<string>;
|
||||
kubeconfigPathP: Promise<string>;
|
||||
}
|
||||
export type GetShellEnv = (args: GetShellEnvArgs) => Promise<Record<string, string | undefined>>;
|
||||
|
||||
const getShellEnvInjectable = getInjectable({
|
||||
id: "get-shell-env",
|
||||
instantiate: (di): GetShellEnv => {
|
||||
const isWindows = di.inject(isWindowsInjectable);
|
||||
const shellEnv = di.inject(shellEnvInjectable);
|
||||
const resolvedShell = di.inject(resolvedShellInjectable);
|
||||
const appName = di.inject(appNameInjectable);
|
||||
const appVersion = di.inject(appVersionInjectable);
|
||||
|
||||
return async ({ cluster, initialPathEntries, kubeconfigPathP, kubectlBinDirP }) => {
|
||||
const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(await shellEnv())));
|
||||
const shell = resolvedShell.get();
|
||||
const initialPATH = [await kubectlBinDirP, ...initialPathEntries, process.env.PATH].join(path.delimiter);
|
||||
|
||||
delete env.DEBUG; // don't pass DEBUG into shells
|
||||
|
||||
if (isWindows) {
|
||||
env.SystemRoot = process.env.SystemRoot;
|
||||
env.PTYSHELL = shell || "powershell.exe";
|
||||
env.PATH = initialPATH;
|
||||
env.LENS_SESSION = "true";
|
||||
env.WSLENV = [
|
||||
process.env.WSLENV,
|
||||
"KUBECONFIG/up:LENS_SESSION/u",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(":");
|
||||
} else if (shell !== undefined) {
|
||||
env.PTYSHELL = shell;
|
||||
env.PATH = initialPATH;
|
||||
} else {
|
||||
env.PTYSHELL = ""; // blank runs the system default shell
|
||||
}
|
||||
|
||||
if (path.basename(env.PTYSHELL) === "zsh") {
|
||||
env.OLD_ZDOTDIR = env.ZDOTDIR || env.HOME;
|
||||
env.ZDOTDIR = await kubectlBinDirP;
|
||||
env.DISABLE_AUTO_UPDATE = "true";
|
||||
}
|
||||
|
||||
env.PTYPID = process.pid.toString();
|
||||
env.KUBECONFIG = await kubeconfigPathP;
|
||||
env.TERM_PROGRAM = appName;
|
||||
env.TERM_PROGRAM_VERSION = appVersion;
|
||||
|
||||
if (cluster.preferences.httpsProxy) {
|
||||
env.HTTPS_PROXY = cluster.preferences.httpsProxy;
|
||||
}
|
||||
|
||||
env.NO_PROXY = [
|
||||
"localhost",
|
||||
"127.0.0.1",
|
||||
env.NO_PROXY,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join();
|
||||
|
||||
return env;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getShellEnvInjectable;
|
||||
63
src/main/shell-session/get-valid-cwd.injectable.ts
Normal file
63
src/main/shell-session/get-valid-cwd.injectable.ts
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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 { homedir } from "os";
|
||||
import statInjectable from "../../common/fs/stat.injectable";
|
||||
import isMacInjectable from "../../common/vars/is-mac.injectable";
|
||||
import isWindowsInjectable from "../../common/vars/is-windows.injectable";
|
||||
|
||||
export type GetValidCwd = (cwd: string | undefined, env: Record<string, string | undefined>) => Promise<string>;
|
||||
|
||||
const getValidCwdInjectable = getInjectable({
|
||||
id: "get-valid-cwd",
|
||||
instantiate: (di): GetValidCwd => {
|
||||
const stat = di.inject(statInjectable);
|
||||
const isWindows = di.inject(isWindowsInjectable);
|
||||
const isMac = di.inject(isMacInjectable);
|
||||
|
||||
return async (cwd, env) => {
|
||||
const cwdOptions = [cwd];
|
||||
|
||||
if (isWindows) {
|
||||
cwdOptions.push(
|
||||
env.USERPROFILE,
|
||||
homedir(),
|
||||
"C:\\",
|
||||
);
|
||||
} else {
|
||||
cwdOptions.push(
|
||||
env.HOME,
|
||||
homedir(),
|
||||
);
|
||||
|
||||
if (isMac) {
|
||||
cwdOptions.push("/Users");
|
||||
} else {
|
||||
cwdOptions.push("/home");
|
||||
}
|
||||
}
|
||||
|
||||
for (const potentialCwd of cwdOptions) {
|
||||
if (!potentialCwd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const stats = await stat(potentialCwd);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return potentialCwd;
|
||||
}
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
}
|
||||
|
||||
return "."; // Always valid
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getValidCwdInjectable;
|
||||
27
src/main/shell-session/kill-all-processes.injectable.ts
Normal file
27
src/main/shell-session/kill-all-processes.injectable.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import shellProcessesInjectable from "./shell-processes.injectable";
|
||||
|
||||
const killAllShellProcessesInjectable = getInjectable({
|
||||
id: "kill-all-shell-processes",
|
||||
instantiate: (di) => {
|
||||
const shellProcesses = di.inject(shellProcessesInjectable);
|
||||
|
||||
return () => {
|
||||
for (const shellProcess of shellProcesses.values()) {
|
||||
try {
|
||||
process.kill(shellProcess.pid);
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
}
|
||||
|
||||
shellProcesses.clear();
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default killAllShellProcessesInjectable;
|
||||
@ -6,10 +6,10 @@
|
||||
import path from "path";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import type { TerminalShellEnvModify } from "../shell-env-modifier/terminal-shell-env-modify.injectable";
|
||||
import type { ShellSessionArgs } from "../shell-session";
|
||||
import type { ShellSessionArgs, ShellSessionDependencies } from "../shell-session";
|
||||
import { ShellSession } from "../shell-session";
|
||||
|
||||
export interface LocalShellSessionDependencies {
|
||||
export interface LocalShellSessionDependencies extends ShellSessionDependencies {
|
||||
terminalShellEnvModify: TerminalShellEnvModify;
|
||||
readonly baseBundeledBinariesDirectory: string;
|
||||
}
|
||||
@ -18,7 +18,7 @@ export class LocalShellSession extends ShellSession {
|
||||
ShellType = "shell";
|
||||
|
||||
constructor(protected readonly dependencies: LocalShellSessionDependencies, args: ShellSessionArgs) {
|
||||
super(args);
|
||||
super(dependencies, args);
|
||||
}
|
||||
|
||||
protected getPathEntries(): string[] {
|
||||
|
||||
@ -6,18 +6,19 @@
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { Watch, CoreV1Api } from "@kubernetes/client-node";
|
||||
import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import type { ShellSessionArgs } from "../shell-session";
|
||||
import type { ShellSessionArgs, ShellSessionDependencies } from "../shell-session";
|
||||
import { ShellOpenError, ShellSession } from "../shell-session";
|
||||
import { get, once } from "lodash";
|
||||
import { Node, NodeApi } from "../../../common/k8s-api/endpoints";
|
||||
import { KubeJsonApi } from "../../../common/k8s-api/kube-json-api";
|
||||
import logger from "../../logger";
|
||||
import { TerminalChannels } from "../../../common/terminal/channels";
|
||||
|
||||
export interface NodeShellSessionArgs extends ShellSessionArgs {
|
||||
nodeName: string;
|
||||
}
|
||||
|
||||
export interface NodeShellSessionDependencies extends ShellSessionDependencies {}
|
||||
|
||||
export class NodeShellSession extends ShellSession {
|
||||
ShellType = "node-shell";
|
||||
|
||||
@ -26,8 +27,8 @@ export class NodeShellSession extends ShellSession {
|
||||
protected readonly cwd: string | undefined = undefined;
|
||||
protected readonly nodeName: string;
|
||||
|
||||
constructor({ nodeName, ...args }: NodeShellSessionArgs) {
|
||||
super(args);
|
||||
constructor(protected readonly dependencies: NodeShellSessionDependencies, { nodeName, ...args }: NodeShellSessionArgs) {
|
||||
super(dependencies, args);
|
||||
this.nodeName = nodeName;
|
||||
}
|
||||
|
||||
@ -39,7 +40,7 @@ export class NodeShellSession extends ShellSession {
|
||||
const cleanup = once(() => {
|
||||
coreApi
|
||||
.deleteNamespacedPod(this.podName, "kube-system")
|
||||
.catch(error => logger.warn(`[NODE-SHELL]: failed to remove pod shell`, error));
|
||||
.catch(error => this.dependencies.logger.warn(`[NODE-SHELL]: failed to remove pod shell`, error));
|
||||
});
|
||||
|
||||
this.websocket.once("close", cleanup);
|
||||
@ -79,7 +80,7 @@ export class NodeShellSession extends ShellSession {
|
||||
|
||||
switch (nodeOs) {
|
||||
default:
|
||||
logger.warn(`[NODE-SHELL-SESSION]: could not determine node OS, falling back with assumption of linux`);
|
||||
this.dependencies.logger.warn(`[NODE-SHELL-SESSION]: could not determine node OS, falling back with assumption of linux`);
|
||||
// fallthrough
|
||||
case "linux":
|
||||
args.push("sh", "-c", "((clear && bash) || (clear && ash) || (clear && sh))");
|
||||
@ -131,7 +132,7 @@ export class NodeShellSession extends ShellSession {
|
||||
}
|
||||
|
||||
protected waitForRunningPod(kc: KubeConfig): Promise<void> {
|
||||
logger.debug(`[NODE-SHELL]: waiting for ${this.podName} to be running`);
|
||||
this.dependencies.logger.debug(`[NODE-SHELL]: waiting for ${this.podName} to be running`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
new Watch(kc)
|
||||
@ -150,19 +151,19 @@ export class NodeShellSession extends ShellSession {
|
||||
},
|
||||
// done callback is called if the watch terminates normally
|
||||
(err) => {
|
||||
logger.error(`[NODE-SHELL]: ${this.podName} was not created in time`);
|
||||
this.dependencies.logger.error(`[NODE-SHELL]: ${this.podName} was not created in time`);
|
||||
reject(err);
|
||||
},
|
||||
)
|
||||
.then(req => {
|
||||
setTimeout(() => {
|
||||
logger.error(`[NODE-SHELL]: aborting wait for ${this.podName}, timing out`);
|
||||
this.dependencies.logger.error(`[NODE-SHELL]: aborting wait for ${this.podName}, timing out`);
|
||||
req.abort();
|
||||
reject("Pod creation timed out");
|
||||
}, 2 * 60 * 1000); // 2 * 60 * 1000
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(`[NODE-SHELL]: waiting for ${this.podName} failed: ${error}`);
|
||||
this.dependencies.logger.error(`[NODE-SHELL]: waiting for ${this.podName} failed: ${error}`);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
13
src/main/shell-session/shell-processes.injectable.ts
Normal file
13
src/main/shell-session/shell-processes.injectable.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { IPty } from "node-pty";
|
||||
|
||||
const shellProcessesInjectable = getInjectable({
|
||||
id: "shell-processes",
|
||||
instantiate: () => new Map<string, IPty>(),
|
||||
});
|
||||
|
||||
export default shellProcessesInjectable;
|
||||
@ -6,19 +6,12 @@
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import type { Kubectl } from "../kubectl/kubectl";
|
||||
import type WebSocket from "ws";
|
||||
import { shellEnv } from "../utils/shell-env";
|
||||
import { app } from "electron";
|
||||
import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { isMac, isWindows } from "../../common/vars";
|
||||
import { UserStore } from "../../common/user-store";
|
||||
import * as pty from "node-pty";
|
||||
import { appEventBus } from "../../common/app-event-bus/event-bus";
|
||||
import logger from "../logger";
|
||||
import { stat } from "fs/promises";
|
||||
import { getOrInsertWith } from "../../common/utils";
|
||||
import { type TerminalMessage, TerminalChannels } from "../../common/terminal/channels";
|
||||
import type { EnsureShellProcess } from "./ensure-shell-process.injectable";
|
||||
import type { GetValidCwd } from "./get-valid-cwd.injectable";
|
||||
import type { GetShellEnv } from "./get-shell-env.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
|
||||
export class ShellOpenError extends Error {
|
||||
constructor(message: string, options?: ErrorOptions) {
|
||||
@ -111,27 +104,16 @@ export interface ShellSessionArgs {
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
export interface ShellSessionDependencies {
|
||||
ensureShellProcess: EnsureShellProcess;
|
||||
getValidCwd: GetValidCwd;
|
||||
getCachedShellEnv: GetShellEnv;
|
||||
readonly logger: Logger;
|
||||
}
|
||||
|
||||
export abstract class ShellSession {
|
||||
abstract readonly ShellType: string;
|
||||
|
||||
private static readonly shellEnvs = new Map<string, Record<string, string | undefined>>();
|
||||
private static readonly processes = new Map<string, pty.IPty>();
|
||||
|
||||
/**
|
||||
* Kill all remaining shell backing processes. Should be called when about to
|
||||
* quit
|
||||
*/
|
||||
public static cleanup(): void {
|
||||
for (const shellProcess of this.processes.values()) {
|
||||
try {
|
||||
process.kill(shellProcess.pid);
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
}
|
||||
|
||||
this.processes.clear();
|
||||
}
|
||||
protected abstract readonly cwd: string | undefined;
|
||||
|
||||
protected running = false;
|
||||
protected readonly kubectlBinDirP: Promise<string>;
|
||||
@ -141,28 +123,7 @@ export abstract class ShellSession {
|
||||
protected readonly websocket: WebSocket;
|
||||
protected readonly cluster: Cluster;
|
||||
|
||||
protected abstract readonly cwd: string | undefined;
|
||||
|
||||
protected ensureShellProcess(shell: string, args: string[], env: Record<string, string | undefined>, cwd: string): { shellProcess: pty.IPty; resume: boolean } {
|
||||
const resume = ShellSession.processes.has(this.terminalId);
|
||||
const shellProcess = getOrInsertWith(ShellSession.processes, this.terminalId, () => (
|
||||
pty.spawn(shell, args, {
|
||||
rows: 30,
|
||||
cols: 80,
|
||||
cwd,
|
||||
env: env as Record<string, string>,
|
||||
name: "xterm-256color",
|
||||
// TODO: Something else is broken here so we need to force the use of winPty on windows
|
||||
useConpty: false,
|
||||
})
|
||||
));
|
||||
|
||||
logger.info(`[SHELL-SESSION]: PTY for ${this.terminalId} is ${resume ? "resumed" : "started"} with PID=${shellProcess.pid}`);
|
||||
|
||||
return { shellProcess, resume };
|
||||
}
|
||||
|
||||
constructor({ cluster, kubectl, tabId, websocket }: ShellSessionArgs) {
|
||||
constructor(protected readonly dependencies: ShellSessionDependencies, { cluster, kubectl, tabId, websocket }: ShellSessionArgs) {
|
||||
this.cluster = cluster;
|
||||
this.kubectl = kubectl;
|
||||
this.websocket = websocket;
|
||||
@ -175,50 +136,15 @@ export abstract class ShellSession {
|
||||
this.websocket.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
protected async getCwd(env: Record<string, string | undefined>): Promise<string> {
|
||||
const cwdOptions = [this.cwd];
|
||||
|
||||
if (isWindows) {
|
||||
cwdOptions.push(
|
||||
env.USERPROFILE,
|
||||
os.homedir(),
|
||||
"C:\\",
|
||||
);
|
||||
} else {
|
||||
cwdOptions.push(
|
||||
env.HOME,
|
||||
os.homedir(),
|
||||
);
|
||||
|
||||
if (isMac) {
|
||||
cwdOptions.push("/Users");
|
||||
} else {
|
||||
cwdOptions.push("/home");
|
||||
}
|
||||
}
|
||||
|
||||
for (const potentialCwd of cwdOptions) {
|
||||
if (!potentialCwd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const stats = await stat(potentialCwd);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return potentialCwd;
|
||||
}
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
}
|
||||
|
||||
return "."; // Always valid
|
||||
}
|
||||
|
||||
protected async openShellProcess(shell: string, args: string[], env: Record<string, string | undefined>) {
|
||||
const cwd = await this.getCwd(env);
|
||||
const { shellProcess, resume } = this.ensureShellProcess(shell, args, env, cwd);
|
||||
const cwd = await this.dependencies.getValidCwd(this.cwd, env);
|
||||
const { shellProcess, resume, cleanup } = this.dependencies.ensureShellProcess({
|
||||
shell,
|
||||
args,
|
||||
env,
|
||||
cwd,
|
||||
terminalId: this.terminalId,
|
||||
});
|
||||
|
||||
if (resume) {
|
||||
this.send({ type: TerminalChannels.CONNECTED });
|
||||
@ -227,7 +153,7 @@ export abstract class ShellSession {
|
||||
this.running = true;
|
||||
shellProcess.onData(data => this.send({ type: TerminalChannels.STDOUT, data }));
|
||||
shellProcess.onExit(({ exitCode }) => {
|
||||
logger.info(`[SHELL-SESSION]: shell has exited for ${this.terminalId} closed with exitcode=${exitCode}`);
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: shell has exited for ${this.terminalId} closed with exitcode=${exitCode}`);
|
||||
|
||||
// This might already be false because of the kill() within the websocket.on("close") handler
|
||||
if (this.running) {
|
||||
@ -245,11 +171,11 @@ export abstract class ShellSession {
|
||||
this.websocket
|
||||
.on("message", (rawData: unknown): void => {
|
||||
if (!this.running) {
|
||||
return void logger.debug(`[SHELL-SESSION]: received message from ${this.terminalId}, but shellProcess isn't running`);
|
||||
return void this.dependencies.logger.debug(`[SHELL-SESSION]: received message from ${this.terminalId}, but shellProcess isn't running`);
|
||||
}
|
||||
|
||||
if (!(rawData instanceof Buffer)) {
|
||||
return void logger.error(`[SHELL-SESSION]: Received message non-buffer message.`, { rawData });
|
||||
return void this.dependencies.logger.error(`[SHELL-SESSION]: Received message non-buffer message.`, { rawData });
|
||||
}
|
||||
|
||||
const data = rawData.toString();
|
||||
@ -265,18 +191,18 @@ export abstract class ShellSession {
|
||||
shellProcess.resize(message.data.width, message.data.height);
|
||||
break;
|
||||
case TerminalChannels.PING:
|
||||
logger.silly(`[SHELL-SESSION]: ${this.terminalId} ping!`);
|
||||
this.dependencies.logger.silly(`[SHELL-SESSION]: ${this.terminalId} ping!`);
|
||||
break;
|
||||
default:
|
||||
logger.warn(`[SHELL-SESSION]: unknown or unhandleable message type for ${this.terminalId}`, message);
|
||||
this.dependencies.logger.warn(`[SHELL-SESSION]: unknown or unhandleable message type for ${this.terminalId}`, message);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[SHELL-SESSION]: failed to handle message for ${this.terminalId}`, error);
|
||||
this.dependencies.logger.error(`[SHELL-SESSION]: failed to handle message for ${this.terminalId}`, error);
|
||||
}
|
||||
})
|
||||
.once("close", code => {
|
||||
logger.info(`[SHELL-SESSION]: websocket for ${this.terminalId} closed with code=${WebSocketCloseEvent[code]}(${code})`, { cluster: this.cluster.getMeta() });
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: websocket for ${this.terminalId} closed with code=${WebSocketCloseEvent[code]}(${code})`, { cluster: this.cluster.getMeta() });
|
||||
|
||||
const stopShellSession = this.running
|
||||
&& (
|
||||
@ -291,11 +217,10 @@ export abstract class ShellSession {
|
||||
this.running = false;
|
||||
|
||||
try {
|
||||
logger.info(`[SHELL-SESSION]: Killing shell process (pid=${shellProcess.pid}) for ${this.terminalId}`);
|
||||
shellProcess.kill();
|
||||
ShellSession.processes.delete(this.terminalId);
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: Killing shell process (pid=${shellProcess.pid}) for ${this.terminalId}`);
|
||||
cleanup();
|
||||
} catch (error) {
|
||||
logger.warn(`[SHELL-SESSION]: failed to kill shell process (pid=${shellProcess.pid}) for ${this.terminalId}`, error);
|
||||
this.dependencies.logger.warn(`[SHELL-SESSION]: failed to kill shell process (pid=${shellProcess.pid}) for ${this.terminalId}`, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -307,73 +232,13 @@ export abstract class ShellSession {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected async getCachedShellEnv() {
|
||||
const { id: clusterId } = this.cluster;
|
||||
|
||||
let env = ShellSession.shellEnvs.get(clusterId);
|
||||
|
||||
if (!env) {
|
||||
env = await this.getShellEnv();
|
||||
ShellSession.shellEnvs.set(clusterId, env);
|
||||
} else {
|
||||
// refresh env in the background
|
||||
this.getShellEnv().then((shellEnv: any) => {
|
||||
ShellSession.shellEnvs.set(clusterId, shellEnv);
|
||||
});
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
protected async getShellEnv() {
|
||||
const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(await shellEnv())));
|
||||
const pathStr = [await this.kubectlBinDirP, ...this.getPathEntries(), process.env.PATH].join(path.delimiter);
|
||||
const shell = UserStore.getInstance().resolvedShell;
|
||||
|
||||
delete env.DEBUG; // don't pass DEBUG into shells
|
||||
|
||||
if (isWindows) {
|
||||
env.SystemRoot = process.env.SystemRoot;
|
||||
env.PTYSHELL = shell || "powershell.exe";
|
||||
env.PATH = pathStr;
|
||||
env.LENS_SESSION = "true";
|
||||
env.WSLENV = [
|
||||
process.env.WSLENV,
|
||||
"KUBECONFIG/up:LENS_SESSION/u",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(":");
|
||||
} else if (shell !== undefined) {
|
||||
env.PTYSHELL = shell;
|
||||
env.PATH = pathStr;
|
||||
} else {
|
||||
env.PTYSHELL = ""; // blank runs the system default shell
|
||||
}
|
||||
|
||||
if (path.basename(env.PTYSHELL) === "zsh") {
|
||||
env.OLD_ZDOTDIR = env.ZDOTDIR || env.HOME;
|
||||
env.ZDOTDIR = await this.kubectlBinDirP;
|
||||
env.DISABLE_AUTO_UPDATE = "true";
|
||||
}
|
||||
|
||||
env.PTYPID = process.pid.toString();
|
||||
env.KUBECONFIG = await this.kubeconfigPathP;
|
||||
env.TERM_PROGRAM = app.getName();
|
||||
env.TERM_PROGRAM_VERSION = app.getVersion();
|
||||
|
||||
if (this.cluster.preferences.httpsProxy) {
|
||||
env.HTTPS_PROXY = this.cluster.preferences.httpsProxy;
|
||||
}
|
||||
|
||||
env.NO_PROXY = [
|
||||
"localhost",
|
||||
"127.0.0.1",
|
||||
env.NO_PROXY,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join();
|
||||
|
||||
return env;
|
||||
protected getCachedShellEnv() {
|
||||
return this.dependencies.getCachedShellEnv({
|
||||
cluster: this.cluster,
|
||||
initialPathEntries: this.getPathEntries(),
|
||||
kubeconfigPathP: this.kubeconfigPathP,
|
||||
kubectlBinDirP: this.kubectlBinDirP,
|
||||
});
|
||||
}
|
||||
|
||||
protected exit(code = WebSocketCloseEvent.NormalClosure) {
|
||||
|
||||
14
src/main/shell-session/spawn-pty.injectable.ts
Normal file
14
src/main/shell-session/spawn-pty.injectable.ts
Normal 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 { spawn } from "node-pty";
|
||||
|
||||
const spawnPtyInjectable = getInjectable({
|
||||
id: "spawn-pty",
|
||||
instantiate: () => spawn,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default spawnPtyInjectable;
|
||||
@ -4,15 +4,13 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { beforeQuitOfBackEndInjectionToken } from "../runnable-tokens/before-quit-of-back-end-injection-token";
|
||||
import { ShellSession } from "../../shell-session/shell-session";
|
||||
import killAllShellProcessesInjectable from "../../shell-session/kill-all-processes.injectable";
|
||||
|
||||
const cleanUpShellSessionsInjectable = getInjectable({
|
||||
id: "clean-up-shell-sessions",
|
||||
|
||||
instantiate: () => ({
|
||||
run: () => {
|
||||
ShellSession.cleanup();
|
||||
},
|
||||
instantiate: (di) => ({
|
||||
run: di.inject(killAllShellProcessesInjectable),
|
||||
}),
|
||||
|
||||
injectionToken: beforeQuitOfBackEndInjectionToken,
|
||||
|
||||
@ -13,7 +13,7 @@ const anyKubeconfig = /^kubeconfig$/i;
|
||||
* before KUBECONFIG and we only set KUBECONFIG.
|
||||
* @param env The current copy of env
|
||||
*/
|
||||
export function clearKubeconfigEnvVars(env: Record<string, any>): Record<string, any> {
|
||||
export function clearKubeconfigEnvVars(env: Record<string, string | undefined>): Record<string, string | undefined> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(env)
|
||||
.filter(([key]) => anyKubeconfig.exec(key) === null),
|
||||
|
||||
14
src/main/utils/shell-env.injectable.ts
Normal file
14
src/main/utils/shell-env.injectable.ts
Normal 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 { shellEnv } from "./shell-env";
|
||||
|
||||
const shellEnvInjectable = getInjectable({
|
||||
id: "shell-env",
|
||||
instantiate: () => shellEnv,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default shellEnvInjectable;
|
||||
@ -30,7 +30,7 @@ export const DockTabs = ({ tabs, autoFocus, selectedTab, onChangeTab }: DockTabs
|
||||
return Array.from(elem.current?.querySelectorAll(".Tabs .Tab") ?? []);
|
||||
};
|
||||
|
||||
const renderTab = (tab?: DockTabModel) => {
|
||||
const renderTab = (tab: DockTabModel | undefined, index: number) => {
|
||||
if (!tab) {
|
||||
return null;
|
||||
}
|
||||
@ -38,14 +38,37 @@ export const DockTabs = ({ tabs, autoFocus, selectedTab, onChangeTab }: DockTabs
|
||||
switch (tab.kind) {
|
||||
case TabKind.CREATE_RESOURCE:
|
||||
case TabKind.EDIT_RESOURCE:
|
||||
return <DockTab value={tab} icon="edit" />;
|
||||
return (
|
||||
<DockTab
|
||||
value={tab}
|
||||
icon="edit"
|
||||
data-testid={`dock-tab-${index}`}
|
||||
/>
|
||||
);
|
||||
case TabKind.INSTALL_CHART:
|
||||
case TabKind.UPGRADE_CHART:
|
||||
return <DockTab value={tab} icon="install_desktop" />;
|
||||
return (
|
||||
<DockTab
|
||||
value={tab}
|
||||
icon="install_desktop"
|
||||
data-testid={`dock-tab-${index}`}
|
||||
/>
|
||||
);
|
||||
case TabKind.POD_LOGS:
|
||||
return <DockTab value={tab} icon="subject" />;
|
||||
return (
|
||||
<DockTab
|
||||
value={tab}
|
||||
icon="subject"
|
||||
data-testid={`dock-tab-${index}`}
|
||||
/>
|
||||
);
|
||||
case TabKind.TERMINAL:
|
||||
return <TerminalTab value={tab} />;
|
||||
return (
|
||||
<TerminalTab
|
||||
value={tab}
|
||||
data-testid={`dock-tab-${index}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -96,7 +119,11 @@ export const DockTabs = ({ tabs, autoFocus, selectedTab, onChangeTab }: DockTabs
|
||||
scrollable={showScrollbar}
|
||||
className={styles.tabs}
|
||||
>
|
||||
{tabs.map(tab => <Fragment key={tab.id}>{renderTab(tab)}</Fragment>)}
|
||||
{tabs.map((tab, index) => (
|
||||
<Fragment key={tab.id}>
|
||||
{renderTab(tab, index)}
|
||||
</Fragment>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -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";
|
||||
|
||||
// This is here so that in tests we can override it to disable a warning because we are using jest-canvas-mock
|
||||
|
||||
const allowTerminalTransparencyInjectable = getInjectable({
|
||||
id: "allow-terminal-transparency",
|
||||
instantiate: () => false,
|
||||
});
|
||||
|
||||
export default allowTerminalTransparencyInjectable;
|
||||
@ -11,6 +11,7 @@ import terminalSpawningPoolInjectable from "./terminal-spawning-pool.injectable"
|
||||
import terminalConfigInjectable from "../../../../common/user-store/terminal-config.injectable";
|
||||
import terminalCopyOnSelectInjectable from "../../../../common/user-store/terminal-copy-on-select.injectable";
|
||||
import themeStoreInjectable from "../../../themes/store.injectable";
|
||||
import allowTerminalTransparencyInjectable from "./allow-transparency.injectable";
|
||||
|
||||
export type CreateTerminal = (tabId: TabId, api: TerminalApi) => Terminal;
|
||||
|
||||
@ -22,6 +23,7 @@ const createTerminalInjectable = getInjectable({
|
||||
terminalConfig: di.inject(terminalConfigInjectable),
|
||||
terminalCopyOnSelect: di.inject(terminalCopyOnSelectInjectable),
|
||||
themeStore: di.inject(themeStoreInjectable),
|
||||
allowTransparency: di.inject(allowTerminalTransparencyInjectable),
|
||||
};
|
||||
|
||||
return (tabId, api) => new Terminal(dependencies, { tabId, api });
|
||||
|
||||
@ -25,6 +25,7 @@ export interface TerminalDependencies {
|
||||
readonly terminalConfig: IComputedValue<TerminalConfig>;
|
||||
readonly terminalCopyOnSelect: IComputedValue<boolean>;
|
||||
readonly themeStore: ThemeStore;
|
||||
readonly allowTransparency: boolean;
|
||||
}
|
||||
|
||||
export interface TerminalArguments {
|
||||
@ -88,6 +89,7 @@ export class Terminal {
|
||||
cursorStyle: "bar",
|
||||
fontSize: this.fontSize,
|
||||
fontFamily: this.fontFamily,
|
||||
allowTransparency: dependencies.allowTransparency,
|
||||
});
|
||||
// enable terminal addons
|
||||
this.xterm.loadAddon(this.fitAddon);
|
||||
@ -119,6 +121,9 @@ export class Terminal {
|
||||
() => this.api.removeAllListeners(),
|
||||
() => window.removeEventListener("resize", this.onResize),
|
||||
() => this.elem.removeEventListener("contextmenu", this.onContextMenu),
|
||||
this.xterm.onResize(({ cols, rows }) => {
|
||||
this.api.sendTerminalSize(cols, rows);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -127,21 +132,7 @@ export class Terminal {
|
||||
this.xterm.dispose();
|
||||
}
|
||||
|
||||
fit = () => {
|
||||
try {
|
||||
const { cols, rows } = this.fitAddon.proposeDimensions();
|
||||
|
||||
// attempt to resize/fit terminal when it's not visible in DOM will crash with exception
|
||||
// see: https://github.com/xtermjs/xterm.js/issues/3118
|
||||
if (isNaN(cols) || isNaN(rows)) return;
|
||||
|
||||
this.fitAddon.fit();
|
||||
this.api.sendTerminalSize(cols, rows);
|
||||
} catch (error) {
|
||||
// see https://github.com/lensapp/lens/issues/1891
|
||||
logger.error(`[TERMINAL]: failed to resize terminal to fit`, error);
|
||||
}
|
||||
};
|
||||
fit = () => this.fitAddon.fit();
|
||||
|
||||
fitLazy = debounce(this.fit, 250);
|
||||
|
||||
|
||||
@ -66,16 +66,16 @@ type EnableExtensions<T> = (...extensions: T[]) => void;
|
||||
type DisableExtensions<T> = (...extensions: T[]) => void;
|
||||
|
||||
export interface ApplicationBuilder {
|
||||
dis: DiContainers;
|
||||
readonly dis: DiContainers;
|
||||
setEnvironmentToClusterFrame: () => ApplicationBuilder;
|
||||
|
||||
extensions: {
|
||||
renderer: {
|
||||
readonly extensions: {
|
||||
readonly renderer: {
|
||||
enable: EnableExtensions<LensRendererExtension>;
|
||||
disable: DisableExtensions<LensRendererExtension>;
|
||||
};
|
||||
|
||||
main: {
|
||||
readonly main: {
|
||||
enable: EnableExtensions<LensMainExtension>;
|
||||
disable: DisableExtensions<LensMainExtension>;
|
||||
};
|
||||
@ -89,38 +89,42 @@ export interface ApplicationBuilder {
|
||||
beforeRender: (callback: Callback) => ApplicationBuilder;
|
||||
render: () => Promise<RenderResult>;
|
||||
|
||||
tray: {
|
||||
readonly tray: {
|
||||
click: (id: string) => Promise<void>;
|
||||
get: (id: string) => MinimalTrayMenuItem | null;
|
||||
getIconPath: () => string;
|
||||
};
|
||||
|
||||
applicationMenu: {
|
||||
readonly dock: {
|
||||
click: (index: number) => void;
|
||||
};
|
||||
|
||||
readonly applicationMenu: {
|
||||
click: (path: string) => void;
|
||||
};
|
||||
|
||||
preferences: {
|
||||
readonly preferences: {
|
||||
close: () => void;
|
||||
navigate: () => void;
|
||||
navigateTo: (route: Route<any>, params: Partial<NavigateToRouteOptions<any>>) => void;
|
||||
navigation: {
|
||||
readonly navigation: {
|
||||
click: (id: string) => void;
|
||||
};
|
||||
};
|
||||
|
||||
helmCharts: {
|
||||
readonly helmCharts: {
|
||||
navigate: () => void;
|
||||
};
|
||||
|
||||
select: {
|
||||
readonly select: {
|
||||
openMenu: (id: string) => void;
|
||||
selectOption: (menuId: string, labelText: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
interface DiContainers {
|
||||
rendererDi: DiContainer;
|
||||
mainDi: DiContainer;
|
||||
readonly rendererDi: DiContainer;
|
||||
readonly mainDi: DiContainer;
|
||||
}
|
||||
|
||||
interface Environment {
|
||||
@ -254,6 +258,11 @@ export const getApplicationBuilder = () => {
|
||||
const enableMainExtension = enableExtensionsFor(mainExtensionsState, mainDi);
|
||||
const disableRendererExtension = disableExtensionsFor(rendererExtensionsState, rendererDi);
|
||||
const disableMainExtension = disableExtensionsFor(mainExtensionsState, mainDi);
|
||||
const dock: ApplicationBuilder["dock"] = {
|
||||
click: (index) => {
|
||||
rendered.getByTestId(`dock-tab-${index}`).click();
|
||||
},
|
||||
};
|
||||
|
||||
const builder: ApplicationBuilder = {
|
||||
dis,
|
||||
@ -293,6 +302,14 @@ export const getApplicationBuilder = () => {
|
||||
},
|
||||
},
|
||||
|
||||
get dock() {
|
||||
if (environment === environments.clusterFrame) {
|
||||
return dock;
|
||||
}
|
||||
|
||||
throw new Error("cannot use dock in application frame");
|
||||
},
|
||||
|
||||
tray: {
|
||||
get: (id: string) => {
|
||||
const lastCall = last(traySetMenuItemsMock.mock.calls);
|
||||
|
||||
@ -69,6 +69,7 @@ import kubeObjectDetailsClusterFrameChildComponentInjectable from "./components/
|
||||
import kubeconfigDialogClusterFrameChildComponentInjectable from "./components/kubeconfig-dialog/kubeconfig-dialog-cluster-frame-child-component.injectable";
|
||||
import portForwardDialogClusterFrameChildComponentInjectable from "./port-forward/port-forward-dialog-cluster-frame-child-component.injectable";
|
||||
import setupSystemCaInjectable from "./frames/root-frame/setup-system-ca.injectable";
|
||||
import allowTerminalTransparencyInjectable from "./components/dock/terminal/allow-transparency.injectable";
|
||||
|
||||
export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => {
|
||||
const {
|
||||
@ -185,7 +186,7 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
||||
isTableColumnHidden: () => false,
|
||||
extensionRegistryUrl: { customUrl: "some-custom-url" },
|
||||
syncKubeconfigEntries: observable.map(),
|
||||
terminalConfig: { fontSize: 42 },
|
||||
terminalConfig: { fontSize: 42, fontFamily: "RobotoMono" },
|
||||
editorConfiguration: { minimap: {}, tabSize: 42, fontSize: 42 },
|
||||
} as unknown as UserStore),
|
||||
);
|
||||
@ -197,7 +198,7 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
||||
overrideFsWithFakes(di);
|
||||
|
||||
di.override(focusWindowInjectable, () => () => {});
|
||||
|
||||
di.override(allowTerminalTransparencyInjectable, () => true);
|
||||
di.override(loggerInjectable, () => ({
|
||||
warn: noop,
|
||||
debug: noop,
|
||||
|
||||
582
src/techincal/shell-sessions/__snapshots__/local.test.ts.snap
Normal file
582
src/techincal/shell-sessions/__snapshots__/local.test.ts.snap
Normal file
@ -0,0 +1,582 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`local shell session techincal tests renders 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="Notifications flex column align-flex-end"
|
||||
/>
|
||||
<div
|
||||
class="mainLayout"
|
||||
style="--sidebar-width: 200px;"
|
||||
>
|
||||
<div
|
||||
class="sidebar"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col"
|
||||
data-testid="cluster-sidebar"
|
||||
>
|
||||
<div
|
||||
class="SidebarCluster"
|
||||
>
|
||||
<div
|
||||
class="Avatar rounded loadingAvatar"
|
||||
style="width: 40px; height: 40px;"
|
||||
>
|
||||
??
|
||||
</div>
|
||||
<div
|
||||
class="loadingClusterName"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="sidebarNav sidebar-active-status"
|
||||
>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="true"
|
||||
data-testid="sidebar-item-workloads"
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
class="nav-item flex gaps align-center expandable active"
|
||||
data-testid="sidebar-item-link-for-workloads"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon svg focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
/>
|
||||
</i>
|
||||
<span
|
||||
class="link-text box grow"
|
||||
>
|
||||
Workloads
|
||||
</span>
|
||||
<i
|
||||
class="Icon expand-icon box right material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="keyboard_arrow_down"
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</span>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-config"
|
||||
>
|
||||
<a
|
||||
class="nav-item flex gaps align-center"
|
||||
data-testid="sidebar-item-link-for-config"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="list"
|
||||
>
|
||||
list
|
||||
</span>
|
||||
</i>
|
||||
<span
|
||||
class="link-text box grow"
|
||||
>
|
||||
Config
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-network"
|
||||
>
|
||||
<a
|
||||
class="nav-item flex gaps align-center expandable"
|
||||
data-testid="sidebar-item-link-for-network"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="device_hub"
|
||||
>
|
||||
device_hub
|
||||
</span>
|
||||
</i>
|
||||
<span
|
||||
class="link-text box grow"
|
||||
>
|
||||
Network
|
||||
</span>
|
||||
<i
|
||||
class="Icon expand-icon box right material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="keyboard_arrow_down"
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</span>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-storage"
|
||||
>
|
||||
<a
|
||||
class="nav-item flex gaps align-center"
|
||||
data-testid="sidebar-item-link-for-storage"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="storage"
|
||||
>
|
||||
storage
|
||||
</span>
|
||||
</i>
|
||||
<span
|
||||
class="link-text box grow"
|
||||
>
|
||||
Storage
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-helm"
|
||||
>
|
||||
<a
|
||||
class="nav-item flex gaps align-center expandable"
|
||||
data-testid="sidebar-item-link-for-helm"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon svg focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
/>
|
||||
</i>
|
||||
<span
|
||||
class="link-text box grow"
|
||||
>
|
||||
Helm
|
||||
</span>
|
||||
<i
|
||||
class="Icon expand-icon box right material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="keyboard_arrow_down"
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</span>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-user-management"
|
||||
>
|
||||
<a
|
||||
class="nav-item flex gaps align-center"
|
||||
data-testid="sidebar-item-link-for-user-management"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="security"
|
||||
>
|
||||
security
|
||||
</span>
|
||||
</i>
|
||||
<span
|
||||
class="link-text box grow"
|
||||
>
|
||||
Access Control
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-custom-resources"
|
||||
>
|
||||
<a
|
||||
class="nav-item flex gaps align-center expandable"
|
||||
data-testid="sidebar-item-link-for-custom-resources"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="extension"
|
||||
>
|
||||
extension
|
||||
</span>
|
||||
</i>
|
||||
<span
|
||||
class="link-text box grow"
|
||||
>
|
||||
Custom Resources
|
||||
</span>
|
||||
<i
|
||||
class="Icon expand-icon box right material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="keyboard_arrow_down"
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</span>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ResizingAnchor horizontal trailing"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="contents"
|
||||
>
|
||||
<div
|
||||
class="TabLayout"
|
||||
data-testid="tab-layout"
|
||||
>
|
||||
<div
|
||||
class="Tabs center scrollable"
|
||||
>
|
||||
<div
|
||||
class="Tab flex gaps align-center active"
|
||||
data-is-active-test="true"
|
||||
data-testid="tab-link-for-overview"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="label"
|
||||
>
|
||||
Overview
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<main>
|
||||
<div
|
||||
class="WorkloadsOverview flex column gaps"
|
||||
>
|
||||
<div
|
||||
class="header flex gaps align-center"
|
||||
>
|
||||
<h5
|
||||
class="box grow"
|
||||
>
|
||||
Overview
|
||||
</h5>
|
||||
<div>
|
||||
<div
|
||||
class="Select theme-dark NamespaceSelect NamespaceSelectFilter css-b62m3t-container"
|
||||
>
|
||||
<span
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
id="react-select-overview-namespace-select-filter-input-live-region"
|
||||
/>
|
||||
<span
|
||||
aria-atomic="false"
|
||||
aria-live="polite"
|
||||
aria-relevant="additions text"
|
||||
class="css-1f43avz-a11yText-A11yText"
|
||||
/>
|
||||
<div
|
||||
class="Select__control css-1s2u09g-control"
|
||||
>
|
||||
<div
|
||||
class="Select__value-container Select__value-container--is-multi css-319lph-ValueContainer"
|
||||
>
|
||||
<div
|
||||
class="Select__placeholder css-14el2xx-placeholder"
|
||||
id="react-select-overview-namespace-select-filter-input-placeholder"
|
||||
>
|
||||
All namespaces
|
||||
</div>
|
||||
<div
|
||||
class="Select__input-container css-6j8wv5-Input"
|
||||
data-value=""
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-describedby="react-select-overview-namespace-select-filter-input-placeholder"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
class="Select__input"
|
||||
id="overview-namespace-select-filter-input"
|
||||
role="combobox"
|
||||
spellcheck="false"
|
||||
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
|
||||
tabindex="0"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
|
||||
>
|
||||
<span
|
||||
class="Select__indicator-separator css-1okebmr-indicatorSeparator"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="Select__indicator Select__dropdown-indicator css-tlfecz-indicatorContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="css-tj5bde-Svg"
|
||||
focusable="false"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
>
|
||||
<path
|
||||
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="OverviewStatuses"
|
||||
>
|
||||
<div
|
||||
class="workloads"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="footer"
|
||||
>
|
||||
<div
|
||||
class="Dock isOpen"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="ResizingAnchor vertical leading"
|
||||
/>
|
||||
<div
|
||||
class="tabs-container flex align-center"
|
||||
>
|
||||
<div
|
||||
class="dockTabs"
|
||||
role="tablist"
|
||||
>
|
||||
<div
|
||||
class="Tabs tabs"
|
||||
>
|
||||
<div
|
||||
class="Tab flex gaps align-center DockTab TerminalTab active"
|
||||
data-testid="dock-tab-0"
|
||||
id="tab-terminal"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="terminal"
|
||||
>
|
||||
terminal
|
||||
</span>
|
||||
</i>
|
||||
<div
|
||||
class="label"
|
||||
>
|
||||
<div
|
||||
class="flex align-center"
|
||||
>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
Terminal
|
||||
</span>
|
||||
<div
|
||||
class="close"
|
||||
>
|
||||
<i
|
||||
class="Icon material interactive focusable small"
|
||||
id="tooltip_target_13"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="close"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
<div />
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="toolbar flex gaps align-center box grow"
|
||||
>
|
||||
<div
|
||||
class="dock-menu box grow"
|
||||
>
|
||||
<i
|
||||
class="Icon new-dock-tab material interactive focusable"
|
||||
id="menu_actions_14"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="add"
|
||||
>
|
||||
add
|
||||
</span>
|
||||
<div />
|
||||
</i>
|
||||
</div>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
id="tooltip_target_16"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="fullscreen"
|
||||
>
|
||||
fullscreen
|
||||
</span>
|
||||
<div />
|
||||
</i>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
id="tooltip_target_17"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="keyboard_arrow_down"
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</span>
|
||||
<div />
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-content terminal"
|
||||
style="flex-basis: 300px;"
|
||||
>
|
||||
<div
|
||||
class="TerminalWindow dark"
|
||||
>
|
||||
<div
|
||||
class="terminal xterm focus"
|
||||
dir="ltr"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="xterm-viewport"
|
||||
style="background-color: rgb(0, 0, 0);"
|
||||
>
|
||||
<div
|
||||
class="xterm-scroll-area"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="xterm-screen"
|
||||
>
|
||||
<div
|
||||
class="xterm-helpers"
|
||||
>
|
||||
<textarea
|
||||
aria-label="Terminal input"
|
||||
aria-multiline="false"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
class="xterm-helper-textarea"
|
||||
spellcheck="false"
|
||||
tabindex="0"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="xterm-char-measure-element"
|
||||
style="font-family: RobotoMono; font-size: 42px;"
|
||||
>
|
||||
W
|
||||
</span>
|
||||
<div
|
||||
class="composition-view"
|
||||
/>
|
||||
</div>
|
||||
<canvas
|
||||
class="xterm-text-layer"
|
||||
style="z-index: 0;"
|
||||
/>
|
||||
<canvas
|
||||
class="xterm-selection-layer"
|
||||
style="z-index: 1;"
|
||||
/>
|
||||
<canvas
|
||||
class="xterm-link-layer"
|
||||
style="z-index: 2;"
|
||||
/>
|
||||
<canvas
|
||||
class="xterm-cursor-layer"
|
||||
style="z-index: 3;"
|
||||
/>
|
||||
<div
|
||||
class="xterm-decoration-container"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
37
src/techincal/shell-sessions/local.test.ts
Normal file
37
src/techincal/shell-sessions/local.test.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import getShellAuthTokenChannelHandlerInjectable from "../../main/lens-proxy/proxy-functions/shell/auth-token-channel-handler.injectable";
|
||||
import type { GetShellAuthToken } from "../../common/shell-authentication/get-auth-token.injectable";
|
||||
|
||||
describe("local shell session techincal tests", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
let result: RenderResult;
|
||||
let authenticationSpy: jest.SpyInstance<Uint8Array | Promise<Uint8Array>, Parameters<GetShellAuthToken>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
builder = getApplicationBuilder()
|
||||
.beforeRender(() => {
|
||||
const shellAuthentication = builder.dis.mainDi.inject(getShellAuthTokenChannelHandlerInjectable);
|
||||
|
||||
authenticationSpy = jest.spyOn(shellAuthentication, "handler");
|
||||
});
|
||||
builder.setEnvironmentToClusterFrame();
|
||||
|
||||
result = await builder.render();
|
||||
builder.dock.click(0);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should call the authentication function", () => {
|
||||
expect(authenticationSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
198
yarn.lock
198
yarn.lock
@ -1108,6 +1108,21 @@
|
||||
lodash "^4.17.15"
|
||||
tmp-promise "^3.0.2"
|
||||
|
||||
"@mapbox/node-pre-gyp@^1.0.0":
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc"
|
||||
integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==
|
||||
dependencies:
|
||||
detect-libc "^2.0.0"
|
||||
https-proxy-agent "^5.0.0"
|
||||
make-dir "^3.1.0"
|
||||
node-fetch "^2.6.7"
|
||||
nopt "^5.0.0"
|
||||
npmlog "^5.0.1"
|
||||
rimraf "^3.0.2"
|
||||
semver "^7.3.5"
|
||||
tar "^6.1.11"
|
||||
|
||||
"@material-ui/core@^4.12.3":
|
||||
version "4.12.4"
|
||||
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.12.4.tgz#4ac17488e8fcaf55eb6a7f5efb2a131e10138a73"
|
||||
@ -2944,6 +2959,11 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1:
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||
integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==
|
||||
|
||||
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
@ -3028,7 +3048,7 @@ aproba@^1.0.3, aproba@^1.1.1, aproba@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
||||
integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
|
||||
|
||||
"aproba@^1.1.2 || 2", aproba@^2.0.0:
|
||||
"aproba@^1.0.3 || ^2.0.0", "aproba@^1.1.2 || 2", aproba@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
|
||||
integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
|
||||
@ -3038,6 +3058,14 @@ archy@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40"
|
||||
integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=
|
||||
|
||||
are-we-there-yet@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
|
||||
integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
|
||||
dependencies:
|
||||
delegates "^1.0.0"
|
||||
readable-stream "^3.6.0"
|
||||
|
||||
are-we-there-yet@~1.1.2:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146"
|
||||
@ -3687,6 +3715,15 @@ caniuse-lite@^1.0.30001332:
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001339.tgz#f9aece4ea8156071613b27791547ba0b33f176cf"
|
||||
integrity sha512-Es8PiVqCe+uXdms0Gu5xP5PF2bxLR7OBp3wUzUnuO7OHzhOfCyg3hdiGWVPVxhiuniOzng+hTc1u3fEQ0TlkSQ==
|
||||
|
||||
canvas@^2.9.3:
|
||||
version "2.9.3"
|
||||
resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.9.3.tgz#8723c4f970442d4cdcedba5221579f9660a58bdb"
|
||||
integrity sha512-WOUM7ghii5TV2rbhaZkh1youv/vW1/Canev6Yx6BG2W+1S07w8jKZqKkPnbiPpQEDsnJdN8ouDd7OvQEGXDcUw==
|
||||
dependencies:
|
||||
"@mapbox/node-pre-gyp" "^1.0.0"
|
||||
nan "^2.15.0"
|
||||
simple-get "^3.0.3"
|
||||
|
||||
capture-stack-trace@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
|
||||
@ -3697,6 +3734,17 @@ caseless@~0.12.0:
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||
|
||||
chalk@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
||||
integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==
|
||||
dependencies:
|
||||
ansi-styles "^2.2.1"
|
||||
escape-string-regexp "^1.0.2"
|
||||
has-ansi "^2.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
@ -3980,6 +4028,11 @@ color-string@^1.6.0, color-string@^1.9.0:
|
||||
color-name "^1.0.0"
|
||||
simple-swizzle "^0.2.2"
|
||||
|
||||
color-support@^1.1.2:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
|
||||
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
|
||||
|
||||
color@^3.1.3, color@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164"
|
||||
@ -4453,11 +4506,6 @@ cssesc@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||
|
||||
cssfontparser@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/cssfontparser/-/cssfontparser-1.2.1.tgz#f4022fc8f9700c68029d542084afbaf425a3f3e3"
|
||||
integrity sha1-9AIvyPlwDGgCnVQghK+69CWj8+M=
|
||||
|
||||
cssom@^0.4.4:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
|
||||
@ -4594,6 +4642,13 @@ decompress-response@^3.3.0:
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
decompress-response@^4.2.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
|
||||
integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
|
||||
dependencies:
|
||||
mimic-response "^2.0.0"
|
||||
|
||||
decompress-response@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
|
||||
@ -5414,7 +5469,7 @@ escape-html@~1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
||||
|
||||
escape-string-regexp@^1.0.5:
|
||||
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
@ -6220,6 +6275,21 @@ functions-have-names@^1.2.2:
|
||||
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
|
||||
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
|
||||
|
||||
gauge@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
|
||||
integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
|
||||
dependencies:
|
||||
aproba "^1.0.3 || ^2.0.0"
|
||||
color-support "^1.1.2"
|
||||
console-control-strings "^1.0.0"
|
||||
has-unicode "^2.0.1"
|
||||
object-assign "^4.1.1"
|
||||
signal-exit "^3.0.0"
|
||||
string-width "^4.2.3"
|
||||
strip-ansi "^6.0.1"
|
||||
wide-align "^1.1.2"
|
||||
|
||||
gauge@~2.7.3:
|
||||
version "2.7.4"
|
||||
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
|
||||
@ -6292,6 +6362,11 @@ get-package-type@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
|
||||
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
|
||||
|
||||
get-stdin@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
|
||||
integrity sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==
|
||||
|
||||
get-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
||||
@ -6434,6 +6509,11 @@ globby@^11.0.1, globby@^11.1.0:
|
||||
merge2 "^1.4.1"
|
||||
slash "^3.0.0"
|
||||
|
||||
glur@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glur/-/glur-1.1.2.tgz#f20ea36db103bfc292343921f1f91e83c3467689"
|
||||
integrity sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==
|
||||
|
||||
got@^11.8.5:
|
||||
version "11.8.5"
|
||||
resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046"
|
||||
@ -6547,6 +6627,13 @@ harmony-reflect@^1.4.6:
|
||||
resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710"
|
||||
integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==
|
||||
|
||||
has-ansi@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
|
||||
integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
has-bigints@^1.0.1, has-bigints@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
|
||||
@ -6581,7 +6668,7 @@ has-tostringtag@^1.0.0:
|
||||
dependencies:
|
||||
has-symbols "^1.0.2"
|
||||
|
||||
has-unicode@^2.0.0, has-unicode@~2.0.1:
|
||||
has-unicode@^2.0.0, has-unicode@^2.0.1, has-unicode@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||
integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
|
||||
@ -7492,14 +7579,6 @@ jake@^10.8.5:
|
||||
filelist "^1.0.1"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
jest-canvas-mock@^2.3.1:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.4.0.tgz#947b71442d7719f8e055decaecdb334809465341"
|
||||
integrity sha512-mmMpZzpmLzn5vepIaHk5HoH3Ka4WykbSoLuG/EKoJd0x0ID/t+INo1l8ByfcUJuDM+RIsL4QDg/gDnBbrj2/IQ==
|
||||
dependencies:
|
||||
cssfontparser "^1.2.1"
|
||||
moo-color "^1.0.2"
|
||||
|
||||
jest-changed-files@^28.0.2:
|
||||
version "28.0.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.0.2.tgz#7d7810660a5bd043af9e9cfbe4d58adb05e91531"
|
||||
@ -7665,6 +7744,21 @@ jest-haste-map@^28.1.1:
|
||||
optionalDependencies:
|
||||
fsevents "^2.3.2"
|
||||
|
||||
jest-image-snapshot@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-image-snapshot/-/jest-image-snapshot-5.1.0.tgz#8282abab770d6943b5f78498eca548cfebc4c1d5"
|
||||
integrity sha512-GpTaiJehsVeMRvszSuMkRK0ODSHxr31zalk3qoHVqHKGztgYlJ+Y6AheWHU6azODuoVpMQH52zY/sxri9d/GaA==
|
||||
dependencies:
|
||||
chalk "^1.1.3"
|
||||
get-stdin "^5.0.1"
|
||||
glur "^1.1.2"
|
||||
lodash "^4.17.4"
|
||||
mkdirp "^0.5.1"
|
||||
pixelmatch "^5.1.0"
|
||||
pngjs "^3.4.0"
|
||||
rimraf "^2.6.2"
|
||||
ssim.js "^3.1.1"
|
||||
|
||||
jest-leak-detector@^28.1.1:
|
||||
version "28.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.1.tgz#537f37afd610a4b3f4cab15e06baf60484548efb"
|
||||
@ -8887,6 +8981,11 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
|
||||
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
|
||||
|
||||
mimic-response@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
|
||||
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
|
||||
|
||||
mimic-response@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
|
||||
@ -9073,13 +9172,6 @@ monaco-editor@^0.29.1:
|
||||
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.29.1.tgz#6ee93d8a5320704d48fd7058204deed72429c020"
|
||||
integrity sha512-rguaEG/zrPQSaKzQB7IfX/PpNa0qxF1FY8ZXRkN4WIl8qZdTQRSRJCtRto7IMcSgrU6H53RXI+fTcywOBC4aVw==
|
||||
|
||||
moo-color@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/moo-color/-/moo-color-1.0.3.tgz#d56435f8359c8284d83ac58016df7427febece74"
|
||||
integrity sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==
|
||||
dependencies:
|
||||
color-name "^1.1.4"
|
||||
|
||||
move-concurrently@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
||||
@ -9125,6 +9217,11 @@ nan@^2.14.0:
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
|
||||
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
|
||||
|
||||
nan@^2.15.0:
|
||||
version "2.16.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916"
|
||||
integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==
|
||||
|
||||
nanoid@^3.3.4:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
||||
@ -9198,7 +9295,7 @@ node-fetch-npm@^2.0.2:
|
||||
json-parse-better-errors "^1.0.0"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
node-fetch@2.6.7:
|
||||
node-fetch@2.6.7, node-fetch@^2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||
@ -9600,6 +9697,16 @@ npmlog@^4.1.2, npmlog@~4.1.2:
|
||||
gauge "~2.7.3"
|
||||
set-blocking "~2.0.0"
|
||||
|
||||
npmlog@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
|
||||
integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
|
||||
dependencies:
|
||||
are-we-there-yet "^2.0.0"
|
||||
console-control-strings "^1.1.0"
|
||||
gauge "^3.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
|
||||
nth-check@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2"
|
||||
@ -10151,6 +10258,13 @@ pirates@^4.0.4:
|
||||
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
|
||||
integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
|
||||
|
||||
pixelmatch@^5.1.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.3.0.tgz#5e5321a7abedfb7962d60dbf345deda87cb9560a"
|
||||
integrity sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==
|
||||
dependencies:
|
||||
pngjs "^6.0.0"
|
||||
|
||||
pkg-dir@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
|
||||
@ -10185,6 +10299,16 @@ plist@^3.0.1, plist@^3.0.4:
|
||||
base64-js "^1.5.1"
|
||||
xmlbuilder "^9.0.7"
|
||||
|
||||
pngjs@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
|
||||
integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
|
||||
|
||||
pngjs@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821"
|
||||
integrity sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==
|
||||
|
||||
popper.js@1.16.1-lts:
|
||||
version "1.16.1-lts"
|
||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05"
|
||||
@ -11086,6 +11210,11 @@ reserved-words@^0.1.2:
|
||||
resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"
|
||||
integrity sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE=
|
||||
|
||||
resize-observer-polyfill@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||
|
||||
resolve-alpn@^1.0.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
|
||||
@ -11550,6 +11679,15 @@ simple-concat@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
|
||||
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
|
||||
|
||||
simple-get@^3.0.3:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55"
|
||||
integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==
|
||||
dependencies:
|
||||
decompress-response "^4.2.0"
|
||||
once "^1.3.1"
|
||||
simple-concat "^1.0.0"
|
||||
|
||||
simple-get@^4.0.0, simple-get@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
|
||||
@ -11796,6 +11934,11 @@ sshpk@^1.7.0:
|
||||
safer-buffer "^2.0.2"
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
ssim.js@^3.1.1:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/ssim.js/-/ssim.js-3.5.0.tgz#d7276b9ee99b57a5ff0db34035f02f35197e62df"
|
||||
integrity sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==
|
||||
|
||||
ssri@^6.0.0, ssri@^6.0.1, ssri@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
|
||||
@ -12073,6 +12216,11 @@ sumchecker@^3.0.1:
|
||||
dependencies:
|
||||
debug "^4.1.0"
|
||||
|
||||
supports-color@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||
integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==
|
||||
|
||||
supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
@ -13252,7 +13400,7 @@ which@^2.0.1, which@^2.0.2:
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wide-align@^1.1.0:
|
||||
wide-align@^1.1.0, wide-align@^1.1.2:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
|
||||
integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
|
||||
|
||||
Loading…
Reference in New Issue
Block a user