mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into wrap-pod-logs
This commit is contained in:
commit
31c7ae118d
1328 files changed
+73244
-43415
No files matched your search
@@ -18,13 +18,13 @@ import { createClusterInjectionToken } from "../cluster/create-cluster-injection
|
||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable from "../vars/app-version.injectable";
|
||||
import assert from "assert";
|
||||
import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||
import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable";
|
||||
import kubectlDownloadingNormalizedArchInjectable from "../../main/kubectl/normalized-arch.injectable";
|
||||
import normalizedPlatformInjectable from "../vars/normalized-platform.injectable";
|
||||
import fsInjectable from "../fs/fs.injectable";
|
||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
@@ -372,7 +372,7 @@ users:
|
||||
|
||||
mockFs(mockOpts);
|
||||
|
||||
mainDi.override(appVersionInjectable, () => "3.6.0");
|
||||
mainDi.override(storeMigrationVersionInjectable, () => "3.6.0");
|
||||
|
||||
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { AppEvent } from "../app-event-bus/event-bus";
|
||||
import { appEventBus } from "../app-event-bus/event-bus";
|
||||
import { assert, Console } from "console";
|
||||
import { stdout, stderr } from "process";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
describe("event bus tests", () => {
|
||||
describe("emit", () => {
|
||||
it("emits an event", () => {
|
||||
let event: AppEvent | undefined;
|
||||
|
||||
appEventBus.addListener((data) => {
|
||||
event = data;
|
||||
});
|
||||
|
||||
appEventBus.emit({ name: "foo", action: "bar" });
|
||||
assert(event);
|
||||
expect(event?.name).toBe("foo");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,6 @@ import mockFs from "mock-fs";
|
||||
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable from "../vars/app-version.injectable";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import hotbarStoreInjectable from "../hotbars/store.injectable";
|
||||
import type { HotbarStore } from "../hotbars/store";
|
||||
@@ -19,6 +18,7 @@ import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import type { Logger } from "../logger";
|
||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||
|
||||
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
|
||||
return {
|
||||
@@ -348,7 +348,7 @@ describe("HotbarStore", () => {
|
||||
|
||||
mockFs(configurationToBeMigrated);
|
||||
|
||||
di.override(appVersionInjectable, () => "5.0.0-beta.10");
|
||||
di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10");
|
||||
|
||||
hotbarStore = di.inject(hotbarStoreInjectable);
|
||||
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import https from "https";
|
||||
import os from "os";
|
||||
import { getMacRootCA, getWinRootCA, injectCAs, DSTRootCAX3 } from "../system-ca";
|
||||
import { dependencies, devDependencies } from "../../../package.json";
|
||||
import assert from "assert";
|
||||
|
||||
const deps = { ...dependencies, ...devDependencies };
|
||||
|
||||
// Skip the test if mac-ca is not installed, or os is not darwin
|
||||
(deps["mac-ca"] && os.platform().includes("darwin") ? describe: describe.skip)("inject CA for Mac", () => {
|
||||
// for reset https.globalAgent.options.ca after testing
|
||||
let _ca: string | Buffer | (string | Buffer)[] | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
_ca = https.globalAgent.options.ca;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
https.globalAgent.options.ca = _ca;
|
||||
});
|
||||
|
||||
/**
|
||||
* The test to ensure using getMacRootCA + injectCAs injects CAs in the same way as using
|
||||
* the auto injection (require('mac-ca'))
|
||||
*/
|
||||
it("should inject the same ca as mac-ca", async () => {
|
||||
const osxCAs = await getMacRootCA();
|
||||
|
||||
injectCAs(osxCAs);
|
||||
const injected = https.globalAgent.options.ca as (string | Buffer)[];
|
||||
|
||||
await import("mac-ca");
|
||||
const injectedByMacCA = https.globalAgent.options.ca as (string | Buffer)[];
|
||||
|
||||
expect(new Set(injected)).toEqual(new Set(injectedByMacCA));
|
||||
});
|
||||
|
||||
it("shouldn't included the expired DST Root CA X3 on Mac", async () => {
|
||||
const osxCAs = await getMacRootCA();
|
||||
|
||||
injectCAs(osxCAs);
|
||||
const injected = https.globalAgent.options.ca;
|
||||
|
||||
assert(injected);
|
||||
expect(injected.includes(DSTRootCAX3)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
// Skip the test if win-ca is not installed, or os is not win32
|
||||
(deps["win-ca"] && os.platform().includes("win32") ? describe: describe.skip)("inject CA for Windows", () => {
|
||||
// for reset https.globalAgent.options.ca after testing
|
||||
let _ca: string | Buffer | (string | Buffer)[] | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
_ca = https.globalAgent.options.ca;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
https.globalAgent.options.ca = _ca;
|
||||
});
|
||||
|
||||
/**
|
||||
* The test to ensure using win-ca/api injects CAs in the same way as using
|
||||
* the auto injection (require('win-ca').inject('+'))
|
||||
*/
|
||||
it("should inject the same ca as winca.inject('+')", async () => {
|
||||
const winCAs = await getWinRootCA();
|
||||
|
||||
const wincaAPI = await import("win-ca/api");
|
||||
|
||||
wincaAPI.inject("+", winCAs);
|
||||
const injected = https.globalAgent.options.ca as (string | Buffer)[];
|
||||
|
||||
const winca = await import("win-ca");
|
||||
|
||||
winca.inject("+"); // see: https://github.com/ukoloff/win-ca#caveats
|
||||
const injectedByWinCA = https.globalAgent.options.ca as (string | Buffer)[];
|
||||
|
||||
expect(new Set(injected)).toEqual(new Set(injectedByWinCA));
|
||||
});
|
||||
|
||||
it("shouldn't included the expired DST Root CA X3 on Windows", async () => {
|
||||
const winCAs = await getWinRootCA();
|
||||
|
||||
const wincaAPI = await import("win-ca/api");
|
||||
|
||||
wincaAPI.inject("true", winCAs);
|
||||
const injected = https.globalAgent.options.ca as (string | Buffer)[];
|
||||
|
||||
expect(injected.includes(DSTRootCAX3)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -23,8 +23,6 @@ jest.mock("electron", () => ({
|
||||
|
||||
import type { UserStore } from "../user-store";
|
||||
import { Console } from "console";
|
||||
import { SemVer } from "semver";
|
||||
import electron from "electron";
|
||||
import { stdout, stderr } from "process";
|
||||
import userStoreInjectable from "../user-store/user-store.injectable";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
@@ -34,7 +32,9 @@ import { defaultThemeId } from "../vars";
|
||||
import writeFileInjectable from "../fs/write-file.injectable";
|
||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable from "../vars/app-version.injectable";
|
||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||
import releaseChannelInjectable from "../vars/release-channel.injectable";
|
||||
import defaultUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("user store tests", () => {
|
||||
let userStore: UserStore;
|
||||
let di: DiContainer;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
mockFs();
|
||||
@@ -52,6 +52,12 @@ describe("user store tests", () => {
|
||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||
di.permitSideEffects(userStoreInjectable);
|
||||
|
||||
di.override(releaseChannelInjectable, () => ({
|
||||
get: () => "latest" as const,
|
||||
init: async () => {},
|
||||
}));
|
||||
await di.inject(defaultUpdateChannelInjectable).init();
|
||||
|
||||
di.unoverride(userStoreInjectable);
|
||||
});
|
||||
|
||||
@@ -64,6 +70,7 @@ describe("user store tests", () => {
|
||||
mockFs({ "some-directory-for-user-data": { "config.json": "{}", "kube_config": "{}" }});
|
||||
|
||||
userStore = di.inject(userStoreInjectable);
|
||||
userStore.load();
|
||||
});
|
||||
|
||||
it("allows setting and retrieving lastSeenAppVersion", () => {
|
||||
@@ -86,13 +93,6 @@ describe("user store tests", () => {
|
||||
userStore.resetTheme();
|
||||
expect(userStore.colorTheme).toBe(defaultThemeId);
|
||||
});
|
||||
|
||||
it("correctly calculates if the last seen version is an old release", () => {
|
||||
expect(userStore.isNewVersion).toBe(true);
|
||||
|
||||
userStore.lastSeenAppVersion = (new SemVer(electron.app.getVersion())).inc("major").format();
|
||||
expect(userStore.isNewVersion).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("migrations", () => {
|
||||
@@ -125,9 +125,10 @@ describe("user store tests", () => {
|
||||
},
|
||||
});
|
||||
|
||||
di.override(appVersionInjectable, () => "10.0.0");
|
||||
di.override(storeMigrationVersionInjectable, () => "10.0.0");
|
||||
|
||||
userStore = di.inject(userStoreInjectable);
|
||||
userStore.load();
|
||||
});
|
||||
|
||||
it("sets last seen app version to 0.0.0", () => {
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { appEventBus } from "./event-bus";
|
||||
import { EventEmitter } from "../event-emitter";
|
||||
import type { AppEvent } from "./event-bus";
|
||||
|
||||
const appEventBusInjectable = getInjectable({
|
||||
id: "app-event-bus",
|
||||
instantiate: () => appEventBus,
|
||||
causesSideEffects: true,
|
||||
instantiate: () => new EventEmitter<[AppEvent]>,
|
||||
decorable: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -4,11 +4,18 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import appEventBusInjectable from "./app-event-bus.injectable";
|
||||
import type { AppEvent } from "./event-bus";
|
||||
|
||||
const emitEventInjectable = getInjectable({
|
||||
id: "emit-event",
|
||||
instantiate: (di) => di.inject(appEventBusInjectable).emit,
|
||||
export type EmitAppEvent = (event: AppEvent) => void;
|
||||
|
||||
const emitAppEventInjectable = getInjectable({
|
||||
id: "emit-app-event",
|
||||
instantiate: (di): EmitAppEvent => {
|
||||
const bus = di.inject(appEventBusInjectable);
|
||||
|
||||
return (event) => bus.emit(event);
|
||||
},
|
||||
decorable: false,
|
||||
});
|
||||
|
||||
export default emitEventInjectable;
|
||||
export default emitAppEventInjectable;
|
||||
@@ -3,13 +3,12 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from "../event-emitter";
|
||||
|
||||
/**
|
||||
* Data for telemetry
|
||||
*/
|
||||
export interface AppEvent {
|
||||
name: string;
|
||||
action: string;
|
||||
destination?: string;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
export const appEventBus = new EventEmitter<[AppEvent]>();
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* 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 { AppPaths } from "./app-path-injection-token";
|
||||
import type { RequestChannel } from "../utils/channel/request-channel-injection-token";
|
||||
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
|
||||
|
||||
export type AppPathsChannel = RequestChannel<void, AppPaths>;
|
||||
|
||||
const appPathsChannelInjectable = getInjectable({
|
||||
id: "app-paths-channel",
|
||||
|
||||
instantiate: (): AppPathsChannel => ({
|
||||
id: "app-paths",
|
||||
}),
|
||||
|
||||
injectionToken: messageChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default appPathsChannelInjectable;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { AppPaths } from "./app-path-injection-token";
|
||||
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
|
||||
|
||||
export type AppPathsChannel = RequestChannel<void, AppPaths>;
|
||||
|
||||
export const appPathsChannel: AppPathsChannel = {
|
||||
id: "app-paths",
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@ import { appPathsInjectionToken } from "./app-path-injection-token";
|
||||
import getElectronAppPathInjectable from "../../main/app-paths/get-electron-app-path/get-electron-app-path.injectable";
|
||||
import type { PathName } from "./app-path-names";
|
||||
import setElectronAppPathInjectable from "../../main/app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
||||
import appNameInjectable from "../../main/app-paths/app-name/app-name.injectable";
|
||||
import directoryForIntegrationTestingInjectable from "../../main/app-paths/directory-for-integration-testing/directory-for-integration-testing.injectable";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
@@ -53,8 +52,6 @@ describe("app-paths", () => {
|
||||
defaultAppPathsStub[key] = path;
|
||||
},
|
||||
);
|
||||
|
||||
mainDi.override(appNameInjectable, () => "some-app-name");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,7 +85,7 @@ describe("app-paths", () => {
|
||||
recent: "some-recent",
|
||||
temp: "some-temp",
|
||||
videos: "some-videos",
|
||||
userData: "some-app-data/some-app-name",
|
||||
userData: "some-app-data/some-product-name",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -111,7 +108,7 @@ describe("app-paths", () => {
|
||||
recent: "some-recent",
|
||||
temp: "some-temp",
|
||||
videos: "some-videos",
|
||||
userData: "some-app-data/some-app-name",
|
||||
userData: "some-app-data/some-product-name",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -137,7 +134,7 @@ describe("app-paths", () => {
|
||||
|
||||
expect({ appData, userData }).toEqual({
|
||||
appData: "some-integration-testing-app-data",
|
||||
userData: `some-integration-testing-app-data/some-app-name`,
|
||||
userData: `some-integration-testing-app-data/some-product-name`,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -146,7 +143,7 @@ describe("app-paths", () => {
|
||||
|
||||
expect({ appData, userData }).toEqual({
|
||||
appData: "some-integration-testing-app-data",
|
||||
userData: "some-integration-testing-app-data/some-app-name",
|
||||
userData: "some-integration-testing-app-data/some-product-name",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
||||
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||
|
||||
const directoryForBinariesInjectable = getInjectable({
|
||||
id: "directory-for-binaries",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||
|
||||
return getAbsolutePath(directoryForUserData, "binaries");
|
||||
return joinPaths(directoryForUserData, "binaries");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+3
-6
@@ -4,19 +4,16 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
||||
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||
|
||||
const directoryForKubeConfigsInjectable = getInjectable({
|
||||
id: "directory-for-kube-configs",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||
|
||||
return getAbsolutePath(
|
||||
directoryForUserData,
|
||||
"kubeconfigs",
|
||||
);
|
||||
return joinPaths(directoryForUserData, "kubeconfigs");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+3
-4
@@ -4,17 +4,16 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForBinariesInjectable from "../directory-for-binaries/directory-for-binaries.injectable";
|
||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
||||
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||
|
||||
const directoryForKubectlBinariesInjectable = getInjectable({
|
||||
id: "directory-for-kubectl-binaries",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const directoryForBinaries = di.inject(directoryForBinariesInjectable);
|
||||
|
||||
|
||||
return getAbsolutePath(directoryForBinaries, "kubectl");
|
||||
return joinPaths(directoryForBinaries, "kubectl");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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 appPathsInjectable from "./app-paths.injectable";
|
||||
|
||||
const directoryForLogsInjectable = getInjectable({
|
||||
id: "directory-for-logs",
|
||||
instantiate: (di) => di.inject(appPathsInjectable).logs,
|
||||
});
|
||||
|
||||
export default directoryForLogsInjectable;
|
||||
+3
-4
@@ -4,17 +4,16 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForKubeConfigsInjectable from "../directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
||||
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||
|
||||
const getCustomKubeConfigDirectoryInjectable = getInjectable({
|
||||
id: "get-custom-kube-config-directory",
|
||||
|
||||
instantiate: (di) => {
|
||||
const directoryForKubeConfigs = di.inject(directoryForKubeConfigsInjectable);
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
return (directoryName: string) =>
|
||||
getAbsolutePath(directoryForKubeConfigs, directoryName);
|
||||
return (directoryName: string) => joinPaths(directoryForKubeConfigs, directoryName);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* 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 { MessageChannel } from "../../utils/channel/message-channel-injection-token";
|
||||
import { messageChannelInjectionToken } from "../../utils/channel/message-channel-injection-token";
|
||||
|
||||
export type RestartAndInstallUpdateChannel = MessageChannel;
|
||||
|
||||
const restartAndInstallUpdateChannel = getInjectable({
|
||||
id: "restart-and-install-update-channel",
|
||||
|
||||
instantiate: (): RestartAndInstallUpdateChannel => ({
|
||||
id: "restart-and-install-update-channel",
|
||||
}),
|
||||
|
||||
injectionToken: messageChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default restartAndInstallUpdateChannel;
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 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 appSemanticVersionInjectable from "../../vars/app-semantic-version.injectable";
|
||||
import type { UpdateChannelId } from "../update-channels";
|
||||
import { updateChannels } from "../update-channels";
|
||||
|
||||
const defaultUpdateChannelInjectable = getInjectable({
|
||||
id: "default-update-channel",
|
||||
|
||||
instantiate: (di) => {
|
||||
const appSemanticVersion = di.inject(appSemanticVersionInjectable);
|
||||
const currentReleaseChannel = appSemanticVersion.prerelease[0]?.toString();
|
||||
|
||||
if (currentReleaseChannel in updateChannels) {
|
||||
return updateChannels[currentReleaseChannel as UpdateChannelId];
|
||||
}
|
||||
|
||||
return updateChannels.latest;
|
||||
},
|
||||
});
|
||||
|
||||
export default defaultUpdateChannelInjectable;
|
||||
@@ -19,7 +19,7 @@ import { kebabCase } from "lodash";
|
||||
import { getLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import directoryForUserDataInjectable from "./app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import getConfigurationFileModelInjectable from "./get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import appVersionInjectable from "./vars/app-version.injectable";
|
||||
import storeMigrationVersionInjectable from "./vars/store-migration-version.injectable";
|
||||
|
||||
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
||||
syncOptions?: {
|
||||
@@ -60,7 +60,7 @@ export abstract class BaseStore<T extends object> extends Singleton {
|
||||
|
||||
this.storeConfig = getConfigurationFileModel({
|
||||
projectName: "lens",
|
||||
projectVersion: di.inject(appVersionInjectable),
|
||||
projectVersion: di.inject(storeMigrationVersionInjectable),
|
||||
cwd: this.cwd(),
|
||||
...this.params,
|
||||
});
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
||||
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity";
|
||||
import { productName } from "../vars";
|
||||
import productNameInjectable from "../vars/product-name.injectable";
|
||||
import { WeblinkStore } from "../weblink-store";
|
||||
|
||||
export type WebLinkStatusPhase = "available" | "unavailable";
|
||||
@@ -30,6 +31,9 @@ export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus,
|
||||
}
|
||||
|
||||
onContextMenuOpen(context: CatalogEntityContextMenuContext) {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
const productName = di.inject(productNameInjectable);
|
||||
|
||||
if (this.metadata.source === "local") {
|
||||
context.menuItems.push({
|
||||
title: "Delete",
|
||||
|
||||
@@ -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 catalogCategoryRegistryInjectable from "./category-registry.injectable";
|
||||
|
||||
const filteredCategoriesInjectable = getInjectable({
|
||||
id: "filtered-categories",
|
||||
instantiate: (di) => {
|
||||
const registry = di.inject(catalogCategoryRegistryInjectable);
|
||||
|
||||
return computed(() => [...registry.filteredItems]);
|
||||
},
|
||||
});
|
||||
|
||||
export default filteredCategoriesInjectable;
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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 { globalAgent } from "https";
|
||||
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
|
||||
|
||||
// DST Root CA X3, which was expired on 9.30.2021
|
||||
const DSTRootCAX3 = "-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n";
|
||||
|
||||
function isCertActive(cert: string) {
|
||||
const isExpired = typeof cert !== "string" || cert.includes(DSTRootCAX3);
|
||||
|
||||
return !isExpired;
|
||||
}
|
||||
|
||||
const injectSystemCAsInjectable = getInjectable({
|
||||
id: "inject-system-cas",
|
||||
instantiate: (di) => {
|
||||
const requestSystemCAs = di.inject(requestSystemCAsInjectionToken);
|
||||
|
||||
return async () => {
|
||||
const certs = await requestSystemCAs();
|
||||
|
||||
if (certs.length === 0) {
|
||||
// Leave the global option alone
|
||||
return;
|
||||
}
|
||||
|
||||
const cas = (() => {
|
||||
if (Array.isArray(globalAgent.options.ca)) {
|
||||
return globalAgent.options.ca;
|
||||
}
|
||||
|
||||
if (globalAgent.options.ca) {
|
||||
return [globalAgent.options.ca];
|
||||
}
|
||||
|
||||
return [];
|
||||
})();
|
||||
|
||||
for (const cert of certs) {
|
||||
if (!isCertActive(cert)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cas.includes(cert)) {
|
||||
cas.push(cert);
|
||||
}
|
||||
}
|
||||
|
||||
globalAgent.options.ca = cas;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default injectSystemCAsInjectable;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
|
||||
export const requestSystemCAsInjectionToken = getInjectionToken<() => Promise<string[]>>({
|
||||
id: "request-system-cas-token",
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 execFileInjectable from "../fs/exec-file.injectable";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import type { AsyncResult } from "../utils/async-result";
|
||||
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet#other_assertions
|
||||
const certSplitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g;
|
||||
|
||||
const requestSystemCAsInjectable = getInjectable({
|
||||
id: "request-system-cas",
|
||||
instantiate: (di) => {
|
||||
const execFile = di.inject(execFileInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
const execSecurity = async (...args: string[]): Promise<AsyncResult<string[]>> => {
|
||||
const result = await execFile("/usr/bin/security", args);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: result.error.stderr || result.error.message,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
response: result.response.split(certSplitPattern),
|
||||
};
|
||||
};
|
||||
|
||||
return async () => {
|
||||
const [trustedResult, rootCAResult] = await Promise.all([
|
||||
execSecurity("find-certificate", "-a", "-p"),
|
||||
execSecurity("find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain"),
|
||||
]);
|
||||
|
||||
if (!trustedResult.callWasSuccessful) {
|
||||
logger.warn(`[INJECT-CAS]: Error retreiving trusted CAs: ${trustedResult.error}`);
|
||||
} else if (!rootCAResult.callWasSuccessful) {
|
||||
logger.warn(`[INJECT-CAS]: Error retreiving root CAs: ${rootCAResult.error}`);
|
||||
} else {
|
||||
return [...new Set([...trustedResult.response, ...rootCAResult.response])];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
},
|
||||
causesSideEffects: true,
|
||||
injectionToken: requestSystemCAsInjectionToken,
|
||||
});
|
||||
|
||||
export default requestSystemCAsInjectable;
|
||||
@@ -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 { requestSystemCAsInjectionToken } from "./request-system-cas-token";
|
||||
|
||||
const requestSystemCAsInjectable = getInjectable({
|
||||
id: "request-system-cas",
|
||||
instantiate: () => async () => [],
|
||||
injectionToken: requestSystemCAsInjectionToken,
|
||||
});
|
||||
|
||||
export default requestSystemCAsInjectable;
|
||||
@@ -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 { requestSystemCAsInjectionToken } from "./request-system-cas-token";
|
||||
|
||||
const requestSystemCAsInjectable = getInjectable({
|
||||
id: "request-system-cas",
|
||||
instantiate: () => async () => [],
|
||||
injectionToken: requestSystemCAsInjectionToken,
|
||||
});
|
||||
|
||||
export default requestSystemCAsInjectable;
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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 execFileInjectable from "../fs/exec-file.injectable";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
|
||||
|
||||
const pemEncoding = (hexEncodedCert: String) => {
|
||||
const certData = Buffer.from(hexEncodedCert, "hex").toString("base64");
|
||||
const lines = ["-----BEGIN CERTIFICATE-----"];
|
||||
|
||||
for (let i = 0; i < certData.length; i += 64) {
|
||||
lines.push(certData.substring(i, i + 64));
|
||||
}
|
||||
|
||||
lines.push("-----END CERTIFICATE-----", "");
|
||||
|
||||
return lines.join("\r\n");
|
||||
};
|
||||
|
||||
const requestSystemCAsInjectable = getInjectable({
|
||||
id: "request-system-cas",
|
||||
instantiate: (di) => {
|
||||
const wincaRootsExePath: string = __non_webpack_require__.resolve("win-ca/lib/roots.exe");
|
||||
const execFile = di.inject(execFileInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async () => {
|
||||
/**
|
||||
* This needs to be done manually because for some reason calling the api from "win-ca"
|
||||
* directly fails to load "child_process" correctly on renderer
|
||||
*/
|
||||
const result = await execFile(wincaRootsExePath, {
|
||||
maxBuffer: 128 * 1024 * 1024, // 128 MiB
|
||||
});
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
logger.warn(`[INJECT-CAS]: Error retreiving CAs`, result.error);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return result
|
||||
.response
|
||||
.split("\r\n")
|
||||
.filter(Boolean)
|
||||
.map(pemEncoding);
|
||||
};
|
||||
},
|
||||
causesSideEffects: true,
|
||||
injectionToken: requestSystemCAsInjectionToken,
|
||||
});
|
||||
|
||||
export default requestSystemCAsInjectable;
|
||||
@@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { ClusterStore } from "./cluster-store";
|
||||
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
||||
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
|
||||
import emitAppEventInjectable from "../app-event-bus/emit-event.injectable";
|
||||
|
||||
const clusterStoreInjectable = getInjectable({
|
||||
id: "cluster-store",
|
||||
@@ -16,6 +17,7 @@ const clusterStoreInjectable = getInjectable({
|
||||
return ClusterStore.createInstance({
|
||||
createCluster: di.inject(createClusterInjectionToken),
|
||||
readClusterConfigSync: di.inject(readClusterConfigSyncInjectable),
|
||||
emitAppEvent: di.inject(emitAppEventInjectable),
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import { BaseStore } from "../base-store";
|
||||
import { Cluster } from "../cluster/cluster";
|
||||
import migrations from "../../migrations/cluster-store";
|
||||
import logger from "../../main/logger";
|
||||
import { appEventBus } from "../app-event-bus/event-bus";
|
||||
import { ipcMainHandle } from "../ipc";
|
||||
import { disposer, toJS } from "../utils";
|
||||
import type { ClusterModel, ClusterId, ClusterState } from "../cluster-types";
|
||||
@@ -18,6 +17,7 @@ import { requestInitialClusterStates } from "../../renderer/ipc";
|
||||
import { clusterStates } from "../ipc/cluster";
|
||||
import type { CreateCluster } from "../cluster/create-cluster-injection-token";
|
||||
import type { ReadClusterConfigSync } from "./read-cluster-config.injectable";
|
||||
import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable";
|
||||
|
||||
export interface ClusterStoreModel {
|
||||
clusters?: ClusterModel[];
|
||||
@@ -26,6 +26,7 @@ export interface ClusterStoreModel {
|
||||
interface Dependencies {
|
||||
createCluster: CreateCluster;
|
||||
readClusterConfigSync: ReadClusterConfigSync;
|
||||
emitAppEvent: EmitAppEvent;
|
||||
}
|
||||
|
||||
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
@@ -34,7 +35,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
|
||||
protected disposer = disposer();
|
||||
|
||||
constructor(private dependencies: Dependencies) {
|
||||
constructor(private readonly dependencies: Dependencies) {
|
||||
super({
|
||||
configName: "lens-cluster-store",
|
||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||
@@ -115,7 +116,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
}
|
||||
|
||||
addCluster(clusterOrModel: ClusterModel | Cluster): Cluster {
|
||||
appEventBus.emit({ name: "cluster", action: "add" });
|
||||
this.dependencies.emitAppEvent({ name: "cluster", action: "add" });
|
||||
|
||||
const cluster = clusterOrModel instanceof Cluster
|
||||
? clusterOrModel
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 { ClusterId } from "../cluster-types";
|
||||
import type { Cluster } from "../cluster/cluster";
|
||||
import clusterStoreInjectable from "./cluster-store.injectable";
|
||||
|
||||
export type GetClusterById = (id: ClusterId) => Cluster | undefined;
|
||||
|
||||
const getClusterByIdInjectable = getInjectable({
|
||||
id: "get-cluster-by-id",
|
||||
instantiate: (di): GetClusterById => {
|
||||
const store = di.inject(clusterStoreInjectable);
|
||||
|
||||
return (id) => store.getById(id);
|
||||
},
|
||||
});
|
||||
|
||||
export default getClusterByIdInjectable;
|
||||
@@ -3,6 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import Joi from "joi";
|
||||
|
||||
/**
|
||||
* JSON serializable metadata type
|
||||
*/
|
||||
@@ -27,6 +29,37 @@ export type ClusterId = string;
|
||||
*/
|
||||
export type UpdateClusterModel = Omit<ClusterModel, "id">;
|
||||
|
||||
/**
|
||||
* A type validator for `UpdateClusterModel` so that only expected types are present
|
||||
*/
|
||||
export const updateClusterModelChecker = Joi.object<UpdateClusterModel>({
|
||||
kubeConfigPath: Joi.string()
|
||||
.required()
|
||||
.min(1),
|
||||
contextName: Joi.string()
|
||||
.required()
|
||||
.min(1),
|
||||
workspace: Joi.string()
|
||||
.optional(),
|
||||
workspaces: Joi.array()
|
||||
.items(Joi.string()),
|
||||
preferences: Joi.object(),
|
||||
metadata: Joi.object(),
|
||||
accessibleNamespaces: Joi.array()
|
||||
.items(Joi.string()),
|
||||
labels: Joi.object().pattern(Joi.string(), Joi.string()),
|
||||
});
|
||||
|
||||
/**
|
||||
* A type validator for just the `id` fields of `ClusterModel`. The rest is
|
||||
* covered by `updateClusterModelChecker`
|
||||
*/
|
||||
export const clusterModelIdChecker = Joi.object<Pick<ClusterModel, "id">>({
|
||||
id: Joi.string()
|
||||
.required()
|
||||
.min(1),
|
||||
});
|
||||
|
||||
/**
|
||||
* The model for passing cluster data around, including to disk
|
||||
*/
|
||||
@@ -162,13 +195,6 @@ export enum ClusterMetricsResourceType {
|
||||
*/
|
||||
export const initialNodeShellImage = "docker.io/alpine:3.13";
|
||||
|
||||
/**
|
||||
* The arguments for requesting to refresh a cluster's metadata
|
||||
*/
|
||||
export interface ClusterRefreshOptions {
|
||||
refreshMetadata?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The data representing a cluster's state, for passing between main and renderer
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Logger } from "../logger";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import type { KubeApiResource } from "../rbac";
|
||||
|
||||
/**
|
||||
* Requests the permissions for actions on the kube cluster
|
||||
* @param namespace The namespace of the resources
|
||||
* @param availableResources List of available resources in the cluster to resolve glob values fir api groups
|
||||
* @returns list of allowed resources names
|
||||
*/
|
||||
export type RequestNamespaceResources = (namespace: string, availableResources: KubeApiResource[]) => Promise<string[]>;
|
||||
|
||||
/**
|
||||
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
||||
*/
|
||||
export type AuthorizationNamespaceReview = (proxyConfig: KubeConfig) => RequestNamespaceResources;
|
||||
|
||||
interface Dependencies {
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
const authorizationNamespaceReview = ({ logger }: Dependencies): AuthorizationNamespaceReview => {
|
||||
return (proxyConfig) => {
|
||||
|
||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
||||
|
||||
return async (namespace, availableResources) => {
|
||||
try {
|
||||
const { body } = await api.createSelfSubjectRulesReview({
|
||||
apiVersion: "authorization.k8s.io/v1",
|
||||
kind: "SelfSubjectRulesReview",
|
||||
spec: { namespace },
|
||||
});
|
||||
|
||||
const resources = new Set<string>();
|
||||
|
||||
body.status?.resourceRules.forEach(resourceRule => {
|
||||
if (!resourceRule.verbs.some(verb => ["*", "list"].includes(verb)) || !resourceRule.resources) {
|
||||
return;
|
||||
}
|
||||
|
||||
const apiGroups = resourceRule.apiGroups;
|
||||
|
||||
if (resourceRule.resources.length === 1 && resourceRule.resources[0] === "*" && apiGroups) {
|
||||
if (apiGroups[0] === "*") {
|
||||
availableResources.forEach(resource => resources.add(resource.apiName));
|
||||
} else {
|
||||
availableResources.forEach((apiResource)=> {
|
||||
if (apiGroups.includes(apiResource.group || "")) {
|
||||
resources.add(apiResource.apiName);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
resourceRule.resources.forEach(resource => resources.add(resource));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return [...resources];
|
||||
} catch (error) {
|
||||
logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review: ${error}`, { namespace });
|
||||
|
||||
return [];
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const authorizationNamespaceReviewInjectable = getInjectable({
|
||||
id: "authorization-namespace-review",
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return authorizationNamespaceReview({ logger });
|
||||
},
|
||||
});
|
||||
|
||||
export default authorizationNamespaceReviewInjectable;
|
||||
@@ -5,42 +5,55 @@
|
||||
|
||||
import type { KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
||||
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||
import logger from "../logger";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Logger } from "../logger";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
|
||||
/**
|
||||
* Requests the permissions for actions on the kube cluster
|
||||
* @param resourceAttributes The descriptor of the action that is desired to be known if it is allowed
|
||||
* @returns `true` if the actions described are allowed
|
||||
*/
|
||||
export type CanI = (resourceAttributes: V1ResourceAttributes) => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
||||
*/
|
||||
export function authorizationReview(proxyConfig: KubeConfig): CanI {
|
||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
||||
*/
|
||||
export type AuthorizationReview = (proxyConfig: KubeConfig) => CanI;
|
||||
|
||||
/**
|
||||
* Requests the permissions for actions on the kube cluster
|
||||
* @param resourceAttributes The descriptor of the action that is desired to be known if it is allowed
|
||||
* @returns `true` if the actions described are allowed
|
||||
*/
|
||||
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
||||
try {
|
||||
const { body } = await api.createSelfSubjectAccessReview({
|
||||
apiVersion: "authorization.k8s.io/v1",
|
||||
kind: "SelfSubjectAccessReview",
|
||||
spec: { resourceAttributes },
|
||||
});
|
||||
|
||||
return body.status?.allowed ?? false;
|
||||
} catch (error) {
|
||||
logger.error(`[AUTHORIZATION-REVIEW]: failed to create access review: ${error}`, { resourceAttributes });
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
interface Dependencies {
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
const authorizationReview = ({ logger }: Dependencies): AuthorizationReview => {
|
||||
return (proxyConfig) => {
|
||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
||||
|
||||
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
||||
try {
|
||||
const { body } = await api.createSelfSubjectAccessReview({
|
||||
apiVersion: "authorization.k8s.io/v1",
|
||||
kind: "SelfSubjectAccessReview",
|
||||
spec: { resourceAttributes },
|
||||
});
|
||||
|
||||
return body.status?.allowed ?? false;
|
||||
} catch (error) {
|
||||
logger.error(`[AUTHORIZATION-REVIEW]: failed to create access review: ${error}`, { resourceAttributes });
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const authorizationReviewInjectable = getInjectable({
|
||||
id: "authorization-review",
|
||||
instantiate: () => authorizationReview,
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return authorizationReview({ logger });
|
||||
},
|
||||
});
|
||||
|
||||
export default authorizationReviewInjectable;
|
||||
+100
-64
@@ -9,14 +9,13 @@ import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import { HttpError } from "@kubernetes/client-node";
|
||||
import type { Kubectl } from "../../main/kubectl/kubectl";
|
||||
import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager";
|
||||
import { loadConfigFromFile } from "../kube-helpers";
|
||||
import type { KubeApiResource, KubeResource } from "../rbac";
|
||||
import { apiResourceRecord, apiResources } from "../rbac";
|
||||
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
|
||||
import type { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
|
||||
import plimit from "p-limit";
|
||||
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate, ClusterConfigData } from "../cluster-types";
|
||||
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../cluster-types";
|
||||
import type { ClusterState, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate, ClusterConfigData } from "../cluster-types";
|
||||
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus, clusterModelIdChecker, updateClusterModelChecker } from "../cluster-types";
|
||||
import { disposer, isDefined, isRequestError, toJS } from "../utils";
|
||||
import type { Response } from "request";
|
||||
import { clusterListNamespaceForbiddenChannel } from "../ipc/cluster";
|
||||
@@ -25,6 +24,9 @@ import type { ListNamespaces } from "./list-namespaces.injectable";
|
||||
import assert from "assert";
|
||||
import type { Logger } from "../logger";
|
||||
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
|
||||
import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable";
|
||||
import type { RequestNamespaceResources } from "./authorization-namespace-review.injectable";
|
||||
import type { RequestListApiResources } from "./list-api-resources.injectable";
|
||||
|
||||
export interface ClusterDependencies {
|
||||
readonly directoryForKubeConfigs: string;
|
||||
@@ -34,9 +36,12 @@ export interface ClusterDependencies {
|
||||
createContextHandler: (cluster: Cluster) => ClusterContextHandler;
|
||||
createKubectl: (clusterVersion: string) => Kubectl;
|
||||
createAuthorizationReview: (config: KubeConfig) => CanI;
|
||||
createAuthorizationNamespaceReview: (config: KubeConfig) => RequestNamespaceResources;
|
||||
createListApiResources: (cluster: Cluster) => RequestListApiResources;
|
||||
createListNamespaces: (config: KubeConfig) => ListNamespaces;
|
||||
createVersionDetector: (cluster: Cluster) => VersionDetector;
|
||||
broadcastMessage: BroadcastMessage;
|
||||
loadConfigfromFile: LoadConfigfromFile;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,9 +242,16 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
return this.preferences.defaultNamespace;
|
||||
}
|
||||
|
||||
constructor(private readonly dependencies: ClusterDependencies, model: ClusterModel, configData: ClusterConfigData) {
|
||||
constructor(private readonly dependencies: ClusterDependencies, { id, ...model }: ClusterModel, configData: ClusterConfigData) {
|
||||
makeObservable(this);
|
||||
this.id = model.id;
|
||||
|
||||
const { error } = clusterModelIdChecker.validate({ id });
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.id = id;
|
||||
this.updateModel(model);
|
||||
this.apiUrl = configData.clusterServerUrl;
|
||||
|
||||
@@ -261,6 +273,12 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
@action updateModel(model: UpdateClusterModel) {
|
||||
// Note: do not assign ID as that should never be updated
|
||||
|
||||
const { error } = updateClusterModelChecker.validate(model, { allowUnknown: true });
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.kubeConfigPath = model.kubeConfigPath;
|
||||
this.contextName = model.contextName;
|
||||
|
||||
@@ -295,7 +313,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
protected bindEvents() {
|
||||
this.dependencies.logger.info(`[CLUSTER]: bind events`, this.getMeta());
|
||||
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
||||
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000); // every 15 minutes
|
||||
const refreshMetadataTimer = setInterval(() => this.available && this.refreshAccessibilityAndMetadata(), 900000); // every 15 minutes
|
||||
|
||||
this.eventsDisposer.push(
|
||||
reaction(() => this.getState(), state => this.pushState(state)),
|
||||
@@ -425,69 +443,71 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param opts refresh options
|
||||
*/
|
||||
@action
|
||||
async refresh(opts: ClusterRefreshOptions = {}) {
|
||||
async refresh() {
|
||||
this.dependencies.logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
||||
await this.refreshConnectionStatus();
|
||||
|
||||
if (this.accessible) {
|
||||
await this.refreshAccessibility();
|
||||
|
||||
if (opts.refreshMetadata) {
|
||||
this.refreshMetadata();
|
||||
}
|
||||
}
|
||||
this.pushState();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@action
|
||||
async refreshMetadata() {
|
||||
this.dependencies.logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
|
||||
const metadata = await this.dependencies.detectorRegistry.detectForCluster(this);
|
||||
const existingMetadata = this.metadata;
|
||||
|
||||
this.metadata = Object.assign(existingMetadata, metadata);
|
||||
@action
|
||||
async refreshAccessibilityAndMetadata() {
|
||||
await this.refreshAccessibility();
|
||||
await this.refreshMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
private async refreshAccessibility(): Promise<void> {
|
||||
const proxyConfig = await this.getProxyKubeconfig();
|
||||
const canI = this.dependencies.createAuthorizationReview(proxyConfig);
|
||||
async refreshMetadata() {
|
||||
this.dependencies.logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
|
||||
const metadata = await this.dependencies.detectorRegistry.detectForCluster(this);
|
||||
const existingMetadata = this.metadata;
|
||||
|
||||
this.isAdmin = await canI({
|
||||
namespace: "kube-system",
|
||||
resource: "*",
|
||||
verb: "create",
|
||||
});
|
||||
this.isGlobalWatchEnabled = await canI({
|
||||
verb: "watch",
|
||||
resource: "*",
|
||||
});
|
||||
this.allowedNamespaces = await this.getAllowedNamespaces(proxyConfig);
|
||||
this.allowedResources = await this.getAllowedResources(canI);
|
||||
this.ready = true;
|
||||
}
|
||||
this.metadata = Object.assign(existingMetadata, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
private async refreshAccessibility(): Promise<void> {
|
||||
this.dependencies.logger.info(`[CLUSTER]: refreshAccessibility`, this.getMeta());
|
||||
const proxyConfig = await this.getProxyKubeconfig();
|
||||
const canI = this.dependencies.createAuthorizationReview(proxyConfig);
|
||||
const requestNamespaceResources = this.dependencies.createAuthorizationNamespaceReview(proxyConfig);
|
||||
const listApiResources = this.dependencies.createListApiResources(this);
|
||||
|
||||
this.isAdmin = await canI({
|
||||
namespace: "kube-system",
|
||||
resource: "*",
|
||||
verb: "create",
|
||||
});
|
||||
this.isGlobalWatchEnabled = await canI({
|
||||
verb: "watch",
|
||||
resource: "*",
|
||||
});
|
||||
this.allowedNamespaces = await this.getAllowedNamespaces(proxyConfig);
|
||||
this.allowedResources = await this.getAllowedResources(listApiResources, requestNamespaceResources);
|
||||
this.ready = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@action
|
||||
async refreshConnectionStatus() {
|
||||
const connectionStatus = await this.getConnectionStatus();
|
||||
async refreshConnectionStatus() {
|
||||
const connectionStatus = await this.getConnectionStatus();
|
||||
|
||||
this.online = connectionStatus > ClusterStatus.Offline;
|
||||
this.accessible = connectionStatus == ClusterStatus.AccessGranted;
|
||||
}
|
||||
this.online = connectionStatus > ClusterStatus.Offline;
|
||||
this.accessible = connectionStatus == ClusterStatus.AccessGranted;
|
||||
}
|
||||
|
||||
async getKubeconfig(): Promise<KubeConfig> {
|
||||
const { config } = await loadConfigFromFile(this.kubeConfigPath);
|
||||
const { config } = await this.dependencies.loadConfigfromFile(this.kubeConfigPath);
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -497,7 +517,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
*/
|
||||
async getProxyKubeconfig(): Promise<KubeConfig> {
|
||||
const proxyKCPath = await this.getProxyKubeconfigPath();
|
||||
const { config } = await loadConfigFromFile(proxyKCPath);
|
||||
const { config } = await this.dependencies.loadConfigfromFile(proxyKCPath);
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -653,32 +673,48 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
}
|
||||
}
|
||||
|
||||
protected async getAllowedResources(canI: CanI) {
|
||||
protected async getAllowedResources(listApiResources:RequestListApiResources, requestNamespaceResources: RequestNamespaceResources) {
|
||||
try {
|
||||
if (!this.allowedNamespaces.length) {
|
||||
return [];
|
||||
}
|
||||
const resources = apiResources.filter((resource) => this.resourceAccessStatuses.get(resource) === undefined);
|
||||
const apiLimit = plimit(5); // 5 concurrent api requests
|
||||
const requests = [];
|
||||
|
||||
for (const apiResource of resources) {
|
||||
requests.push(apiLimit(async () => {
|
||||
for (const namespace of this.allowedNamespaces.slice(0, 10)) {
|
||||
if (!this.resourceAccessStatuses.get(apiResource)) {
|
||||
const result = await canI({
|
||||
resource: apiResource.apiName,
|
||||
group: apiResource.group,
|
||||
verb: "list",
|
||||
namespace,
|
||||
});
|
||||
const unknownResources = new Map<string, KubeApiResource>(apiResources.map(resource => ([resource.apiName, resource])));
|
||||
|
||||
this.resourceAccessStatuses.set(apiResource, result);
|
||||
const availableResources = await listApiResources();
|
||||
const availableResourcesNames = new Set(availableResources.map(apiResource => apiResource.apiName));
|
||||
|
||||
[...unknownResources.values()].map(unknownResource => {
|
||||
if (!availableResourcesNames.has(unknownResource.apiName)) {
|
||||
this.resourceAccessStatuses.set(unknownResource, false);
|
||||
unknownResources.delete(unknownResource.apiName);
|
||||
}
|
||||
});
|
||||
|
||||
if (unknownResources.size > 0) {
|
||||
const apiLimit = plimit(5); // 5 concurrent api requests
|
||||
|
||||
await Promise.all(this.allowedNamespaces.map(namespace => apiLimit(async () => {
|
||||
if (unknownResources.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const namespaceResources = await requestNamespaceResources(namespace, availableResources);
|
||||
|
||||
for (const resourceName of namespaceResources) {
|
||||
const unknownResource = unknownResources.get(resourceName);
|
||||
|
||||
if (unknownResource) {
|
||||
this.resourceAccessStatuses.set(unknownResource, true);
|
||||
unknownResources.delete(resourceName);
|
||||
}
|
||||
}
|
||||
}));
|
||||
})));
|
||||
|
||||
for (const forbiddenResource of unknownResources.values()) {
|
||||
this.resourceAccessStatuses.set(forbiddenResource, false);
|
||||
}
|
||||
}
|
||||
await Promise.all(requests);
|
||||
|
||||
return apiResources
|
||||
.filter((resource) => this.resourceAccessStatuses.get(resource))
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type {
|
||||
V1APIGroupList,
|
||||
V1APIResourceList,
|
||||
V1APIVersions,
|
||||
} from "@kubernetes/client-node";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { K8sRequest } from "../../main/k8s-request.injectable";
|
||||
import k8SRequestInjectable from "../../main/k8s-request.injectable";
|
||||
import type { Logger } from "../logger";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import type { KubeApiResource, KubeResource } from "../rbac";
|
||||
import type { Cluster } from "./cluster";
|
||||
import plimit from "p-limit";
|
||||
|
||||
export type RequestListApiResources = () => Promise<KubeApiResource[]>;
|
||||
|
||||
/**
|
||||
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
||||
*/
|
||||
export type ListApiResources = (cluster: Cluster) => RequestListApiResources;
|
||||
|
||||
interface Dependencies {
|
||||
logger: Logger;
|
||||
k8sRequest: K8sRequest;
|
||||
}
|
||||
|
||||
const listApiResources = ({ k8sRequest, logger }: Dependencies): ListApiResources => {
|
||||
return (cluster) => {
|
||||
const clusterRequest = (path: string) => k8sRequest(cluster, path);
|
||||
const apiLimit = plimit(5);
|
||||
|
||||
return async () => {
|
||||
const resources: KubeApiResource[] = [];
|
||||
|
||||
try {
|
||||
const resourceListGroups:{ group:string;path:string }[] = [];
|
||||
|
||||
await Promise.all(
|
||||
[
|
||||
clusterRequest("/api").then((response:V1APIVersions)=>response.versions.forEach(version => resourceListGroups.push({ group:version, path:`/api/${version}` }))),
|
||||
clusterRequest("/apis").then((response:V1APIGroupList) => response.groups.forEach(group => {
|
||||
const preferredVersion = group.preferredVersion?.groupVersion;
|
||||
|
||||
if (preferredVersion) {
|
||||
resourceListGroups.push({ group:group.name, path:`/apis/${preferredVersion}` });
|
||||
}
|
||||
})),
|
||||
],
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
resourceListGroups.map(({ group, path }) => apiLimit(async () => {
|
||||
const apiResources:V1APIResourceList = await clusterRequest(path);
|
||||
|
||||
if (apiResources.resources) {
|
||||
resources.push(
|
||||
...apiResources.resources.filter(resource => resource.verbs.includes("list")).map((resource) => ({
|
||||
apiName: resource.name as KubeResource,
|
||||
kind: resource.kind,
|
||||
group,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`);
|
||||
}
|
||||
|
||||
return resources;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const listApiResourcesInjectable = getInjectable({
|
||||
id: "list-api-resources",
|
||||
instantiate: (di) => {
|
||||
const k8sRequest = di.inject(k8SRequestInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return listApiResources({ k8sRequest, logger });
|
||||
},
|
||||
});
|
||||
|
||||
export default listApiResourcesInjectable;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ClusterId } from "../cluster-types";
|
||||
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
||||
|
||||
export const clusterVisibilityChannel: MessageChannel<ClusterId | null> = {
|
||||
id: "cluster-visibility",
|
||||
};
|
||||
+3
-3
@@ -4,16 +4,16 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import getAbsolutePathInjectable from "../path/get-absolute-path.injectable";
|
||||
import joinPathsInjectable from "../path/join-paths.injectable";
|
||||
|
||||
const directoryForLensLocalStorageInjectable = getInjectable({
|
||||
id: "directory-for-lens-local-storage",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||
|
||||
return getAbsolutePath(
|
||||
return joinPaths(
|
||||
directoryForUserData,
|
||||
"lens-local-storage",
|
||||
);
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import initializeSentryReportingWithInjectable from "./initialize-sentry-reporting.injectable";
|
||||
|
||||
export default getGlobalOverride(initializeSentryReportingWithInjectable, () => () => {});
|
||||
@@ -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 type { ElectronMainOptions } from "@sentry/electron/main";
|
||||
import type { BrowserOptions } from "@sentry/electron/renderer";
|
||||
import isProductionInjectable from "../vars/is-production.injectable";
|
||||
import sentryDataSourceNameInjectable from "../vars/sentry-dsn-url.injectable";
|
||||
import { Dedupe, Offline } from "@sentry/integrations";
|
||||
import { inspect } from "util";
|
||||
import userStoreInjectable from "../user-store/user-store.injectable";
|
||||
|
||||
export type InitializeSentryReportingWith = (initSentry: (opts: BrowserOptions | ElectronMainOptions) => void) => void;
|
||||
|
||||
const mapProcessName = (type: "browser" | "renderer" | "worker") => type === "browser" ? "main" : type;
|
||||
|
||||
const initializeSentryReportingWithInjectable = getInjectable({
|
||||
id: "initialize-sentry-reporting-with",
|
||||
instantiate: (di): InitializeSentryReportingWith => {
|
||||
const sentryDataSourceName = di.inject(sentryDataSourceNameInjectable);
|
||||
const isProduction = di.inject(isProductionInjectable);
|
||||
const userStore = di.inject(userStoreInjectable);
|
||||
|
||||
if (!sentryDataSourceName) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
return (initSentry) => initSentry({
|
||||
beforeSend: (event) => {
|
||||
if (userStore.allowErrorReporting) {
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly write to stdout so that no other integrations capture this and create an infinite loop
|
||||
*/
|
||||
process.stdout.write(`🔒 [SENTRY-BEFORE-SEND-HOOK]: Sentry event is caught but not sent to server.`);
|
||||
process.stdout.write("🔒 [SENTRY-BEFORE-SEND-HOOK]: === START OF SENTRY EVENT ===");
|
||||
process.stdout.write(inspect(event, false, null, true));
|
||||
process.stdout.write("🔒 [SENTRY-BEFORE-SEND-HOOK]: === END OF SENTRY EVENT ===");
|
||||
|
||||
// if return null, the event won't be sent
|
||||
// ref https://github.com/getsentry/sentry-javascript/issues/2039
|
||||
return null;
|
||||
},
|
||||
dsn: sentryDataSourceName,
|
||||
integrations: [
|
||||
new Dedupe(),
|
||||
new Offline(),
|
||||
],
|
||||
initialScope: {
|
||||
tags: {
|
||||
"process": mapProcessName(process.type),
|
||||
},
|
||||
},
|
||||
environment: isProduction ? "production" : "development",
|
||||
});
|
||||
},
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default initializeSentryReportingWithInjectable;
|
||||
@@ -29,7 +29,7 @@ export class EventEmitter<D extends [...any[]]> {
|
||||
this.listeners.length = 0;
|
||||
}
|
||||
|
||||
emit = (...data: D) => {
|
||||
emit(...data: D) {
|
||||
for (const [callback, { once }] of this.listeners) {
|
||||
if (once) {
|
||||
this.removeListener(callback);
|
||||
@@ -39,5 +39,5 @@ export class EventEmitter<D extends [...any[]]> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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 { RequestInit, Response } from "node-fetch";
|
||||
import type { AsyncResult } from "../utils/async-result";
|
||||
import fetchInjectable from "./fetch.injectable";
|
||||
|
||||
export interface DownloadBinaryOptions {
|
||||
signal?: AbortSignal | null | undefined;
|
||||
}
|
||||
|
||||
export type DownloadBinary = (url: string, opts?: DownloadBinaryOptions) => Promise<AsyncResult<Buffer, string>>;
|
||||
|
||||
const downloadBinaryInjectable = getInjectable({
|
||||
id: "download-binary",
|
||||
instantiate: (di): DownloadBinary => {
|
||||
const fetch = di.inject(fetchInjectable);
|
||||
|
||||
return async (url, opts) => {
|
||||
let result: Response;
|
||||
|
||||
try {
|
||||
// TODO: upgrade node-fetch once we switch to ESM
|
||||
result = await fetch(url, opts as RequestInit);
|
||||
} catch (error) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: String(error),
|
||||
};
|
||||
}
|
||||
|
||||
if (result.status < 200 || 300 <= result.status) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: result.statusText,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
response: await result.buffer(),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: String(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default downloadBinaryInjectable;
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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 { RequestInit, Response } from "node-fetch";
|
||||
import type { AsyncResult } from "../utils/async-result";
|
||||
import fetchInjectable from "./fetch.injectable";
|
||||
|
||||
export interface DownloadJsonOptions {
|
||||
signal?: AbortSignal | null | undefined;
|
||||
}
|
||||
|
||||
export type DownloadJson = (url: string, opts?: DownloadJsonOptions) => Promise<AsyncResult<unknown, string>>;
|
||||
|
||||
const downloadJsonInjectable = getInjectable({
|
||||
id: "download-json",
|
||||
instantiate: (di): DownloadJson => {
|
||||
const fetch = di.inject(fetchInjectable);
|
||||
|
||||
return async (url, opts) => {
|
||||
let result: Response;
|
||||
|
||||
try {
|
||||
result = await fetch(url, opts as RequestInit);
|
||||
} catch (error) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: String(error),
|
||||
};
|
||||
}
|
||||
|
||||
if (result.status < 200 || 300 <= result.status) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: result.statusText,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
response: await result.json(),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: String(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default downloadJsonInjectable;
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverrideForFunction } from "../test-utils/get-global-override-for-function";
|
||||
import fetchInjectable from "./fetch.injectable";
|
||||
|
||||
export default getGlobalOverrideForFunction(fetchInjectable);
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 * as FetchModule from "node-fetch";
|
||||
|
||||
const { NodeFetch: { default: fetch }} = require("../../../build/webpack/node-fetch.bundle") as { NodeFetch: typeof FetchModule };
|
||||
|
||||
type Response = FetchModule.Response;
|
||||
type RequestInit = FetchModule.RequestInit;
|
||||
|
||||
export type Fetch = (url: string, init?: RequestInit) => Promise<Response>;
|
||||
|
||||
const fetchInjectable = getInjectable({
|
||||
id: "fetch",
|
||||
instantiate: (): Fetch => fetch,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default fetchInjectable;
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates an AbortController with an associated timeout
|
||||
* @param timeout The number of milliseconds before this controller will auto abort
|
||||
*/
|
||||
export function withTimeout(timeout: number): AbortController {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
controller.signal.addEventListener("abort", () => clearTimeout(id));
|
||||
|
||||
return controller;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* 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 { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
||||
import type { MessageChannel } from "../utils/channel/message-channel-injection-token";
|
||||
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
|
||||
|
||||
export type AppNavigationChannel = MessageChannel<string>;
|
||||
|
||||
const appNavigationChannelInjectable = getInjectable({
|
||||
id: "app-navigation-channel",
|
||||
|
||||
instantiate: (): AppNavigationChannel => ({
|
||||
id: IpcRendererNavigationEvents.NAVIGATE_IN_APP,
|
||||
}),
|
||||
|
||||
injectionToken: messageChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default appNavigationChannelInjectable;
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
||||
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
||||
|
||||
export type AppNavigationChannel = MessageChannel<string>;
|
||||
|
||||
export const appNavigationChannel: AppNavigationChannel = {
|
||||
id: IpcRendererNavigationEvents.NAVIGATE_IN_APP,
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* 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 { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
||||
import type { MessageChannel } from "../utils/channel/message-channel-injection-token";
|
||||
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
|
||||
|
||||
export type ClusterFrameNavigationChannel = MessageChannel<string>;
|
||||
|
||||
const clusterFrameNavigationChannelInjectable = getInjectable({
|
||||
id: "cluster-frame-navigation-channel",
|
||||
|
||||
instantiate: (): ClusterFrameNavigationChannel => ({
|
||||
id: IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER,
|
||||
}),
|
||||
|
||||
injectionToken: messageChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default clusterFrameNavigationChannelInjectable;
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
||||
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
||||
|
||||
export type ClusterFrameNavigationChannel = MessageChannel<string>;
|
||||
|
||||
export const clusterFrameNavigationChannel: ClusterFrameNavigationChannel = {
|
||||
id: IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER,
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable";
|
||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||
|
||||
const leasesRouteInjectable = getInjectable({
|
||||
id: "leases",
|
||||
|
||||
instantiate: (di) => {
|
||||
const isAllowedResource = di.inject(isAllowedResourceInjectable, "leases");
|
||||
|
||||
return {
|
||||
path: "/leases",
|
||||
clusterFrame: true,
|
||||
isEnabled: isAllowedResource,
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default leasesRouteInjectable;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import leasesRouteInjectable from "./leases-route.injectable";
|
||||
import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token";
|
||||
|
||||
const navigateToLeasesInjectable = getInjectable({
|
||||
id: "navigate-to-leases",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
const route = di.inject(leasesRouteInjectable);
|
||||
|
||||
return () => navigateToRoute(route);
|
||||
},
|
||||
});
|
||||
|
||||
export default navigateToLeasesInjectable;
|
||||
+6
-6
@@ -3,18 +3,18 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import appPreferencesRouteInjectable from "./app-preferences-route.injectable";
|
||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
||||
import runtimeClassesRouteInjectable from "./runtime-classes-route.injectable";
|
||||
import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token";
|
||||
|
||||
const navigateToAppPreferencesInjectable = getInjectable({
|
||||
id: "navigate-to-app-preferences",
|
||||
const navigateToRuntimeClassesInjectable = getInjectable({
|
||||
id: "navigate-to-runtime-classes",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
const route = di.inject(appPreferencesRouteInjectable);
|
||||
const route = di.inject(runtimeClassesRouteInjectable);
|
||||
|
||||
return () => navigateToRoute(route);
|
||||
},
|
||||
});
|
||||
|
||||
export default navigateToAppPreferencesInjectable;
|
||||
export default navigateToRuntimeClassesInjectable;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable";
|
||||
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||
|
||||
const runtimeClassesRouteInjectable = getInjectable({
|
||||
id: "runtime-classes-route",
|
||||
|
||||
instantiate: (di) => {
|
||||
const isAllowedResource = di.inject(isAllowedResourceInjectable, "runtimeclasses");
|
||||
|
||||
return {
|
||||
path: "/runtimeclasses",
|
||||
clusterFrame: true,
|
||||
isEnabled: isAllowedResource,
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default runtimeClassesRouteInjectable;
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* 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 { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
import type { Route } from "../../../front-end-route-injection-token";
|
||||
|
||||
interface ExtensionPreferenceRouteParams {
|
||||
extensionId: string;
|
||||
tabId?: string;
|
||||
}
|
||||
|
||||
const extensionPreferencesRouteInjectable = getInjectable({
|
||||
id: "extension-preferences-route",
|
||||
|
||||
instantiate: (): Route<ExtensionPreferenceRouteParams> => ({
|
||||
path: "/preferences/extension/:extensionId/:tabId?",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default extensionPreferencesRouteInjectable;
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* 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 extensionPreferencesRouteInjectable from "./extension-preferences-route.injectable";
|
||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
||||
|
||||
const navigateToExtensionPreferencesInjectable = getInjectable({
|
||||
id: "navigate-to-extension-preferences",
|
||||
|
||||
instantiate: (di) => {
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
const route = di.inject(extensionPreferencesRouteInjectable);
|
||||
|
||||
return (extensionId: string, tabId?: string) => navigateToRoute(route, {
|
||||
parameters: {
|
||||
extensionId,
|
||||
tabId,
|
||||
},
|
||||
withoutAffectingBackButton: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default navigateToExtensionPreferencesInjectable;
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* 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 { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
|
||||
const kubernetesPreferencesRouteInjectable = getInjectable({
|
||||
id: "kubernetes-preferences-route",
|
||||
|
||||
instantiate: () => ({
|
||||
path: "/preferences/kubernetes",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default kubernetesPreferencesRouteInjectable;
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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 navigateToAppPreferencesInjectable from "./app/navigate-to-app-preferences.injectable";
|
||||
|
||||
const navigateToPreferencesInjectable = getInjectable({
|
||||
id: "navigate-to-preferences",
|
||||
|
||||
instantiate: (di) => di.inject(navigateToAppPreferencesInjectable),
|
||||
});
|
||||
|
||||
export default navigateToPreferencesInjectable;
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* 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 { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
|
||||
const proxyPreferencesRouteInjectable = getInjectable({
|
||||
id: "proxy-preferences-route",
|
||||
|
||||
instantiate: () => ({
|
||||
path: "/preferences/proxy",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default proxyPreferencesRouteInjectable;
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* 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 { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
|
||||
const telemetryPreferencesRouteInjectable = getInjectable({
|
||||
id: "telemetry-preferences-route",
|
||||
|
||||
instantiate: () => ({
|
||||
path: "/preferences/telemetry",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default telemetryPreferencesRouteInjectable;
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* 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 { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
||||
|
||||
const terminalPreferencesRouteInjectable = getInjectable({
|
||||
id: "terminal-preferences-route",
|
||||
|
||||
instantiate: () => ({
|
||||
path: "/preferences/terminal",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default terminalPreferencesRouteInjectable;
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import welcomeRouteConfigInjectable from "./welcome-route-config.injectable";
|
||||
import { frontEndRouteInjectionToken } from "../../front-end-route-injection-token";
|
||||
|
||||
const defaultWelcomeRouteInjectable = getInjectable({
|
||||
id: "default-welcome-route",
|
||||
|
||||
instantiate: (di) => {
|
||||
const welcomeRoute = di.inject(welcomeRouteConfigInjectable);
|
||||
|
||||
return {
|
||||
path: "/welcome",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => welcomeRoute === "/welcome"),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
export default defaultWelcomeRouteInjectable;
|
||||
@@ -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 applicationInformationInjectable from "../../../vars/application-information.injectable";
|
||||
|
||||
const welcomeRouteConfigInjectable = getInjectable({
|
||||
id: "welcome-route-config",
|
||||
|
||||
instantiate: (di) => di.inject(applicationInformationInjectable).config.welcomeRoute,
|
||||
});
|
||||
|
||||
export default welcomeRouteConfigInjectable;
|
||||
@@ -4,16 +4,21 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import welcomeRouteConfigInjectable from "./welcome-route-config.injectable";
|
||||
import { frontEndRouteInjectionToken } from "../../front-end-route-injection-token";
|
||||
|
||||
const welcomeRouteInjectable = getInjectable({
|
||||
id: "welcome-route",
|
||||
|
||||
instantiate: () => ({
|
||||
path: "/welcome",
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
}),
|
||||
instantiate: (di) => {
|
||||
const welcomeRoute = di.inject(welcomeRouteConfigInjectable);
|
||||
|
||||
return {
|
||||
path: welcomeRoute,
|
||||
clusterFrame: false,
|
||||
isEnabled: computed(() => true),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: frontEndRouteInjectionToken,
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting";
|
||||
import { routeSpecificComponentInjectionToken } from "../../renderer/routes/route-specific-component-injection-token";
|
||||
import { frontEndRouteInjectionToken } from "./front-end-route-injection-token";
|
||||
import { filter, map, matches } from "lodash/fp";
|
||||
import { filter, map } from "lodash/fp";
|
||||
import clusterStoreInjectable from "../cluster-store/cluster-store.injectable";
|
||||
import type { ClusterStore } from "../cluster-store/cluster-store";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
@@ -27,9 +27,11 @@ describe("verify-that-all-routes-have-component", () => {
|
||||
routes,
|
||||
|
||||
map(
|
||||
(route) => ({
|
||||
path: route.path,
|
||||
routeComponent: routeComponents.find(matches({ route })),
|
||||
(currentRoute) => ({
|
||||
path: currentRoute.path,
|
||||
routeComponent: routeComponents.find(({ route }) => (
|
||||
route.path === currentRoute.path
|
||||
&& route.clusterFrame === currentRoute.clusterFrame)),
|
||||
}),
|
||||
),
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import accessPathInjectable from "./access-path.injectable";
|
||||
|
||||
export default getGlobalOverride(accessPathInjectable, () => async () => {
|
||||
throw new Error("tried to verify path access without override");
|
||||
});
|
||||
@@ -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 fsInjectable from "./fs.injectable";
|
||||
|
||||
export type AccessPath = (path: string, mode?: number) => Promise<boolean>;
|
||||
|
||||
const accessPathInjectable = getInjectable({
|
||||
id: "access-path",
|
||||
instantiate: (di): AccessPath => {
|
||||
const { access } = di.inject(fsInjectable);
|
||||
|
||||
return async (path, mode) => {
|
||||
try {
|
||||
await access(path, mode);
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default accessPathInjectable;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import copyInjectable from "./copy.injectable";
|
||||
|
||||
export default getGlobalOverride(copyInjectable, () => async () => {
|
||||
throw new Error("tried to copy filepaths without override");
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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 { CopyOptions } from "fs-extra";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type Copy = (src: string, dest: string, options?: CopyOptions | undefined) => Promise<void>;
|
||||
|
||||
const copyInjectable = getInjectable({
|
||||
id: "copy",
|
||||
instantiate: (di): Copy => di.inject(fsInjectable).copy,
|
||||
});
|
||||
|
||||
export default copyInjectable;
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 { ReadStream } from "fs";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export interface CreateReadStreamOptions {
|
||||
mode?: number;
|
||||
end?: number | undefined;
|
||||
flags?: string | undefined;
|
||||
encoding?: BufferEncoding | undefined;
|
||||
autoClose?: boolean | undefined;
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
emitClose?: boolean | undefined;
|
||||
start?: number | undefined;
|
||||
highWaterMark?: number | undefined;
|
||||
}
|
||||
|
||||
export type CreateReadFileStream = (filePath: string, options?: CreateReadStreamOptions) => ReadStream;
|
||||
|
||||
const createReadFileStreamInjectable = getInjectable({
|
||||
id: "create-read-file-stream",
|
||||
instantiate: (di): CreateReadFileStream => di.inject(fsInjectable).createReadStream,
|
||||
});
|
||||
|
||||
export default createReadFileStreamInjectable;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import deleteFileInjectable from "./delete-file.injectable";
|
||||
|
||||
export default getGlobalOverride(deleteFileInjectable, () => async () => {
|
||||
throw new Error("tried to delete file without override");
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type DeleteFile = (filePath: string) => Promise<void>;
|
||||
|
||||
const deleteFileInjectable = getInjectable({
|
||||
id: "delete-file",
|
||||
instantiate: (di): DeleteFile => di.inject(fsInjectable).unlink,
|
||||
});
|
||||
|
||||
export default deleteFileInjectable;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import ensureDirInjectable from "./ensure-dir.injectable";
|
||||
|
||||
export default getGlobalOverride(ensureDirInjectable, () => async () => {
|
||||
throw new Error("tried to ensure directory without override");
|
||||
});
|
||||
@@ -5,14 +5,14 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type EnsureDirectory = (dirPath: string) => Promise<void>;
|
||||
|
||||
const ensureDirInjectable = getInjectable({
|
||||
id: "ensure-dir",
|
||||
|
||||
// TODO: Remove usages of ensureDir from business logic.
|
||||
// TODO: Read, Write, Watch etc. operations should do this internally.
|
||||
instantiate: (di) => di.inject(fsInjectable).ensureDir,
|
||||
|
||||
causesSideEffects: true,
|
||||
instantiate: (di): EnsureDirectory => di.inject(fsInjectable).ensureDir,
|
||||
});
|
||||
|
||||
export default ensureDirInjectable;
|
||||
@@ -3,22 +3,52 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { ExecFileOptions } from "child_process";
|
||||
import type { ExecFileException, ExecFileOptions } from "child_process";
|
||||
import { execFile } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import type { AsyncResult } from "../utils/async-result";
|
||||
|
||||
export type ExecFile = (filePath: string, args: string[], options: ExecFileOptions) => Promise<string>;
|
||||
export type ExecFileError = ExecFileException & { stderr: string };
|
||||
|
||||
export interface ExecFile {
|
||||
(filePath: string): Promise<AsyncResult<string, ExecFileError>>;
|
||||
(filePath: string, argsOrOptions: string[] | ExecFileOptions): Promise<AsyncResult<string, ExecFileError>>;
|
||||
(filePath: string, args: string[], options: ExecFileOptions): Promise<AsyncResult<string, ExecFileError>>;
|
||||
}
|
||||
|
||||
const execFileInjectable = getInjectable({
|
||||
id: "exec-file",
|
||||
|
||||
instantiate: (): ExecFile => {
|
||||
const asyncExecFile = promisify(execFile);
|
||||
return (filePath: string, argsOrOptions?: string[] | ExecFileOptions, maybeOptions?: ExecFileOptions) => {
|
||||
const { args, options } = (() => {
|
||||
if (Array.isArray(argsOrOptions)) {
|
||||
return {
|
||||
args: argsOrOptions,
|
||||
options: maybeOptions ?? {},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
args: [],
|
||||
options: argsOrOptions ?? {},
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
return async (filePath, args, options) => {
|
||||
const result = await asyncExecFile(filePath, args, options);
|
||||
|
||||
return result.stdout;
|
||||
return new Promise((resolve) => {
|
||||
execFile(filePath, args, options, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
resolve({
|
||||
callWasSuccessful: false,
|
||||
error: Object.assign(error, { stderr }),
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
callWasSuccessful: true,
|
||||
response: stdout,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import extractTarInjectable from "./extract-tar.injectable";
|
||||
|
||||
export default getGlobalOverride(extractTarInjectable, () => async () => {
|
||||
throw new Error("tried to extract a tar file without override");
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { ExtractOptions } from "tar";
|
||||
import { extract } from "tar";
|
||||
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||
|
||||
export type ExtractTar = (filePath: string, opts?: ExtractOptions) => Promise<void>;
|
||||
|
||||
const extractTarInjectable = getInjectable({
|
||||
id: "extract-tar",
|
||||
instantiate: (di): ExtractTar => {
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
|
||||
return (filePath, opts = {}) => extract({
|
||||
file: filePath,
|
||||
cwd: getDirnameOfPath(filePath),
|
||||
...opts,
|
||||
});
|
||||
},
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default extractTarInjectable;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import lstatInjectable from "./lstat.injectable";
|
||||
|
||||
export default getGlobalOverride(lstatInjectable, () => async () => {
|
||||
throw new Error("tried to lstat a filepath without override");
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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 LStat = (path: string) => Promise<Stats>;
|
||||
|
||||
const lstatInjectable = getInjectable({
|
||||
id: "lstat",
|
||||
instantiate: (di): LStat => di.inject(fsInjectable).lstat,
|
||||
});
|
||||
|
||||
export default lstatInjectable;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import moveInjectable from "./move.injectable";
|
||||
|
||||
export default getGlobalOverride(moveInjectable, () => async () => {
|
||||
throw new Error("tried to move without override");
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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 { MoveOptions } from "fs-extra";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type Move = (src: string, dest: string, options?: MoveOptions) => Promise<void>;
|
||||
|
||||
const moveInjectable = getInjectable({
|
||||
id: "move",
|
||||
instantiate: (di): Move => di.inject(fsInjectable).move,
|
||||
});
|
||||
|
||||
export default moveInjectable;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import pathExistsInjectable from "./path-exists.injectable";
|
||||
|
||||
export default getGlobalOverride(pathExistsInjectable, () => async () => {
|
||||
throw new Error("Tried to check if a path exists without override");
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import readDirectoryInjectable from "./read-directory.injectable";
|
||||
|
||||
export default getGlobalOverride(readDirectoryInjectable, () => async () => {
|
||||
throw new Error("tried to read a directory's content without override");
|
||||
});
|
||||
@@ -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 type { Dirent } from "fs";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export interface ReadDirectory {
|
||||
(
|
||||
path: string,
|
||||
options: "buffer" | { encoding: "buffer"; withFileTypes?: false | undefined }
|
||||
): Promise<Buffer[]>;
|
||||
(
|
||||
path: string,
|
||||
options?:
|
||||
| { encoding: BufferEncoding | string | null; withFileTypes?: false | undefined }
|
||||
| BufferEncoding
|
||||
| string
|
||||
| null,
|
||||
): Promise<string[]>;
|
||||
(
|
||||
path: string,
|
||||
options?: { encoding?: BufferEncoding | string | null | undefined; withFileTypes?: false | undefined },
|
||||
): Promise<string[] | Buffer[]>;
|
||||
(
|
||||
path: string,
|
||||
options: { encoding?: BufferEncoding | string | null | undefined; withFileTypes: true },
|
||||
): Promise<Dirent[]>;
|
||||
}
|
||||
|
||||
const readDirectoryInjectable = getInjectable({
|
||||
id: "read-directory",
|
||||
instantiate: (di): ReadDirectory => di.inject(fsInjectable).readdir,
|
||||
});
|
||||
|
||||
export default readDirectoryInjectable;
|
||||
@@ -5,11 +5,16 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type ReadFile = (filePath: string) => Promise<string>;
|
||||
|
||||
const readFileInjectable = getInjectable({
|
||||
id: "read-file",
|
||||
|
||||
instantiate: (di) => (filePath: string) =>
|
||||
di.inject(fsInjectable).readFile(filePath, "utf-8"),
|
||||
instantiate: (di): ReadFile => {
|
||||
const { readFile } = di.inject(fsInjectable);
|
||||
|
||||
return (filePath) => readFile(filePath, "utf-8");
|
||||
},
|
||||
});
|
||||
|
||||
export default readFileInjectable;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import removePathInjectable from "./remove-path.injectable";
|
||||
|
||||
export default getGlobalOverride(removePathInjectable, () => async () => {
|
||||
throw new Error("tried to remove a path without override");
|
||||
});
|
||||
@@ -5,9 +5,11 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
const readDirInjectable = getInjectable({
|
||||
id: "read-dir",
|
||||
instantiate: (di) => di.inject(fsInjectable).readdir,
|
||||
export type RemovePath = (path: string) => Promise<void>;
|
||||
|
||||
const removePathInjectable = getInjectable({
|
||||
id: "remove-path",
|
||||
instantiate: (di): RemovePath => di.inject(fsInjectable).remove,
|
||||
});
|
||||
|
||||
export default readDirInjectable;
|
||||
export default removePathInjectable;
|
||||
@@ -3,12 +3,14 @@
|
||||
* 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) => di.inject(fsInjectable).stat,
|
||||
instantiate: (di): Stat => di.inject(fsInjectable).stat,
|
||||
});
|
||||
|
||||
export default statInjectable;
|
||||
@@ -3,15 +3,161 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { FSWatcher, WatchOptions } from "chokidar";
|
||||
import { watch } from "chokidar";
|
||||
import type { Stats } from "fs";
|
||||
import type TypedEventEmitter from "typed-emitter";
|
||||
import type { SingleOrMany } from "../../utils";
|
||||
|
||||
export type Watch = (path: string, options?: WatchOptions) => FSWatcher;
|
||||
export interface AlwaysStatWatcherEvents {
|
||||
add: (path: string, stats: Stats) => void;
|
||||
addDir: (path: string, stats: Stats) => void;
|
||||
change: (path: string, stats: Stats) => void;
|
||||
}
|
||||
|
||||
export interface MaybeStatWatcherEvents {
|
||||
add: (path: string, stats?: Stats) => void;
|
||||
addDir: (path: string, stats?: Stats) => void;
|
||||
change: (path: string, stats?: Stats) => void;
|
||||
}
|
||||
|
||||
export type WatcherEvents<AlwaysStat extends boolean> = BaseWatcherEvents
|
||||
& (
|
||||
AlwaysStat extends true
|
||||
? AlwaysStatWatcherEvents
|
||||
: MaybeStatWatcherEvents
|
||||
);
|
||||
|
||||
export interface BaseWatcherEvents {
|
||||
error: (error: Error) => void;
|
||||
ready: () => void;
|
||||
unlink: (path: string) => void;
|
||||
unlinkDir: (path: string) => void;
|
||||
}
|
||||
|
||||
export interface Watcher<AlwaysStat extends boolean> extends TypedEventEmitter<WatcherEvents<AlwaysStat>> {
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
export type WatcherOptions<AlwaysStat extends boolean> = {
|
||||
/**
|
||||
* Indicates whether the process should continue to run as long as files are being watched. If
|
||||
* set to `false` when using `fsevents` to watch, no more events will be emitted after `ready`,
|
||||
* even if the process continues to run.
|
||||
*/
|
||||
persistent?: boolean;
|
||||
|
||||
/**
|
||||
* ([anymatch](https://github.com/micromatch/anymatch)-compatible definition) Defines files/paths to
|
||||
* be ignored. The whole relative or absolute path is tested, not just filename. If a function
|
||||
* with two arguments is provided, it gets called twice per path - once with a single argument
|
||||
* (the path), second time with two arguments (the path and the
|
||||
* [`fs.Stats`](https://nodejs.org/api/fs.html#fs_class_fs_stats) object of that path).
|
||||
*/
|
||||
ignored?: SingleOrMany<string | RegExp | ((path: string) => boolean)>;
|
||||
|
||||
/**
|
||||
* If set to `false` then `add`/`addDir` events are also emitted for matching paths while
|
||||
* instantiating the watching as chokidar discovers these file paths (before the `ready` event).
|
||||
*/
|
||||
ignoreInitial?: boolean;
|
||||
|
||||
/**
|
||||
* When `false`, only the symlinks themselves will be watched for changes instead of following
|
||||
* the link references and bubbling events through the link's path.
|
||||
*/
|
||||
followSymlinks?: boolean;
|
||||
|
||||
/**
|
||||
* The base directory from which watch `paths` are to be derived. Paths emitted with events will
|
||||
* be relative to this.
|
||||
*/
|
||||
cwd?: string;
|
||||
|
||||
/**
|
||||
* If set to true then the strings passed to .watch() and .add() are treated as literal path
|
||||
* names, even if they look like globs. Default: false.
|
||||
*/
|
||||
disableGlobbing?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to use fs.watchFile (backed by polling), or fs.watch. If polling leads to high CPU
|
||||
* utilization, consider setting this to `false`. It is typically necessary to **set this to
|
||||
* `true` to successfully watch files over a network**, and it may be necessary to successfully
|
||||
* watch files in other non-standard situations. Setting to `true` explicitly on OS X overrides
|
||||
* the `useFsEvents` default.
|
||||
*/
|
||||
usePolling?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to use the `fsevents` watching interface if available. When set to `true` explicitly
|
||||
* and `fsevents` is available this supercedes the `usePolling` setting. When set to `false` on
|
||||
* OS X, `usePolling: true` becomes the default.
|
||||
*/
|
||||
useFsEvents?: boolean;
|
||||
|
||||
/**
|
||||
* If set, limits how many levels of subdirectories will be traversed.
|
||||
*/
|
||||
depth?: number;
|
||||
|
||||
/**
|
||||
* Interval of file system polling.
|
||||
*/
|
||||
interval?: number;
|
||||
|
||||
/**
|
||||
* Interval of file system polling for binary files. ([see list of binary extensions](https://gi
|
||||
* thub.com/sindresorhus/binary-extensions/blob/master/binary-extensions.json))
|
||||
*/
|
||||
binaryInterval?: number;
|
||||
|
||||
/**
|
||||
* Indicates whether to watch files that don't have read permissions if possible. If watching
|
||||
* fails due to `EPERM` or `EACCES` with this set to `true`, the errors will be suppressed
|
||||
* silently.
|
||||
*/
|
||||
ignorePermissionErrors?: boolean;
|
||||
|
||||
/**
|
||||
* `true` if `useFsEvents` and `usePolling` are `false`). Automatically filters out artifacts
|
||||
* that occur when using editors that use "atomic writes" instead of writing directly to the
|
||||
* source file. If a file is re-added within 100 ms of being deleted, Chokidar emits a `change`
|
||||
* event rather than `unlink` then `add`. If the default of 100 ms does not work well for you,
|
||||
* you can override it by setting `atomic` to a custom value, in milliseconds.
|
||||
*/
|
||||
atomic?: boolean | number;
|
||||
|
||||
/**
|
||||
* can be set to an object in order to adjust timing params:
|
||||
*/
|
||||
awaitWriteFinish?: AwaitWriteFinishOptions | boolean;
|
||||
} & (AlwaysStat extends true
|
||||
? {
|
||||
alwaysStat: true;
|
||||
}
|
||||
: {
|
||||
alwaysStat?: false;
|
||||
}
|
||||
);
|
||||
|
||||
export interface AwaitWriteFinishOptions {
|
||||
/**
|
||||
* Amount of time in milliseconds for a file size to remain constant before emitting its event.
|
||||
*/
|
||||
stabilityThreshold?: number;
|
||||
|
||||
/**
|
||||
* File size polling interval.
|
||||
*/
|
||||
pollInterval?: number;
|
||||
}
|
||||
|
||||
export type Watch = <AlwaysStat extends boolean = false>(path: string, options?: WatcherOptions<AlwaysStat>) => Watcher<AlwaysStat>;
|
||||
|
||||
// TODO: Introduce wrapper to allow simpler API
|
||||
const watchInjectable = getInjectable({
|
||||
id: "watch",
|
||||
instantiate: (): Watch => watch,
|
||||
instantiate: () => watch as Watch,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import writeFileInjectable from "./write-file.injectable";
|
||||
|
||||
export default getGlobalOverride(writeFileInjectable, () => async () => {
|
||||
throw new Error("tried to write file without override");
|
||||
});
|
||||
@@ -3,20 +3,28 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import type { WriteFileOptions } from "fs-extra";
|
||||
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type WriteFile = (filePath: string, content: string | Buffer, opts?: WriteFileOptions) => Promise<void>;
|
||||
|
||||
const writeFileInjectable = getInjectable({
|
||||
id: "write-file",
|
||||
|
||||
instantiate: (di) => {
|
||||
instantiate: (di): WriteFile => {
|
||||
const { writeFile, ensureDir } = di.inject(fsInjectable);
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
|
||||
return async (filePath: string, content: string | Buffer) => {
|
||||
await ensureDir(path.dirname(filePath), { mode: 0o755 });
|
||||
return async (filePath, content, opts) => {
|
||||
await ensureDir(getDirnameOfPath(filePath), {
|
||||
mode: 0o755,
|
||||
...(opts ?? {}),
|
||||
});
|
||||
|
||||
await writeFile(filePath, content, {
|
||||
encoding: "utf-8",
|
||||
...(opts ?? {}),
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
@@ -3,37 +3,27 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { EnsureOptions, WriteOptions } from "fs-extra";
|
||||
import path from "path";
|
||||
import type { JsonValue } from "type-fest";
|
||||
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type WriteJson = (filePath: string, contents: JsonValue) => Promise<void>;
|
||||
|
||||
interface Dependencies {
|
||||
writeJson: (file: string, object: any, options?: WriteOptions | BufferEncoding | string) => Promise<void>;
|
||||
ensureDir: (dir: string, options?: EnsureOptions | number) => Promise<void>;
|
||||
}
|
||||
|
||||
const writeJsonFile = ({ writeJson, ensureDir }: Dependencies): WriteJson => async (filePath, content) => {
|
||||
await ensureDir(path.dirname(filePath), { mode: 0o755 });
|
||||
|
||||
await writeJson(filePath, content, {
|
||||
encoding: "utf-8",
|
||||
spaces: 2,
|
||||
});
|
||||
};
|
||||
|
||||
const writeJsonFileInjectable = getInjectable({
|
||||
id: "write-json-file",
|
||||
|
||||
instantiate: (di) => {
|
||||
instantiate: (di): WriteJson => {
|
||||
const { writeJson, ensureDir } = di.inject(fsInjectable);
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
|
||||
return writeJsonFile({
|
||||
writeJson,
|
||||
ensureDir,
|
||||
});
|
||||
return async (filePath, content) => {
|
||||
await ensureDir(getDirnameOfPath(filePath), { mode: 0o755 });
|
||||
|
||||
await writeJson(filePath, content, {
|
||||
encoding: "utf-8",
|
||||
spaces: 2,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* 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 { HelmRepo } from "./helm-repo";
|
||||
import type { RequestChannel } from "../utils/channel/request-channel-injection-token";
|
||||
import { requestChannelInjectionToken } from "../utils/channel/request-channel-injection-token";
|
||||
import type { AsyncResult } from "../utils/async-result";
|
||||
|
||||
export type AddHelmRepositoryChannel = RequestChannel<HelmRepo, AsyncResult<string>>;
|
||||
|
||||
const addHelmRepositoryChannelInjectable = getInjectable({
|
||||
id: "add-helm-repository-channel",
|
||||
|
||||
instantiate: (): AddHelmRepositoryChannel => ({
|
||||
id: "add-helm-repository-channel",
|
||||
}),
|
||||
|
||||
injectionToken: requestChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default addHelmRepositoryChannelInjectable;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { HelmRepo } from "./helm-repo";
|
||||
import type { AsyncResult } from "../utils/async-result";
|
||||
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
|
||||
|
||||
export type AddHelmRepositoryChannel = RequestChannel<HelmRepo, AsyncResult<void, string>>;
|
||||
|
||||
export const addHelmRepositoryChannel: AddHelmRepositoryChannel = {
|
||||
id: "add-helm-repository-channel",
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* 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 { RequestChannel } from "../utils/channel/request-channel-injection-token";
|
||||
import type { HelmRepo } from "./helm-repo";
|
||||
import { requestChannelInjectionToken } from "../utils/channel/request-channel-injection-token";
|
||||
import type { AsyncResult } from "../utils/async-result";
|
||||
|
||||
export type GetHelmRepositoriesChannel = RequestChannel<void, AsyncResult<HelmRepo[]>>;
|
||||
|
||||
const getActiveHelmRepositoriesChannelInjectable = getInjectable({
|
||||
id: "get-active-helm-repositories-channel",
|
||||
|
||||
instantiate: (): GetHelmRepositoriesChannel => ({
|
||||
id: "get-helm-active-list-repositories",
|
||||
}),
|
||||
|
||||
injectionToken: requestChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default getActiveHelmRepositoriesChannelInjectable;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { HelmRepo } from "./helm-repo";
|
||||
import type { AsyncResult } from "../utils/async-result";
|
||||
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
|
||||
|
||||
export type GetActiveHelmRepositoriesChannel = RequestChannel<void, AsyncResult<HelmRepo[]>>;
|
||||
|
||||
export const getActiveHelmRepositoriesChannel: GetActiveHelmRepositoriesChannel = {
|
||||
id: "get-helm-active-list-repositories",
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* 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 { HelmRepo } from "./helm-repo";
|
||||
import type { RequestChannel } from "../utils/channel/request-channel-injection-token";
|
||||
import { requestChannelInjectionToken } from "../utils/channel/request-channel-injection-token";
|
||||
|
||||
export type RemoveHelmRepositoryChannel = RequestChannel<HelmRepo>;
|
||||
|
||||
const removeHelmRepositoryChannelInjectable = getInjectable({
|
||||
id: "remove-helm-repository-channel",
|
||||
|
||||
instantiate: (): RemoveHelmRepositoryChannel => ({
|
||||
id: "remove-helm-repository-channel",
|
||||
}),
|
||||
|
||||
injectionToken: requestChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default removeHelmRepositoryChannelInjectable;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { AsyncResult } from "../utils/async-result";
|
||||
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
|
||||
import type { HelmRepo } from "./helm-repo";
|
||||
|
||||
export type RemoveHelmRepositoryChannel = RequestChannel<HelmRepo, AsyncResult<void, string>>;
|
||||
|
||||
export const removeHelmRepositoryChannel: RemoveHelmRepositoryChannel = {
|
||||
id: "remove-helm-repository-channel",
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import type { DiContainer, Injectable } from "@ogre-tools/injectable";
|
||||
import { runInAction } from "mobx";
|
||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||
import type { InitializableState } from "./create";
|
||||
import { createInitializableState } from "./create";
|
||||
|
||||
describe("InitializableState tests", () => {
|
||||
let di: DiContainer;
|
||||
|
||||
beforeEach(() => {
|
||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
});
|
||||
|
||||
describe("when created", () => {
|
||||
let stateInjectable: Injectable<InitializableState<number>, unknown, void>;
|
||||
let initMock: AsyncFnMock<() => number>;
|
||||
|
||||
beforeEach(() => {
|
||||
initMock = asyncFn();
|
||||
stateInjectable = createInitializableState({
|
||||
id: "my-state",
|
||||
init: initMock,
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
di.register(stateInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when injected", () => {
|
||||
let state: InitializableState<number>;
|
||||
|
||||
beforeEach(() => {
|
||||
state = di.inject(stateInjectable);
|
||||
});
|
||||
|
||||
it("when get is called, throw", () => {
|
||||
expect(() => state.get()).toThrowError("InitializableState(my-state) has not been initialized yet");
|
||||
});
|
||||
|
||||
describe("when init is called", () => {
|
||||
beforeEach(() => {
|
||||
state.init();
|
||||
});
|
||||
|
||||
it("should call provided initialization function", () => {
|
||||
expect(initMock).toBeCalled();
|
||||
});
|
||||
|
||||
it("when get is called, throw", () => {
|
||||
expect(() => state.get()).toThrowError("InitializableState(my-state) has not finished initializing");
|
||||
});
|
||||
|
||||
describe("when initialization resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await initMock.resolve(42);
|
||||
});
|
||||
|
||||
it("when get is called, returns value", () => {
|
||||
expect(state.get()).toBe(42);
|
||||
});
|
||||
|
||||
it("when init is called again, throws", async () => {
|
||||
await expect(() => state.init()).rejects.toThrow("Cannot initialize InitializableState(my-state) more than once");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { DiContainerForInjection, Injectable, InjectionToken } from "@ogre-tools/injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
export interface CreateInitializableStateArgs<T> {
|
||||
id: string;
|
||||
init: (di: DiContainerForInjection) => Promise<T> | T;
|
||||
injectionToken?: InjectionToken<InitializableState<T>, void>;
|
||||
}
|
||||
|
||||
export interface InitializableState<T> {
|
||||
get: () => T;
|
||||
init: () => Promise<void>;
|
||||
}
|
||||
|
||||
type InitializableStateValue<T> =
|
||||
| { set: false }
|
||||
| { set: true; value: T } ;
|
||||
|
||||
export function createInitializableState<T>(args: CreateInitializableStateArgs<T>): Injectable<InitializableState<T>, unknown, void> {
|
||||
const { id, init, injectionToken } = args;
|
||||
|
||||
return getInjectable({
|
||||
id,
|
||||
instantiate: (di) => {
|
||||
let box: InitializableStateValue<T> = {
|
||||
set: false,
|
||||
};
|
||||
let initCalled = false;
|
||||
|
||||
return {
|
||||
init: async () => {
|
||||
if (initCalled) {
|
||||
throw new Error(`Cannot initialize InitializableState(${id}) more than once`);
|
||||
}
|
||||
|
||||
initCalled = true;
|
||||
box = {
|
||||
set: true,
|
||||
value: await init(di),
|
||||
};
|
||||
},
|
||||
get: () => {
|
||||
if (!initCalled) {
|
||||
throw new Error(`InitializableState(${id}) has not been initialized yet`);
|
||||
}
|
||||
|
||||
if (box.set === false) {
|
||||
throw new Error(`InitializableState(${id}) has not finished initializing`);
|
||||
}
|
||||
|
||||
return box.value;
|
||||
},
|
||||
};
|
||||
},
|
||||
injectionToken,
|
||||
});
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from "events";
|
||||
import { onCorrect, onceCorrect } from "../type-enforced-ipc";
|
||||
|
||||
describe("type enforced ipc tests", () => {
|
||||
describe("onCorrect tests", () => {
|
||||
it("should call the handler if the args are valid", () => {
|
||||
let called = false;
|
||||
const source = new EventEmitter();
|
||||
const listener = () => called = true;
|
||||
const verifier = (args: unknown[]): args is [] => true;
|
||||
const channel = "foobar";
|
||||
|
||||
onCorrect({ source, listener, verifier, channel });
|
||||
|
||||
source.emit(channel);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it("should not call the handler if the args are not valid", () => {
|
||||
let called = false;
|
||||
const source = new EventEmitter();
|
||||
const listener = () => called = true;
|
||||
const verifier = (args: unknown[]): args is [] => false;
|
||||
const channel = "foobar";
|
||||
|
||||
onCorrect({ source, listener, verifier, channel });
|
||||
|
||||
source.emit(channel);
|
||||
expect(called).toBe(false);
|
||||
});
|
||||
|
||||
it("should call the handler twice if the args are valid on two emits", () => {
|
||||
let called = 0;
|
||||
const source = new EventEmitter();
|
||||
const listener = () => called += 1;
|
||||
const verifier = (args: unknown[]): args is [] => true;
|
||||
const channel = "foobar";
|
||||
|
||||
onCorrect({ source, listener, verifier, channel });
|
||||
|
||||
source.emit(channel);
|
||||
source.emit(channel);
|
||||
expect(called).toBe(2);
|
||||
});
|
||||
|
||||
it("should call the handler twice if the args are [valid, invalid, valid]", () => {
|
||||
let called = 0;
|
||||
const source = new EventEmitter();
|
||||
const listener = () => called += 1;
|
||||
const results = [true, false, true];
|
||||
const verifier = (args: unknown[]): args is [] => results.pop() ?? false;
|
||||
const channel = "foobar";
|
||||
|
||||
onCorrect({ source, listener, verifier, channel });
|
||||
|
||||
source.emit(channel);
|
||||
source.emit(channel);
|
||||
source.emit(channel);
|
||||
expect(called).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("onceCorrect tests", () => {
|
||||
it("should call the handler if the args are valid", () => {
|
||||
let called = false;
|
||||
const source = new EventEmitter();
|
||||
const listener = () => called = true;
|
||||
const verifier = (args: unknown[]): args is [] => true;
|
||||
const channel = "foobar";
|
||||
|
||||
onceCorrect({ source, listener, verifier, channel });
|
||||
|
||||
source.emit(channel);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it("should not call the handler if the args are not valid", () => {
|
||||
let called = false;
|
||||
const source = new EventEmitter();
|
||||
const listener = () => called = true;
|
||||
const verifier = (args: unknown[]): args is [] => false;
|
||||
const channel = "foobar";
|
||||
|
||||
onceCorrect({ source, listener, verifier, channel });
|
||||
|
||||
source.emit(channel);
|
||||
expect(called).toBe(false);
|
||||
});
|
||||
|
||||
it("should call the handler only once even if args are valid multiple times", () => {
|
||||
let called = 0;
|
||||
const source = new EventEmitter();
|
||||
const listener = () => called += 1;
|
||||
const verifier = (args: unknown[]): args is [] => true;
|
||||
const channel = "foobar";
|
||||
|
||||
onceCorrect({ source, listener, verifier, channel });
|
||||
|
||||
source.emit(channel);
|
||||
source.emit(channel);
|
||||
expect(called).toBe(1);
|
||||
});
|
||||
|
||||
it("should call the handler on only the first valid set of args", () => {
|
||||
let called = "";
|
||||
let verifierCalled = 0;
|
||||
const source = new EventEmitter();
|
||||
const listener = (info: any, arg: string) => called = arg;
|
||||
const verifier = (args: unknown[]): args is [string] => (++verifierCalled) % 3 === 0;
|
||||
const channel = "foobar";
|
||||
|
||||
onceCorrect({ source, listener, verifier, channel });
|
||||
|
||||
source.emit(channel, {}, "a");
|
||||
source.emit(channel, {}, "b");
|
||||
source.emit(channel, {}, "c");
|
||||
source.emit(channel, {}, "d");
|
||||
source.emit(channel, {}, "e");
|
||||
source.emit(channel, {}, "f");
|
||||
source.emit(channel, {}, "g");
|
||||
source.emit(channel, {}, "h");
|
||||
source.emit(channel, {}, "i");
|
||||
expect(called).toBe("c");
|
||||
});
|
||||
});
|
||||
});
|
||||
Loaded 100 of 1328 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user