mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Stop using @electron/remote to obtain app.getPath() (#4078)
* Stop using remote to obtain app.getPath() - Initialize entire map in bootstrap() - Updated unit tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Resolve PR comments Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix test Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix build Signed-off-by: Sebastian Malton <sebastian@malton.name> * Ensure that init can only be called once and catch errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace basically all uses of app.getPath() with AppPaths.get() Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix unit tests Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
65b6908bf1
commit
f297407156
45 files changed
+521
-200
No files matched your search
@@ -30,6 +30,7 @@ import { Console } from "console";
|
||||
import { stdout, stderr } from "process";
|
||||
import type { ClusterId } from "../cluster-types";
|
||||
import { getCustomKubeConfigPath } from "../utils";
|
||||
import { AppPaths } from "../app-paths";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
@@ -67,23 +68,26 @@ function embed(clusterId: ClusterId, contents: any): string {
|
||||
return absPath;
|
||||
}
|
||||
|
||||
jest.mock("electron", () => {
|
||||
return {
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
getPath: () => "tmp",
|
||||
getLocale: () => "en",
|
||||
setLoginItemSettings: jest.fn(),
|
||||
},
|
||||
ipcMain: {
|
||||
handle: jest.fn(),
|
||||
on: jest.fn(),
|
||||
removeAllListeners: jest.fn(),
|
||||
off: jest.fn(),
|
||||
send: jest.fn(),
|
||||
}
|
||||
};
|
||||
});
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
getName: () => "lens",
|
||||
setName: jest.fn(),
|
||||
setPath: jest.fn(),
|
||||
getPath: () => "tmp",
|
||||
getLocale: () => "en",
|
||||
setLoginItemSettings: jest.fn(),
|
||||
},
|
||||
ipcMain: {
|
||||
handle: jest.fn(),
|
||||
on: jest.fn(),
|
||||
removeAllListeners: jest.fn(),
|
||||
off: jest.fn(),
|
||||
send: jest.fn(),
|
||||
}
|
||||
}));
|
||||
|
||||
AppPaths.init();
|
||||
|
||||
describe("empty config", () => {
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import { anyObject } from "jest-mock-extended";
|
||||
import mockFs from "mock-fs";
|
||||
import logger from "../../main/logger";
|
||||
import { AppPaths } from "../app-paths";
|
||||
import { ClusterStore } from "../cluster-store";
|
||||
import { HotbarStore } from "../hotbar-store";
|
||||
|
||||
@@ -116,16 +117,23 @@ const awsCluster = {
|
||||
}
|
||||
};
|
||||
|
||||
jest.mock("electron", () => {
|
||||
return {
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
getPath: () => "tmp",
|
||||
getLocale: () => "en",
|
||||
setLoginItemSettings: (): void => void 0,
|
||||
}
|
||||
};
|
||||
});
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
getName: () => "lens",
|
||||
setName: jest.fn(),
|
||||
setPath: jest.fn(),
|
||||
getPath: () => "tmp",
|
||||
getLocale: () => "en",
|
||||
setLoginItemSettings: jest.fn(),
|
||||
},
|
||||
ipcMain: {
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
AppPaths.init();
|
||||
|
||||
describe("HotbarStore", () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -21,16 +21,21 @@
|
||||
|
||||
import mockFs from "mock-fs";
|
||||
|
||||
jest.mock("electron", () => {
|
||||
return {
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
getPath: () => "tmp",
|
||||
getLocale: () => "en",
|
||||
setLoginItemSettings: (): void => void 0,
|
||||
}
|
||||
};
|
||||
});
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
getName: () => "lens",
|
||||
setName: jest.fn(),
|
||||
setPath: jest.fn(),
|
||||
getPath: () => "tmp",
|
||||
getLocale: () => "en",
|
||||
setLoginItemSettings: jest.fn(),
|
||||
},
|
||||
ipcMain: {
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { UserStore } from "../user-store";
|
||||
import { Console } from "console";
|
||||
@@ -39,8 +44,10 @@ import electron from "electron";
|
||||
import { stdout, stderr } from "process";
|
||||
import { ThemeStore } from "../../renderer/theme.store";
|
||||
import type { ClusterStoreModel } from "../cluster-store";
|
||||
import { AppPaths } from "../app-paths";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
AppPaths.init();
|
||||
|
||||
describe("user store tests", () => {
|
||||
describe("for an empty config", () => {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { app, ipcMain, ipcRenderer } from "electron";
|
||||
import { observable, when } from "mobx";
|
||||
import path from "path";
|
||||
import logger from "./logger";
|
||||
import { fromEntries, toJS } from "./utils";
|
||||
import { isWindows } from "./vars";
|
||||
|
||||
export type PathName = Parameters<typeof app["getPath"]>[0];
|
||||
|
||||
const pathNames: PathName[] = [
|
||||
"home",
|
||||
"appData",
|
||||
"userData",
|
||||
"cache",
|
||||
"temp",
|
||||
"exe",
|
||||
"module",
|
||||
"desktop",
|
||||
"documents",
|
||||
"downloads",
|
||||
"music",
|
||||
"pictures",
|
||||
"videos",
|
||||
"logs",
|
||||
"crashDumps",
|
||||
];
|
||||
|
||||
if (isWindows) {
|
||||
pathNames.push("recent");
|
||||
}
|
||||
|
||||
export class AppPaths {
|
||||
private static paths = observable.box<Record<PathName, string> | undefined>();
|
||||
private static readonly ipcChannel = "get-app-paths";
|
||||
|
||||
/**
|
||||
* Initializes the local copy of the paths from electron.
|
||||
*/
|
||||
static async init(): Promise<void> {
|
||||
logger.info(`[APP-PATHS]: initializing`);
|
||||
|
||||
if (AppPaths.paths.get()) {
|
||||
return void logger.error("[APP-PATHS]: init called more than once");
|
||||
}
|
||||
|
||||
if (ipcMain) {
|
||||
AppPaths.initMain();
|
||||
} else {
|
||||
await AppPaths.initRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
private static initMain(): void {
|
||||
if (process.env.CICD) {
|
||||
app.setPath("appData", process.env.CICD);
|
||||
}
|
||||
|
||||
app.setPath("userData", path.join(app.getPath("appData"), app.getName()));
|
||||
|
||||
AppPaths.paths.set(fromEntries(pathNames.map(pathName => [pathName, app.getPath(pathName)])));
|
||||
ipcMain.handle(AppPaths.ipcChannel, () => toJS(AppPaths.paths.get()));
|
||||
}
|
||||
|
||||
private static async initRenderer(): Promise<void> {
|
||||
const paths = await ipcRenderer.invoke(AppPaths.ipcChannel);
|
||||
|
||||
if (!paths || typeof paths !== "object") {
|
||||
throw Object.assign(new Error("[APP-PATHS]: ipc handler returned unexpected data"), { data: paths });
|
||||
}
|
||||
|
||||
AppPaths.paths.set(paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative to `app.getPath()` for use in renderer and common.
|
||||
* This function throws if called before initialization.
|
||||
* @param name The name of the path field
|
||||
*/
|
||||
static get(name: PathName): string {
|
||||
if (!AppPaths.paths.get()) {
|
||||
throw new Error("AppPaths.init() has not been called");
|
||||
}
|
||||
|
||||
return AppPaths.paths.get()[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* An async version of `AppPaths.get()` which waits for `AppPaths.init()` to
|
||||
* be called before returning
|
||||
*/
|
||||
static async getAsync(name: PathName): Promise<string> {
|
||||
await when(() => Boolean(AppPaths.paths.get()));
|
||||
|
||||
return AppPaths.paths.get()[name];
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { isTestEnv } from "./vars";
|
||||
import { kebabCase } from "lodash";
|
||||
import { getPath } from "./utils/getPath";
|
||||
import { AppPaths } from "./app-paths";
|
||||
|
||||
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
||||
syncOptions?: IReactionOptions;
|
||||
@@ -93,7 +93,7 @@ export abstract class BaseStore<T> extends Singleton {
|
||||
}
|
||||
|
||||
protected cwd() {
|
||||
return getPath("userData");
|
||||
return AppPaths.get("userData");
|
||||
}
|
||||
|
||||
protected async saveToFile(model: T) {
|
||||
|
||||
@@ -19,13 +19,12 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { ipcMain } from "electron";
|
||||
import { app, ipcMain } from "electron";
|
||||
import winston, { format } from "winston";
|
||||
import type Transport from "winston-transport";
|
||||
import { consoleFormat } from "winston-console-format";
|
||||
import { isDebugging, isTestEnv } from "./vars";
|
||||
import BrowserConsole from "winston-transport-browserconsole";
|
||||
import { getPath } from "./utils/getPath";
|
||||
|
||||
const logLevel = process.env.LOG_LEVEL
|
||||
? process.env.LOG_LEVEL
|
||||
@@ -66,7 +65,11 @@ if (ipcMain) {
|
||||
handleExceptions: false,
|
||||
level: logLevel,
|
||||
filename: "lens.log",
|
||||
dirname: getPath("logs"),
|
||||
/**
|
||||
* SAFTEY: the `ipcMain` check above should mean that this is only
|
||||
* called in the main process
|
||||
*/
|
||||
dirname: app.getPath("logs"),
|
||||
maxsize: 16 * 1024,
|
||||
maxFiles: 16,
|
||||
tailable: true,
|
||||
|
||||
@@ -33,7 +33,7 @@ import { ObservableToggleSet, toJS } from "../../renderer/utils";
|
||||
import { DESCRIPTORS, KubeconfigSyncValue, UserPreferencesModel, EditorConfiguration } from "./preferences-helpers";
|
||||
import logger from "../../main/logger";
|
||||
import type { monaco } from "react-monaco-editor";
|
||||
import { getPath } from "../utils/getPath";
|
||||
import { AppPaths } from "../app-paths";
|
||||
|
||||
export interface UserStoreModel {
|
||||
lastSeenAppVersion: string;
|
||||
@@ -257,5 +257,5 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
|
||||
* @returns string
|
||||
*/
|
||||
export function getDefaultKubectlDownloadPath(): string {
|
||||
return path.join(getPath("userData"), "binaries");
|
||||
return path.join(AppPaths.get("userData"), "binaries");
|
||||
}
|
||||
@@ -46,6 +46,11 @@ export function getClusterFrameUrl(clusterId: ClusterId) {
|
||||
* Get the result of `getClusterIdFromHost` from the current `location.host`
|
||||
*/
|
||||
export function getHostedClusterId(): ClusterId | undefined {
|
||||
// catch being called in main
|
||||
if (typeof location === "undefined") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getClusterIdFromHost(location.host);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,19 +32,21 @@ export * from "./base64";
|
||||
export * from "./camelCase";
|
||||
export * from "./cloneJson";
|
||||
export * from "./cluster-id-url-parsing";
|
||||
export * from "./convertCpu";
|
||||
export * from "./convertMemory";
|
||||
export * from "./debouncePromise";
|
||||
export * from "./defineGlobal";
|
||||
export * from "./delay";
|
||||
export * from "./disposer";
|
||||
export * from "./downloadFile";
|
||||
export * from "./formatDuration";
|
||||
export * from "./escapeRegExp";
|
||||
export * from "./extended-map";
|
||||
export * from "./getPath";
|
||||
export * from "./formatDuration";
|
||||
export * from "./getRandId";
|
||||
export * from "./hash-set";
|
||||
export * from "./local-kubeconfig";
|
||||
export * from "./n-fircate";
|
||||
export * from "./objects";
|
||||
export * from "./openExternal";
|
||||
export * from "./paths";
|
||||
export * from "./reject-promise";
|
||||
@@ -56,8 +58,6 @@ export * from "./toggle-set";
|
||||
export * from "./toJS";
|
||||
export * from "./type-narrowing";
|
||||
export * from "./types";
|
||||
export * from "./convertMemory";
|
||||
export * from "./convertCpu";
|
||||
|
||||
import * as iter from "./iter";
|
||||
import * as array from "./array";
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
|
||||
import path from "path";
|
||||
import * as uuid from "uuid";
|
||||
import { AppPaths } from "../app-paths";
|
||||
import type { ClusterId } from "../cluster-types";
|
||||
import { getPath } from "./getPath";
|
||||
|
||||
export function storedKubeConfigFolder(): string {
|
||||
return path.resolve(getPath("userData"), "kubeconfigs");
|
||||
return path.resolve(AppPaths.get("userData"), "kubeconfigs");
|
||||
}
|
||||
|
||||
export function getCustomKubeConfigPath(clusterId: ClusterId = uuid.v4()): string {
|
||||
|
||||
@@ -19,19 +19,10 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { app, ipcMain } from "electron";
|
||||
|
||||
const remote = ipcMain ? null : require("@electron/remote");
|
||||
|
||||
/**
|
||||
* calls getPath either on app or on the remote's app
|
||||
*
|
||||
* @deprecated Use a different method for accessing the getPath function
|
||||
* A better typed version of `Object.fromEntries` where the keys are known to
|
||||
* be a specific subset
|
||||
*/
|
||||
export function getPath(name: Parameters<typeof app["getPath"]>[0]): string {
|
||||
if (app) {
|
||||
return app.getPath(name);
|
||||
}
|
||||
|
||||
return remote.app.getPath(name);
|
||||
export function fromEntries<T, Key extends string>(entries: Iterable<readonly [Key, T]>): { [k in Key]: T } {
|
||||
return Object.fromEntries(entries) as { [k in Key]: T };
|
||||
}
|
||||
Reference in new issue
Block a user