diff --git a/src/common/user-store/download-kubectl-binaries.injectable.ts b/src/common/user-store/download-kubectl-binaries.injectable.ts new file mode 100644 index 0000000000..ea9d3f5452 --- /dev/null +++ b/src/common/user-store/download-kubectl-binaries.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import userStoreInjectable from "./user-store.injectable"; + +const downloadKubectlBinariesInjectable = getInjectable({ + id: "download-kubectl-binaries", + instantiate: (di) => { + const store = di.inject(userStoreInjectable); + + return computed(() => store.downloadKubectlBinaries); + }, +}); + +export default downloadKubectlBinariesInjectable; diff --git a/src/common/user-store/kubectl-binaries-path.injectable.ts b/src/common/user-store/kubectl-binaries-path.injectable.ts new file mode 100644 index 0000000000..01de50b826 --- /dev/null +++ b/src/common/user-store/kubectl-binaries-path.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import userStoreInjectable from "./user-store.injectable"; + +const kubectlBinariesPathInjectable = getInjectable({ + id: "kubectl-binaries-path", + instantiate: (di) => { + const store = di.inject(userStoreInjectable); + + return computed(() => store.kubectlBinariesPath); + }, +}); + +export default kubectlBinariesPathInjectable; diff --git a/src/main/__test__/shell-session.test.ts b/src/main/__test__/shell-session.test.ts index 1fe8915e26..c7ac77efed 100644 --- a/src/main/__test__/shell-session.test.ts +++ b/src/main/__test__/shell-session.test.ts @@ -7,14 +7,14 @@ import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars"; describe("clearKubeconfigEnvVars tests", () => { it("should not touch non kubeconfig keys", () => { - expect(clearKubeconfigEnvVars({ a: 1 })).toStrictEqual({ a: 1 }); + expect(clearKubeconfigEnvVars({ a: "1" })).toStrictEqual({ a: 1 }); }); it("should remove a single kubeconfig key", () => { - expect(clearKubeconfigEnvVars({ a: 1, kubeconfig: "1" })).toStrictEqual({ a: 1 }); + expect(clearKubeconfigEnvVars({ a: "1", kubeconfig: "1" })).toStrictEqual({ a: 1 }); }); it("should remove a two kubeconfig key", () => { - expect(clearKubeconfigEnvVars({ a: 1, kubeconfig: "1", kUbeconfig: "1" })).toStrictEqual({ a: 1 }); + expect(clearKubeconfigEnvVars({ a: "1", kubeconfig: "1", kUbeconfig: "1" })).toStrictEqual({ a: 1 }); }); }); diff --git a/src/main/shell-session/local-shell-session/local-shell-session.ts b/src/main/shell-session/local-shell-session/local-shell-session.ts index ec8cc20da5..b655af659d 100644 --- a/src/main/shell-session/local-shell-session/local-shell-session.ts +++ b/src/main/shell-session/local-shell-session/local-shell-session.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { IComputedValue } from "mobx"; 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, ShellSessionDependencies } from "../shell-session"; import { ShellSession } from "../shell-session"; @@ -12,6 +12,8 @@ import { ShellSession } from "../shell-session"; export interface LocalShellSessionDependencies extends ShellSessionDependencies { terminalShellEnvModify: TerminalShellEnvModify; readonly baseBundeledBinariesDirectory: string; + readonly kubectlBinariesPath: IComputedValue; + readonly downloadKubectlBinaries: IComputedValue; } export class LocalShellSession extends ShellSession { @@ -30,11 +32,8 @@ export class LocalShellSession extends ShellSession { } public async open() { - let env = await this.getCachedShellEnv(); - - // extensions can modify the env - env = this.dependencies.terminalShellEnvModify(this.cluster.id, env); - + const cachedEnv = await this.getCachedShellEnv(); + const env = this.dependencies.terminalShellEnvModify(this.cluster.id, cachedEnv); const shell = env.PTYSHELL; if (!shell) { @@ -47,8 +46,8 @@ export class LocalShellSession extends ShellSession { } protected async getShellArgs(shell: string): Promise { - const pathFromPreferences = UserStore.getInstance().kubectlBinariesPath || this.kubectl.getBundledPath(); - const kubectlPathDir = UserStore.getInstance().downloadKubectlBinaries ? await this.kubectlBinDirP : path.dirname(pathFromPreferences); + const pathFromPreferences = this.dependencies.kubectlBinariesPath.get() || this.kubectl.getBundledPath(); + const kubectlPathDir = this.dependencies.downloadKubectlBinaries.get() ? await this.kubectlBinDirP : path.dirname(pathFromPreferences); switch(path.basename(shell)) { case "powershell.exe": diff --git a/src/main/shell-session/local-shell-session/open-local-shell-session.injectable.ts b/src/main/shell-session/local-shell-session/open-local-shell-session.injectable.ts index 2456e6e139..aa89353ffa 100644 --- a/src/main/shell-session/local-shell-session/open-local-shell-session.injectable.ts +++ b/src/main/shell-session/local-shell-session/open-local-shell-session.injectable.ts @@ -10,6 +10,12 @@ import type WebSocket from "ws"; import terminalShellEnvModifyInjectable from "../shell-env-modifier/terminal-shell-env-modify.injectable"; import baseBundeledBinariesDirectoryInjectable from "../../../common/vars/base-bundled-binaries-dir.injectable"; import type { Kubectl } from "../../kubectl/kubectl"; +import kubectlBinariesPathInjectable from "../../../common/user-store/kubectl-binaries-path.injectable"; +import downloadKubectlBinariesInjectable from "../../../common/user-store/download-kubectl-binaries.injectable"; +import ensureShellProcessInjectable from "../ensure-shell-process.injectable"; +import getCachedShellEnvInjectable from "../get-cached-shell-env.injectable"; +import getValidCwdInjectable from "../get-valid-cwd.injectable"; +import loggerInjectable from "../../../common/logger.injectable"; export interface OpenLocalShellSessionArgs { websocket: WebSocket; @@ -26,6 +32,12 @@ const openLocalShellSessionInjectable = getInjectable({ const deps: LocalShellSessionDependencies = { terminalShellEnvModify: di.inject(terminalShellEnvModifyInjectable), baseBundeledBinariesDirectory: di.inject(baseBundeledBinariesDirectoryInjectable), + kubectlBinariesPath: di.inject(kubectlBinariesPathInjectable), + downloadKubectlBinaries: di.inject(downloadKubectlBinariesInjectable), + ensureShellProcess: di.inject(ensureShellProcessInjectable), + getCachedShellEnv: di.inject(getCachedShellEnvInjectable), + getValidCwd: di.inject(getValidCwdInjectable), + logger: di.inject(loggerInjectable), }; return (args) => new LocalShellSession(deps, args).open(); diff --git a/src/main/shell-session/node-shell-session/open-node-shell-session.injectable.ts b/src/main/shell-session/node-shell-session/open-node-shell-session.injectable.ts index 3f32bacc30..38ad82016c 100644 --- a/src/main/shell-session/node-shell-session/open-node-shell-session.injectable.ts +++ b/src/main/shell-session/node-shell-session/open-node-shell-session.injectable.ts @@ -5,8 +5,13 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { Cluster } from "../../../common/cluster/cluster"; import type WebSocket from "ws"; +import type { NodeShellSessionDependencies } from "./node-shell-session"; import { NodeShellSession } from "./node-shell-session"; import type { Kubectl } from "../../kubectl/kubectl"; +import loggerInjectable from "../../../common/logger.injectable"; +import ensureShellProcessInjectable from "../ensure-shell-process.injectable"; +import getCachedShellEnvInjectable from "../get-cached-shell-env.injectable"; +import getValidCwdInjectable from "../get-valid-cwd.injectable"; export interface OpenNodeShellSessionArgs { websocket: WebSocket; @@ -21,7 +26,16 @@ export type OpenNodeShellSession = (args: OpenNodeShellSessionArgs) => Promise (args) => new NodeShellSession(args).open(), + instantiate: (di): OpenNodeShellSession => { + const dependencies: NodeShellSessionDependencies = { + ensureShellProcess: di.inject(ensureShellProcessInjectable), + getCachedShellEnv: di.inject(getCachedShellEnvInjectable), + getValidCwd: di.inject(getValidCwdInjectable), + logger: di.inject(loggerInjectable), + }; + + return (args) => new NodeShellSession(dependencies, args).open(); + }, causesSideEffects: true, }); diff --git a/src/main/shell-session/spawn-pty.injectable.ts b/src/main/shell-session/spawn-pty.injectable.ts index b106822e5f..759c6f7451 100644 --- a/src/main/shell-session/spawn-pty.injectable.ts +++ b/src/main/shell-session/spawn-pty.injectable.ts @@ -5,9 +5,11 @@ import { getInjectable } from "@ogre-tools/injectable"; import { spawn } from "node-pty"; +export type SpawnPty = typeof spawn; + const spawnPtyInjectable = getInjectable({ id: "spawn-pty", - instantiate: () => spawn, + instantiate: (): SpawnPty => spawn, causesSideEffects: true, }); diff --git a/src/renderer/components/dock/terminal/create-terminal.injectable.ts b/src/renderer/components/dock/terminal/create-terminal.injectable.ts index 4de493a334..c8920ac06f 100644 --- a/src/renderer/components/dock/terminal/create-terminal.injectable.ts +++ b/src/renderer/components/dock/terminal/create-terminal.injectable.ts @@ -12,6 +12,7 @@ import terminalConfigInjectable from "../../../../common/user-store/terminal-con import terminalCopyOnSelectInjectable from "../../../../common/user-store/terminal-copy-on-select.injectable"; import themeStoreInjectable from "../../../themes/store.injectable"; import allowTerminalTransparencyInjectable from "./allow-transparency.injectable"; +import loggerInjectable from "../../../../common/logger.injectable"; export type CreateTerminal = (tabId: TabId, api: TerminalApi) => Terminal; @@ -24,6 +25,7 @@ const createTerminalInjectable = getInjectable({ terminalCopyOnSelect: di.inject(terminalCopyOnSelectInjectable), themeStore: di.inject(themeStoreInjectable), allowTransparency: di.inject(allowTerminalTransparencyInjectable), + logger: di.inject(loggerInjectable), }; return (tabId, api) => new Terminal(dependencies, { tabId, api }); diff --git a/src/renderer/components/dock/terminal/terminal.ts b/src/renderer/components/dock/terminal/terminal.ts index 1a2f7cae8d..6c8bb9e47a 100644 --- a/src/renderer/components/dock/terminal/terminal.ts +++ b/src/renderer/components/dock/terminal/terminal.ts @@ -15,7 +15,7 @@ import { disposer } from "../../../utils"; import { isMac } from "../../../../common/vars"; import { once } from "lodash"; import { clipboard } from "electron"; -import logger from "../../../../common/logger"; +import type { Logger } from "../../../../common/logger"; import type { TerminalConfig } from "../../../../common/user-store/preferences-helpers"; import assert from "assert"; import { TerminalChannels } from "../../../../common/terminal/channels"; @@ -26,6 +26,7 @@ export interface TerminalDependencies { readonly terminalCopyOnSelect: IComputedValue; readonly themeStore: ThemeStore; readonly allowTransparency: boolean; + readonly logger: Logger; } export interface TerminalArguments { @@ -196,14 +197,14 @@ export class Terminal { }; setFontSize = (fontSize: number) => { - logger.info(`[TERMINAL]: set fontSize to ${fontSize}`); + this.dependencies.logger.info(`[TERMINAL]: set fontSize to ${fontSize}`); this.xterm.options.fontSize = fontSize; this.fit(); }; setFontFamily = (fontFamily: string) => { - logger.info(`[TERMINAL]: set fontFamily to ${fontFamily}`); + this.dependencies.logger.info(`[TERMINAL]: set fontFamily to ${fontFamily}`); this.xterm.options.fontFamily = fontFamily; this.fit(); diff --git a/src/techincal/shell-sessions/local.test.ts b/src/techincal/shell-sessions/local.test.ts index 3ceacb8a54..a4c9109eaf 100644 --- a/src/techincal/shell-sessions/local.test.ts +++ b/src/techincal/shell-sessions/local.test.ts @@ -4,18 +4,44 @@ */ import type { RenderResult } from "@testing-library/react"; +import { waitFor } 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"; +import type { SpawnPty } from "../../main/shell-session/spawn-pty.injectable"; +import spawnPtyInjectable from "../../main/shell-session/spawn-pty.injectable"; +import type { IPty } from "node-pty"; describe("local shell session techincal tests", () => { let builder: ApplicationBuilder; let result: RenderResult; let authenticationSpy: jest.SpyInstance, Parameters>; + let spawnPtyMock: jest.MockedFunction; + let ptyMock: jest.MockedObject; beforeEach(async () => { builder = getApplicationBuilder() + .beforeApplicationStart(() => { + spawnPtyMock = jest.fn(); + builder.dis.mainDi.override(spawnPtyInjectable, () => spawnPtyMock); + + spawnPtyMock.mockImplementation(() => ptyMock = { + cols: 80, + rows: 40, + pid: 12346, + process: "my-mocked-shell", + handleFlowControl: true, + kill: jest.fn(), + onData: jest.fn(), + onExit: jest.fn(), + on: jest.fn(), + resize: jest.fn(), + write: jest.fn(), + pause: jest.fn(), + resume: jest.fn(), + }); + }) .beforeRender(() => { const shellAuthentication = builder.dis.mainDi.inject(getShellAuthTokenChannelHandlerInjectable); @@ -34,4 +60,9 @@ describe("local shell session techincal tests", () => { it("should call the authentication function", () => { expect(authenticationSpy).toBeCalled(); }); + + it("should create a pty instance", async () => { + await waitFor(() => expect(spawnPtyMock).toBeCalled()); + void ptyMock; + }); });