mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Make base store non Singleton (#6690)
* Remove Singleton from BaseStore to remove global shared state Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove more usages of Singleton Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace use of legacy global execHelm with injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove last use of legacy global execHelm Signed-off-by: Sebastian Malton <sebastian@malton.name> * Extract BaseStore deps into constructor argument Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce method to make store migrations injectable - Use it for ClusterStore Signed-off-by: Sebastian Malton <sebastian@malton.name> * Switch HotbarStore to injectable migrations Signed-off-by: Sebastian Malton <sebastian@malton.name> * Switch UserStore to injectable migrations Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move migration utils into common/utils/ Signed-off-by: Sebastian Malton <sebastian@malton.name> * Switch WeblinkStore to injectable migrations Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove dead code Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type error in base-store tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove tests that reference lastSeenVersion - That value is not used anywhere in code Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove usage of legacy global .getInstance Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove usage of legacy global ClusterStore.getInstance Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add simple migrations dependency for stores without any preexisting migrations Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix messed up import Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add typing to transient injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Cleanup formatting Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix typing in tests to satisfy requirement to have cacheFile Signed-off-by: Sebastian Malton <sebastian@malton.name> * More consistent use of BaseStore.displayName Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add catching of error while starting main application Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move initializing sentry to runnable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove unneeded appPathsInjectionToken - Only had once impl, which was in common anyway Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add support for multiple "runAfter" runnables - Needed so that several dependencies can be declared Signed-off-by: Sebastian Malton <sebastian@malton.name> * Use multiple runAfter support to fix crash on renderer Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove traces Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add global override to fix tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix base store tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix runManyFor tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix hotbar store tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix user store tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add global override for getConfigurationFileModel to fix tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove overrides for configuration stores - Now that there is an override for getConfiguration Signed-off-by: Sebastian Malton <sebastian@malton.name> * Overhaul FS fakes with full in-memory filesystem - This increases our confidence in fs related logic Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove use of global shared Electron.App Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add fake access support Signed-off-by: Sebastian Malton <sebastian@malton.name> * Handle copy as part of fake FS Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add ensureDir/Sync support to fake FS Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type error Signed-off-by: Sebastian Malton <sebastian@malton.name> * Use pathExistsSync instead of fsInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add createReadStream to fake FS Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add stat to fake FS Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove dead code Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix test failures due to incomplete overrides Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully injectable-ize BaseStore so that ApplicationBuilder tests work Signed-off-by: Sebastian Malton <sebastian@malton.name> * Consolidate more bootstrapping into startFrame Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move initializing CatalogCategories to runnable in bootstrap Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert contextMenuOpen initializers into runnables Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert navigateForExtension init to runnable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make cluster state sync fully injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move init hotbar store into runnables Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make LensTheme fully injectable and runnable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Cleanup old code from missed from previous commit Signed-off-by: Sebastian Malton <sebastian@malton.name> * Manually split out terminal color names and fully type LensTheme Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix old imports Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove unnecessart awaits Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove dead code Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully cherry pick injectablizing custom monaco themes Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix duplicate mock warning Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix incorrectly fully cherry picking new runnable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Complete cherry-pick of current cluster injcetablization Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix override file name Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix injecting before app paths are set up Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix injecting before app paths are set up Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix ordering of runnable and order of injection Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all renderer runnables to late-inject style - To help fix issues around injection time Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix react-beautiful-dnd mocks Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update and fix WriteJson(Sync) to fix error in tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix HotbarStore.load being called twice is being buggy Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update listing-active-helm-repositories-in-preferences snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix sidebar-and-tab-navigation-tests - Move enabling extensions in tests to a proper location - Fix flushing promises Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove props from dnd mock to make snapshot diffs smaller Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix import Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests by overriding things that are no longer overriden by default - NOTE: They are overridden when using ApplicationBuilder Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix hotbar store tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix cluster store tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix extension-loader tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix extension-discovery tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix cluster-role-dialog tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix user store tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix kubeconfig sync tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix sidebar and tab tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove unused code Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix pick paths import type error and simplify signature Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type error in legacy ipc registration Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove another use of legacy requestOpenPathPicker Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace use of legacy global PathPicker.Pick Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix usage in light of changed prop names Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix catalog tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix more type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix test flakiness by removing side effects from userStore preferences Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix loading Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type error Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash Signed-off-by: Sebastian Malton <sebastian@malton.name> * Cherry pick updated startFrameInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add tests to verify runMany behaviour in new possible incorrect configuration Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix init ordering during start frame Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix cluster state sync Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots after removing side-effects Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add override for technical test Signed-off-by: Sebastian Malton <sebastian@malton.name> * Correctly mark currentlyInClusterFrame as causedSideEffects Signed-off-by: Sebastian Malton <sebastian@malton.name> * Better formatting Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix behaviour regression Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add better logging Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix BaseStore sync Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update last snapshot Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add global override for randomBytes Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make startMainApplication not an injection time side effect Signed-off-by: Sebastian Malton <sebastian@malton.name> * Choose better names for start-frame runnable tokens Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove duplication of code in RunManyFor Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add unit tests and fix handling empty runAfter array Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace use of mobx from runManyFor with custom barrier Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add missing test Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
7c5faaaf1d
commit
25f37ac1d1
@ -1,14 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
export default {};
|
|
||||||
|
|
||||||
export const Uri = {
|
|
||||||
file: (path: string) => path,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const editor = {
|
|
||||||
getModel: () => ({}),
|
|
||||||
create: () => ({}),
|
|
||||||
};
|
|
||||||
@ -7,9 +7,49 @@ import React from "react";
|
|||||||
import type {
|
import type {
|
||||||
DragDropContextProps,
|
DragDropContextProps,
|
||||||
DraggableProps,
|
DraggableProps,
|
||||||
|
DraggableProvidedDraggableProps,
|
||||||
DroppableProps,
|
DroppableProps,
|
||||||
|
DroppableProvidedProps,
|
||||||
} from "react-beautiful-dnd";
|
} from "react-beautiful-dnd";
|
||||||
|
|
||||||
export const DragDropContext = ({ children }: DragDropContextProps) => <>{ children }</>;
|
export const DragDropContext = ({ children }: DragDropContextProps) => <>{ children }</>;
|
||||||
export const Draggable = ({ children }: DraggableProps) => <>{ children({} as any, {} as any, {} as any) }</>;
|
export const Draggable = ({ children }: DraggableProps) => (
|
||||||
export const Droppable = ({ children }: DroppableProps) => <>{ children({} as any, {} as any) }</>;
|
<>
|
||||||
|
{
|
||||||
|
children(
|
||||||
|
{
|
||||||
|
draggableProps: {} as DraggableProvidedDraggableProps,
|
||||||
|
innerRef: () => {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isDragging: false,
|
||||||
|
isDropAnimating: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
draggableId: "some-mock-draggable-id",
|
||||||
|
mode: "FLUID",
|
||||||
|
source: {
|
||||||
|
droppableId: "some-mock-droppable-id",
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
export const Droppable = ({ children }: DroppableProps) => (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
children(
|
||||||
|
{
|
||||||
|
droppableProps: {} as DroppableProvidedProps,
|
||||||
|
innerRef: () => {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isDraggingOver: false,
|
||||||
|
isUsingPlaceholder: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fs from "fs-extra";
|
|
||||||
import path from "path";
|
|
||||||
import defaultBaseLensTheme from "../src/renderer/themes/lens-dark";
|
|
||||||
|
|
||||||
const outputCssFile = path.resolve("src/renderer/themes/theme-vars.css");
|
|
||||||
|
|
||||||
const banner = `/*
|
|
||||||
Generated Lens theme CSS-variables, don't edit manually.
|
|
||||||
To refresh file run $: yarn run ts-node build/${path.basename(__filename)}
|
|
||||||
*/`;
|
|
||||||
|
|
||||||
const themeCssVars = Object.entries(defaultBaseLensTheme.colors)
|
|
||||||
.map(([varName, value]) => `--${varName}: ${value};`);
|
|
||||||
|
|
||||||
const content = `
|
|
||||||
${banner}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
${themeCssVars.join("\n")}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Run
|
|
||||||
console.info(`"Saving default Lens theme css-variables to "${outputCssFile}""`);
|
|
||||||
fs.ensureFileSync(outputCssFile);
|
|
||||||
fs.writeFile(outputCssFile, content);
|
|
||||||
@ -228,6 +228,7 @@
|
|||||||
"grapheme-splitter": "^1.0.4",
|
"grapheme-splitter": "^1.0.4",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"history": "^4.10.1",
|
"history": "^4.10.1",
|
||||||
|
"hpagent": "^1.2.0",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"immer": "^9.0.16",
|
"immer": "^9.0.16",
|
||||||
"joi": "^17.7.0",
|
"joi": "^17.7.0",
|
||||||
|
|||||||
@ -1,148 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import mockFs from "mock-fs";
|
|
||||||
import { BaseStore } from "../base-store";
|
|
||||||
import { action, comparer, makeObservable, observable, toJS } from "mobx";
|
|
||||||
import { readFileSync } from "fs";
|
|
||||||
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";
|
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
|
||||||
ipcMain: {
|
|
||||||
on: jest.fn(),
|
|
||||||
off: jest.fn(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
interface TestStoreModel {
|
|
||||||
a: string;
|
|
||||||
b: string;
|
|
||||||
c: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestStore extends BaseStore<TestStoreModel> {
|
|
||||||
@observable a = "";
|
|
||||||
@observable b = "";
|
|
||||||
@observable c = "";
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
configName: "test-store",
|
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
|
||||||
syncOptions: {
|
|
||||||
equals: comparer.structural,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
makeObservable(this);
|
|
||||||
this.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
@action updateAll(data: TestStoreModel) {
|
|
||||||
this.a = data.a;
|
|
||||||
this.b = data.b;
|
|
||||||
this.c = data.c;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action fromStore(data: Partial<TestStoreModel> = {}) {
|
|
||||||
this.a = data.a || "";
|
|
||||||
this.b = data.b || "";
|
|
||||||
this.c = data.c || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
onSync(data: TestStoreModel) {
|
|
||||||
super.onSync(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveToFile(model: TestStoreModel) {
|
|
||||||
return super.saveToFile(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): TestStoreModel {
|
|
||||||
const data: TestStoreModel = {
|
|
||||||
a: this.a,
|
|
||||||
b: this.b,
|
|
||||||
c: this.c,
|
|
||||||
};
|
|
||||||
|
|
||||||
return toJS(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("BaseStore", () => {
|
|
||||||
let store: TestStore;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const mainDi = getDiForUnitTesting({ doGeneralOverrides: true });
|
|
||||||
|
|
||||||
mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory");
|
|
||||||
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
|
|
||||||
|
|
||||||
TestStore.resetInstance();
|
|
||||||
|
|
||||||
const mockOpts = {
|
|
||||||
"some-user-data-directory": {
|
|
||||||
"test-store.json": JSON.stringify({}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
mockFs(mockOpts);
|
|
||||||
|
|
||||||
store = TestStore.createInstance();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mockFs.restore();
|
|
||||||
store.disableSync();
|
|
||||||
TestStore.resetInstance();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("persistence", () => {
|
|
||||||
it("persists changes to the filesystem", () => {
|
|
||||||
store.updateAll({
|
|
||||||
a: "foo", b: "bar", c: "hello",
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = JSON.parse(readFileSync("some-user-data-directory/test-store.json").toString());
|
|
||||||
|
|
||||||
expect(data).toEqual({ a: "foo", b: "bar", c: "hello" });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("persists transaction only once", () => {
|
|
||||||
const fileSpy = jest.spyOn(store, "saveToFile");
|
|
||||||
|
|
||||||
store.updateAll({
|
|
||||||
a: "foo", b: "bar", c: "hello",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(fileSpy).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("persists changes one-by-one without transaction", () => {
|
|
||||||
const fileSpy = jest.spyOn(store, "saveToFile");
|
|
||||||
|
|
||||||
store.a = "a";
|
|
||||||
store.b = "b";
|
|
||||||
|
|
||||||
expect(fileSpy).toHaveBeenCalledTimes(2);
|
|
||||||
|
|
||||||
const data = JSON.parse(readFileSync("some-user-data-directory/test-store.json").toString());
|
|
||||||
|
|
||||||
expect(data).toEqual({ a: "a", b: "b", c: "" });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("persists changes coming via onSync (sync from different process)", () => {
|
|
||||||
const fileSpy = jest.spyOn(store, "saveToFile");
|
|
||||||
|
|
||||||
store.onSync({ a: "foo", b: "", c: "bar" });
|
|
||||||
|
|
||||||
expect(store.toJSON()).toEqual({ a: "foo", b: "", c: "bar" });
|
|
||||||
|
|
||||||
expect(fileSpy).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -93,10 +93,9 @@ describe("cluster-store", () => {
|
|||||||
mainDi.override(normalizedPlatformInjectable, () => "darwin");
|
mainDi.override(normalizedPlatformInjectable, () => "darwin");
|
||||||
|
|
||||||
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
|
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
mainDi.permitSideEffects(clusterStoreInjectable);
|
mainDi.unoverride(getConfigurationFileModelInjectable);
|
||||||
mainDi.permitSideEffects(fsInjectable);
|
|
||||||
|
|
||||||
mainDi.unoverride(clusterStoreInjectable);
|
mainDi.permitSideEffects(fsInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -107,23 +106,19 @@ describe("cluster-store", () => {
|
|||||||
let getCustomKubeConfigDirectory: (directoryName: string) => string;
|
let getCustomKubeConfigDirectory: (directoryName: string) => string;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
getCustomKubeConfigDirectory = mainDi.inject(
|
getCustomKubeConfigDirectory = mainDi.inject(getCustomKubeConfigDirectoryInjectable);
|
||||||
getCustomKubeConfigDirectoryInjectable,
|
|
||||||
);
|
|
||||||
|
|
||||||
const mockOpts = {
|
mockFs({
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({}),
|
"lens-cluster-store.json": JSON.stringify({}),
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
mockFs(mockOpts);
|
|
||||||
|
|
||||||
createCluster = mainDi.inject(createClusterInjectionToken);
|
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
clusterStore = mainDi.inject(clusterStoreInjectable);
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
|
|
||||||
clusterStore.unregisterIpcListener();
|
clusterStore.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -207,7 +202,7 @@ describe("cluster-store", () => {
|
|||||||
|
|
||||||
describe("config with existing clusters", () => {
|
describe("config with existing clusters", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const mockOpts = {
|
mockFs({
|
||||||
"temp-kube-config": kubeconfig,
|
"temp-kube-config": kubeconfig,
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
"lens-cluster-store.json": JSON.stringify({
|
||||||
@ -241,13 +236,12 @@ describe("cluster-store", () => {
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
mockFs(mockOpts);
|
|
||||||
|
|
||||||
createCluster = mainDi.inject(createClusterInjectionToken);
|
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
clusterStore = mainDi.inject(clusterStoreInjectable);
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
|
clusterStore.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -297,7 +291,7 @@ users:
|
|||||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const mockOpts = {
|
mockFs({
|
||||||
"invalid-kube-config": invalidKubeconfig,
|
"invalid-kube-config": invalidKubeconfig,
|
||||||
"valid-kube-config": kubeconfig,
|
"valid-kube-config": kubeconfig,
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
@ -325,13 +319,12 @@ users:
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
mockFs(mockOpts);
|
|
||||||
|
|
||||||
createCluster = mainDi.inject(createClusterInjectionToken);
|
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
clusterStore = mainDi.inject(clusterStoreInjectable);
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
|
clusterStore.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -347,7 +340,7 @@ users:
|
|||||||
|
|
||||||
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const mockOpts = {
|
mockFs({
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
"lens-cluster-store.json": JSON.stringify({
|
||||||
__internal__: {
|
__internal__: {
|
||||||
@ -368,15 +361,14 @@ users:
|
|||||||
}),
|
}),
|
||||||
icon_path: testDataIcon,
|
icon_path: testDataIcon,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
mockFs(mockOpts);
|
|
||||||
|
|
||||||
mainDi.override(storeMigrationVersionInjectable, () => "3.6.0");
|
mainDi.override(storeMigrationVersionInjectable, () => "3.6.0");
|
||||||
|
|
||||||
createCluster = mainDi.inject(createClusterInjectionToken);
|
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
clusterStore = mainDi.inject(clusterStoreInjectable);
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
|
clusterStore.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import loggerInjectable from "../logger.injectable";
|
|||||||
import type { Logger } from "../logger";
|
import type { Logger } from "../logger";
|
||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||||
|
import fsInjectable from "../fs/fs.injectable";
|
||||||
|
|
||||||
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
|
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
|
||||||
return {
|
return {
|
||||||
@ -46,7 +47,7 @@ describe("HotbarStore", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
(di as any).unoverride(hotbarStoreInjectable);
|
di.unoverride(hotbarStoreInjectable);
|
||||||
|
|
||||||
testCluster = getMockCatalogEntity({
|
testCluster = getMockCatalogEntity({
|
||||||
apiVersion: "v1",
|
apiVersion: "v1",
|
||||||
@ -112,8 +113,9 @@ describe("HotbarStore", () => {
|
|||||||
catalogCatalogEntity,
|
catalogCatalogEntity,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
di.permitSideEffects(fsInjectable);
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(hotbarStoreInjectable);
|
di.unoverride(getConfigurationFileModelInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -255,22 +257,12 @@ describe("HotbarStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("throws if invalid arguments provided", () => {
|
it("throws if invalid arguments provided", () => {
|
||||||
// Prevent writing to stderr during this render.
|
|
||||||
const { error, warn } = console;
|
|
||||||
|
|
||||||
console.error = jest.fn();
|
|
||||||
console.warn = jest.fn();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
|
|
||||||
expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
|
expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
|
||||||
expect(() => hotbarStore.restackItems(2, -1)).toThrow();
|
expect(() => hotbarStore.restackItems(2, -1)).toThrow();
|
||||||
expect(() => hotbarStore.restackItems(14, 1)).toThrow();
|
expect(() => hotbarStore.restackItems(14, 1)).toThrow();
|
||||||
expect(() => hotbarStore.restackItems(11, 112)).toThrow();
|
expect(() => hotbarStore.restackItems(11, 112)).toThrow();
|
||||||
|
|
||||||
// Restore writing to stderr.
|
|
||||||
console.error = error;
|
|
||||||
console.warn = warn;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("checks if entity already pinned to hotbar", () => {
|
it("checks if entity already pinned to hotbar", () => {
|
||||||
@ -284,7 +276,7 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
|
describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const configurationToBeMigrated = {
|
mockFs({
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
"lens-hotbar-store.json": JSON.stringify({
|
"lens-hotbar-store.json": JSON.stringify({
|
||||||
__internal__: {
|
__internal__: {
|
||||||
@ -344,9 +336,7 @@ describe("HotbarStore", () => {
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
mockFs(configurationToBeMigrated);
|
|
||||||
|
|
||||||
di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10");
|
di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10");
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import getConfigurationFileModelInjectable from "../get-configuration-file-model
|
|||||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||||
import releaseChannelInjectable from "../vars/release-channel.injectable";
|
import releaseChannelInjectable from "../vars/release-channel.injectable";
|
||||||
import defaultUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
|
import defaultUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
|
||||||
|
import fsInjectable from "../fs/fs.injectable";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
@ -49,8 +50,10 @@ describe("user store tests", () => {
|
|||||||
|
|
||||||
di.override(writeFileInjectable, () => () => Promise.resolve());
|
di.override(writeFileInjectable, () => () => Promise.resolve());
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(userStoreInjectable);
|
di.unoverride(getConfigurationFileModelInjectable);
|
||||||
|
di.permitSideEffects(fsInjectable);
|
||||||
|
|
||||||
di.override(releaseChannelInjectable, () => ({
|
di.override(releaseChannelInjectable, () => ({
|
||||||
get: () => "latest" as const,
|
get: () => "latest" as const,
|
||||||
@ -58,7 +61,7 @@ describe("user store tests", () => {
|
|||||||
}));
|
}));
|
||||||
await di.inject(defaultUpdateChannelInjectable).init();
|
await di.inject(defaultUpdateChannelInjectable).init();
|
||||||
|
|
||||||
di.unoverride(userStoreInjectable);
|
userStore = di.inject(userStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -67,17 +70,11 @@ describe("user store tests", () => {
|
|||||||
|
|
||||||
describe("for an empty config", () => {
|
describe("for an empty config", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockFs({ "some-directory-for-user-data": { "config.json": "{}", "kube_config": "{}" }});
|
mockFs({ "some-directory-for-user-data": { "lens-user-store.json": "{}", "kube_config": "{}" }});
|
||||||
|
|
||||||
userStore = di.inject(userStoreInjectable);
|
|
||||||
userStore.load();
|
userStore.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows setting and retrieving lastSeenAppVersion", () => {
|
|
||||||
userStore.lastSeenAppVersion = "1.2.3";
|
|
||||||
expect(userStore.lastSeenAppVersion).toBe("1.2.3");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows setting and getting preferences", () => {
|
it("allows setting and getting preferences", () => {
|
||||||
userStore.httpsProxy = "abcd://defg";
|
userStore.httpsProxy = "abcd://defg";
|
||||||
|
|
||||||
@ -99,10 +96,8 @@ describe("user store tests", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockFs({
|
mockFs({
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
"config.json": JSON.stringify({
|
"lens-user-store.json": JSON.stringify({
|
||||||
user: { username: "foobar" },
|
|
||||||
preferences: { colorTheme: "light" },
|
preferences: { colorTheme: "light" },
|
||||||
lastSeenAppVersion: "1.2.3",
|
|
||||||
}),
|
}),
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
"lens-cluster-store.json": JSON.stringify({
|
||||||
clusters: [
|
clusters: [
|
||||||
@ -127,17 +122,16 @@ describe("user store tests", () => {
|
|||||||
|
|
||||||
di.override(storeMigrationVersionInjectable, () => "10.0.0");
|
di.override(storeMigrationVersionInjectable, () => "10.0.0");
|
||||||
|
|
||||||
userStore = di.inject(userStoreInjectable);
|
|
||||||
userStore.load();
|
userStore.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets last seen app version to 0.0.0", () => {
|
it("skips clusters for adding to kube-sync with files under extension_data/", () => {
|
||||||
expect(userStore.lastSeenAppVersion).toBe("0.0.0");
|
|
||||||
});
|
|
||||||
|
|
||||||
it.only("skips clusters for adding to kube-sync with files under extension_data/", () => {
|
|
||||||
expect(userStore.syncKubeconfigEntries.has("some-directory-for-user-data/extension_data/foo/bar")).toBe(false);
|
expect(userStore.syncKubeconfigEntries.has("some-directory-for-user-data/extension_data/foo/bar")).toBe(false);
|
||||||
expect(userStore.syncKubeconfigEntries.has("some/other/path")).toBe(true);
|
expect(userStore.syncKubeconfigEntries.has("some/other/path")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("allows access to the colorTheme preference", () => {
|
||||||
|
expect(userStore.colorTheme).toBe("light");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,11 +2,6 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
|
||||||
import type { PathName } from "./app-path-names";
|
import type { PathName } from "./app-path-names";
|
||||||
|
|
||||||
export type AppPaths = Record<PathName, string>;
|
export type AppPaths = Record<PathName, string>;
|
||||||
|
|
||||||
export const appPathsInjectionToken = getInjectionToken<AppPaths>({ id: "app-paths-token" });
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,13 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { appPathsInjectionToken } from "./app-path-injection-token";
|
|
||||||
import appPathsStateInjectable from "./app-paths-state.injectable";
|
import appPathsStateInjectable from "./app-paths-state.injectable";
|
||||||
|
|
||||||
const appPathsInjectable = getInjectable({
|
const appPathsInjectable = getInjectable({
|
||||||
id: "app-paths",
|
id: "app-paths",
|
||||||
instantiate: (di) => di.inject(appPathsStateInjectable).get(),
|
instantiate: (di) => di.inject(appPathsStateInjectable).get(),
|
||||||
injectionToken: appPathsInjectionToken,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default appPathsInjectable;
|
export default appPathsInjectable;
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { AppPaths } from "./app-path-injection-token";
|
import type { AppPaths } from "./app-path-injection-token";
|
||||||
import { appPathsInjectionToken } from "./app-path-injection-token";
|
|
||||||
import getElectronAppPathInjectable from "../../main/app-paths/get-electron-app-path/get-electron-app-path.injectable";
|
import getElectronAppPathInjectable from "../../main/app-paths/get-electron-app-path/get-electron-app-path.injectable";
|
||||||
import type { PathName } from "./app-path-names";
|
import type { PathName } from "./app-path-names";
|
||||||
import setElectronAppPathInjectable from "../../main/app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
import setElectronAppPathInjectable from "../../main/app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
||||||
@ -11,6 +10,7 @@ import directoryForIntegrationTestingInjectable from "../../main/app-paths/direc
|
|||||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import appPathsInjectable from "./app-paths.injectable";
|
||||||
|
|
||||||
describe("app-paths", () => {
|
describe("app-paths", () => {
|
||||||
let builder: ApplicationBuilder;
|
let builder: ApplicationBuilder;
|
||||||
@ -68,7 +68,7 @@ describe("app-paths", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("given in renderer, when injecting app paths, returns application specific app paths", () => {
|
it("given in renderer, when injecting app paths, returns application specific app paths", () => {
|
||||||
const actual = windowDi.inject(appPathsInjectionToken);
|
const actual = windowDi.inject(appPathsInjectable);
|
||||||
|
|
||||||
expect(actual).toEqual({
|
expect(actual).toEqual({
|
||||||
currentApp: "some-current-app",
|
currentApp: "some-current-app",
|
||||||
@ -92,7 +92,7 @@ describe("app-paths", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("given in main, when injecting app paths, returns application specific app paths", () => {
|
it("given in main, when injecting app paths, returns application specific app paths", () => {
|
||||||
const actual = mainDi.inject(appPathsInjectionToken);
|
const actual = mainDi.inject(appPathsInjectable);
|
||||||
|
|
||||||
expect(actual).toEqual({
|
expect(actual).toEqual({
|
||||||
currentApp: "some-current-app",
|
currentApp: "some-current-app",
|
||||||
@ -133,7 +133,7 @@ describe("app-paths", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("given in renderer, when injecting path for app data, has integration specific app data path", () => {
|
it("given in renderer, when injecting path for app data, has integration specific app data path", () => {
|
||||||
const { appData, userData } = windowDi.inject(appPathsInjectionToken);
|
const { appData, userData } = windowDi.inject(appPathsInjectable);
|
||||||
|
|
||||||
expect({ appData, userData }).toEqual({
|
expect({ appData, userData }).toEqual({
|
||||||
appData: "some-integration-testing-app-data",
|
appData: "some-integration-testing-app-data",
|
||||||
@ -142,7 +142,7 @@ describe("app-paths", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("given in main, when injecting path for app data, has integration specific app data path", () => {
|
it("given in main, when injecting path for app data, has integration specific app data path", () => {
|
||||||
const { appData, userData } = windowDi.inject(appPathsInjectionToken);
|
const { appData, userData } = windowDi.inject(appPathsInjectable);
|
||||||
|
|
||||||
expect({ appData, userData }).toEqual({
|
expect({ appData, userData }).toEqual({
|
||||||
appData: "some-integration-testing-app-data",
|
appData: "some-integration-testing-app-data",
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { appPathsInjectionToken } from "../app-path-injection-token";
|
import appPathsInjectable from "../app-paths.injectable";
|
||||||
|
|
||||||
const directoryForDownloadsInjectable = getInjectable({
|
const directoryForDownloadsInjectable = getInjectable({
|
||||||
id: "directory-for-downloads",
|
id: "directory-for-downloads",
|
||||||
instantiate: (di) => di.inject(appPathsInjectionToken).downloads,
|
instantiate: (di) => di.inject(appPathsInjectable).downloads,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default directoryForDownloadsInjectable;
|
export default directoryForDownloadsInjectable;
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { appPathsInjectionToken } from "../app-path-injection-token";
|
import appPathsInjectable from "../app-paths.injectable";
|
||||||
|
|
||||||
const directoryForExesInjectable = getInjectable({
|
const directoryForExesInjectable = getInjectable({
|
||||||
id: "directory-for-exes",
|
id: "directory-for-exes",
|
||||||
instantiate: (di) => di.inject(appPathsInjectionToken).exe,
|
instantiate: (di) => di.inject(appPathsInjectable).exe,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default directoryForExesInjectable;
|
export default directoryForExesInjectable;
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { appPathsInjectionToken } from "../app-path-injection-token";
|
import appPathsInjectable from "../app-paths.injectable";
|
||||||
|
|
||||||
const directoryForTempInjectable = getInjectable({
|
const directoryForTempInjectable = getInjectable({
|
||||||
id: "directory-for-temp",
|
id: "directory-for-temp",
|
||||||
instantiate: (di) => di.inject(appPathsInjectionToken).temp,
|
instantiate: (di) => di.inject(appPathsInjectable).temp,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default directoryForTempInjectable;
|
export default directoryForTempInjectable;
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { appPathsInjectionToken } from "../app-path-injection-token";
|
import appPathsInjectable from "../app-paths.injectable";
|
||||||
|
|
||||||
const directoryForUserDataInjectable = getInjectable({
|
const directoryForUserDataInjectable = getInjectable({
|
||||||
id: "directory-for-user-data",
|
id: "directory-for-user-data",
|
||||||
instantiate: (di) => di.inject(appPathsInjectionToken).userData,
|
instantiate: (di) => di.inject(appPathsInjectable).userData,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default directoryForUserDataInjectable;
|
export default directoryForUserDataInjectable;
|
||||||
|
|||||||
@ -1,194 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import path from "path";
|
|
||||||
import type Config from "conf";
|
|
||||||
import type { Options as ConfOptions } from "conf/dist/source/types";
|
|
||||||
import { ipcMain, ipcRenderer } from "electron";
|
|
||||||
import type { IEqualsComparer } from "mobx";
|
|
||||||
import { makeObservable, reaction, runInAction } from "mobx";
|
|
||||||
import type { Disposer } from "./utils";
|
|
||||||
import { Singleton, toJS } from "./utils";
|
|
||||||
import logger from "../main/logger";
|
|
||||||
import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc";
|
|
||||||
import isEqual from "lodash/isEqual";
|
|
||||||
import { isTestEnv } from "./vars";
|
|
||||||
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 storeMigrationVersionInjectable from "./vars/store-migration-version.injectable";
|
|
||||||
|
|
||||||
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
|
||||||
syncOptions?: {
|
|
||||||
fireImmediately?: boolean;
|
|
||||||
equals?: IEqualsComparer<T>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: T should only contain base JSON serializable types.
|
|
||||||
*/
|
|
||||||
export abstract class BaseStore<T extends object> extends Singleton {
|
|
||||||
protected storeConfig?: Config<T>;
|
|
||||||
protected syncDisposers: Disposer[] = [];
|
|
||||||
|
|
||||||
readonly displayName: string = this.constructor.name;
|
|
||||||
|
|
||||||
protected constructor(protected params: BaseStoreParams<T>) {
|
|
||||||
super();
|
|
||||||
makeObservable(this);
|
|
||||||
|
|
||||||
if (ipcRenderer) {
|
|
||||||
params.migrations = undefined; // don't run migrations on renderer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This must be called after the last child's constructor is finished (or just before it finishes)
|
|
||||||
*/
|
|
||||||
load() {
|
|
||||||
if (!isTestEnv) {
|
|
||||||
logger.info(`[${kebabCase(this.displayName).toUpperCase()}]: LOADING from ${this.path} ...`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const di = getLegacyGlobalDiForExtensionApi();
|
|
||||||
|
|
||||||
const getConfigurationFileModel = di.inject(getConfigurationFileModelInjectable);
|
|
||||||
|
|
||||||
this.storeConfig = getConfigurationFileModel({
|
|
||||||
projectName: "lens",
|
|
||||||
projectVersion: di.inject(storeMigrationVersionInjectable),
|
|
||||||
cwd: this.cwd(),
|
|
||||||
...this.params,
|
|
||||||
});
|
|
||||||
|
|
||||||
const res: any = this.fromStore(this.storeConfig.store);
|
|
||||||
|
|
||||||
if (res instanceof Promise || (typeof res === "object" && res && typeof res.then === "function")) {
|
|
||||||
console.error(`${this.displayName} extends BaseStore<T>'s fromStore method returns a Promise or promise-like object. This is an error and must be fixed.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.enableSync();
|
|
||||||
|
|
||||||
if (!isTestEnv) {
|
|
||||||
logger.info(`[${kebabCase(this.displayName).toUpperCase()}]: LOADED from ${this.path}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get name() {
|
|
||||||
return path.basename(this.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get syncRendererChannel() {
|
|
||||||
return `store-sync-renderer:${this.path}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get syncMainChannel() {
|
|
||||||
return `store-sync-main:${this.path}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get path() {
|
|
||||||
return this.storeConfig?.path || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected cwd() {
|
|
||||||
const di = getLegacyGlobalDiForExtensionApi();
|
|
||||||
|
|
||||||
return di.inject(directoryForUserDataInjectable);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected saveToFile(model: T) {
|
|
||||||
logger.info(`[STORE]: SAVING ${this.path}`);
|
|
||||||
|
|
||||||
// todo: update when fixed https://github.com/sindresorhus/conf/issues/114
|
|
||||||
if (this.storeConfig) {
|
|
||||||
for (const [key, value] of Object.entries(model)) {
|
|
||||||
this.storeConfig.set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enableSync() {
|
|
||||||
this.syncDisposers.push(
|
|
||||||
reaction(
|
|
||||||
() => toJS(this.toJSON()), // unwrap possible observables and react to everything
|
|
||||||
model => this.onModelChange(model),
|
|
||||||
this.params.syncOptions,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ipcMain) {
|
|
||||||
this.syncDisposers.push(ipcMainOn(this.syncMainChannel, (event, model: T) => {
|
|
||||||
logger.silly(`[STORE]: SYNC ${this.name} from renderer`, { model });
|
|
||||||
this.onSync(model);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ipcRenderer) {
|
|
||||||
this.syncDisposers.push(ipcRendererOn(this.syncRendererChannel, (event, model: T) => {
|
|
||||||
logger.silly(`[STORE]: SYNC ${this.name} from main`, { model });
|
|
||||||
this.onSyncFromMain(model);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onSyncFromMain(model: T) {
|
|
||||||
this.applyWithoutSync(() => {
|
|
||||||
this.onSync(model);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
unregisterIpcListener() {
|
|
||||||
ipcRenderer?.removeAllListeners(this.syncMainChannel);
|
|
||||||
ipcRenderer?.removeAllListeners(this.syncRendererChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
disableSync() {
|
|
||||||
this.syncDisposers.forEach(dispose => dispose());
|
|
||||||
this.syncDisposers.length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected applyWithoutSync(callback: () => void) {
|
|
||||||
this.disableSync();
|
|
||||||
runInAction(callback);
|
|
||||||
this.enableSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onSync(model: T) {
|
|
||||||
// todo: use "resourceVersion" if merge required (to avoid equality checks => better performance)
|
|
||||||
if (!isEqual(this.toJSON(), model)) {
|
|
||||||
this.fromStore(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onModelChange(model: T) {
|
|
||||||
if (ipcMain) {
|
|
||||||
this.saveToFile(model); // save config file
|
|
||||||
broadcastMessage(this.syncRendererChannel, model);
|
|
||||||
} else {
|
|
||||||
broadcastMessage(this.syncMainChannel, model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fromStore is called internally when a child class syncs with the file
|
|
||||||
* system.
|
|
||||||
*
|
|
||||||
* Note: This function **must** be synchronous.
|
|
||||||
*
|
|
||||||
* @param data the parsed information read from the stored JSON file
|
|
||||||
*/
|
|
||||||
protected abstract fromStore(data: T): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* toJSON is called when syncing the store to the filesystem. It should
|
|
||||||
* produce a JSON serializable object representation of the current state.
|
|
||||||
*
|
|
||||||
* It is recommended that a round trip is valid. Namely, calling
|
|
||||||
* `this.fromStore(this.toJSON())` shouldn't change the state.
|
|
||||||
*/
|
|
||||||
abstract toJSON(): T;
|
|
||||||
}
|
|
||||||
148
src/common/base-store/base-store.ts
Normal file
148
src/common/base-store/base-store.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type Config from "conf";
|
||||||
|
import type { Migrations, Options as ConfOptions } from "conf/dist/source/types";
|
||||||
|
import type { IEqualsComparer } from "mobx";
|
||||||
|
import { makeObservable, reaction } from "mobx";
|
||||||
|
import { disposer, isPromiseLike, toJS } from "../utils";
|
||||||
|
import { broadcastMessage } from "../ipc";
|
||||||
|
import isEqual from "lodash/isEqual";
|
||||||
|
import { kebabCase } from "lodash";
|
||||||
|
import type { GetConfigurationFileModel } from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
|
import type { Logger } from "../logger";
|
||||||
|
import type { PersistStateToConfig } from "./save-to-file";
|
||||||
|
import type { GetBasenameOfPath } from "../path/get-basename.injectable";
|
||||||
|
import type { EnlistMessageChannelListener } from "../utils/channel/enlist-message-channel-listener-injection-token";
|
||||||
|
|
||||||
|
export interface BaseStoreParams<T> extends Omit<ConfOptions<T>, "migrations"> {
|
||||||
|
syncOptions?: {
|
||||||
|
fireImmediately?: boolean;
|
||||||
|
equals?: IEqualsComparer<T>;
|
||||||
|
};
|
||||||
|
configName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IpcChannelPrefixes {
|
||||||
|
local: string;
|
||||||
|
remote: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaseStoreDependencies {
|
||||||
|
readonly logger: Logger;
|
||||||
|
readonly storeMigrationVersion: string;
|
||||||
|
readonly directoryForUserData: string;
|
||||||
|
readonly migrations: Migrations<Record<string, unknown>>;
|
||||||
|
readonly ipcChannelPrefixes: IpcChannelPrefixes;
|
||||||
|
readonly shouldDisableSyncInListener: boolean;
|
||||||
|
getConfigurationFileModel: GetConfigurationFileModel;
|
||||||
|
persistStateToConfig: PersistStateToConfig;
|
||||||
|
getBasenameOfPath: GetBasenameOfPath;
|
||||||
|
enlistMessageChannelListener: EnlistMessageChannelListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: T should only contain base JSON serializable types.
|
||||||
|
*/
|
||||||
|
export abstract class BaseStore<T extends object> {
|
||||||
|
private readonly syncDisposers = disposer();
|
||||||
|
|
||||||
|
readonly displayName = kebabCase(this.params.configName).toUpperCase();
|
||||||
|
|
||||||
|
protected constructor(
|
||||||
|
protected readonly dependencies: BaseStoreDependencies,
|
||||||
|
protected readonly params: BaseStoreParams<T>,
|
||||||
|
) {
|
||||||
|
makeObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This must be called after the last child's constructor is finished (or just before it finishes)
|
||||||
|
*/
|
||||||
|
load() {
|
||||||
|
this.dependencies.logger.info(`[${this.displayName}]: LOADING ...`);
|
||||||
|
|
||||||
|
const config = this.dependencies.getConfigurationFileModel({
|
||||||
|
projectName: "lens",
|
||||||
|
projectVersion: this.dependencies.storeMigrationVersion,
|
||||||
|
cwd: this.cwd(),
|
||||||
|
...this.params,
|
||||||
|
migrations: this.dependencies.migrations as Migrations<T>,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = this.fromStore(config.store);
|
||||||
|
|
||||||
|
if (isPromiseLike(res)) {
|
||||||
|
this.dependencies.logger.error(`${this.displayName} extends BaseStore<T>'s fromStore method returns a Promise or promise-like object. This is an error and must be fixed.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startSyncing(config);
|
||||||
|
this.dependencies.logger.info(`[${this.displayName}]: LOADED from ${config.path}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected cwd() {
|
||||||
|
return this.dependencies.directoryForUserData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private startSyncing(config: Config<T>) {
|
||||||
|
const name = this.dependencies.getBasenameOfPath(config.path);
|
||||||
|
|
||||||
|
const disableSync = () => this.syncDisposers();
|
||||||
|
const enableSync = () => {
|
||||||
|
this.syncDisposers.push(
|
||||||
|
reaction(
|
||||||
|
() => toJS(this.toJSON()), // unwrap possible observables and react to everything
|
||||||
|
model => {
|
||||||
|
this.dependencies.persistStateToConfig(config, model);
|
||||||
|
broadcastMessage(`${this.dependencies.ipcChannelPrefixes.remote}:${config.path}`, model);
|
||||||
|
},
|
||||||
|
this.params.syncOptions,
|
||||||
|
),
|
||||||
|
this.dependencies.enlistMessageChannelListener({
|
||||||
|
channel: {
|
||||||
|
id: `${this.dependencies.ipcChannelPrefixes.local}:${config.path}`,
|
||||||
|
},
|
||||||
|
handler: (model) => {
|
||||||
|
this.dependencies.logger.silly(`[${this.displayName}]: syncing ${name}`, { model });
|
||||||
|
|
||||||
|
if (this.dependencies.shouldDisableSyncInListener) {
|
||||||
|
disableSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: use "resourceVersion" if merge required (to avoid equality checks => better performance)
|
||||||
|
if (!isEqual(this.toJSON(), model)) {
|
||||||
|
this.fromStore(model as T);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.dependencies.shouldDisableSyncInListener) {
|
||||||
|
enableSync();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
enableSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fromStore is called internally when a child class syncs with the file
|
||||||
|
* system.
|
||||||
|
*
|
||||||
|
* Note: This function **must** be synchronous.
|
||||||
|
*
|
||||||
|
* @param data the parsed information read from the stored JSON file
|
||||||
|
*/
|
||||||
|
protected abstract fromStore(data: T): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toJSON is called when syncing the store to the filesystem. It should
|
||||||
|
* produce a JSON serializable object representation of the current state.
|
||||||
|
*
|
||||||
|
* It is recommended that a round trip is valid. Namely, calling
|
||||||
|
* `this.fromStore(this.toJSON())` shouldn't change the state.
|
||||||
|
*/
|
||||||
|
abstract toJSON(): T;
|
||||||
|
}
|
||||||
11
src/common/base-store/channel-prefix.ts
Normal file
11
src/common/base-store/channel-prefix.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
import type { IpcChannelPrefixes } from "./base-store";
|
||||||
|
|
||||||
|
export const baseStoreIpcChannelPrefixesInjectionToken = getInjectionToken<IpcChannelPrefixes>({
|
||||||
|
id: "base-store-ipc-channel-prefix-token",
|
||||||
|
});
|
||||||
@ -2,9 +2,9 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
|
||||||
import type { Runnable } from "../../common/runnable/run-many-for";
|
|
||||||
|
|
||||||
export const beforeFrameStartsInjectionToken = getInjectionToken<Runnable>({
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
id: "before-frame-starts",
|
|
||||||
|
export const shouldBaseStoreDisableSyncInIpcListenerInjectionToken = getInjectionToken<boolean>({
|
||||||
|
id: "should-base-store-disable-sync-in-ipc-listener-token",
|
||||||
});
|
});
|
||||||
46
src/common/base-store/migrations.injectable.ts
Normal file
46
src/common/base-store/migrations.injectable.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { InjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import { lifecycleEnum, getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type Conf from "conf/dist/source";
|
||||||
|
import type { Migrations } from "conf/dist/source/types";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import { getOrInsert, iter } from "../utils";
|
||||||
|
|
||||||
|
export interface MigrationDeclaration {
|
||||||
|
version: string;
|
||||||
|
run(store: Conf<Partial<Record<string, unknown>>>): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const storeMigrationsInjectable = getInjectable({
|
||||||
|
id: "store-migrations",
|
||||||
|
instantiate: (di, token): Migrations<Record<string, unknown>> => {
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
const declarations = di.injectMany(token);
|
||||||
|
const migrations = new Map<string, MigrationDeclaration["run"][]>();
|
||||||
|
|
||||||
|
for (const decl of declarations) {
|
||||||
|
getOrInsert(migrations, decl.version, []).push(decl.run);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
iter.map(
|
||||||
|
migrations,
|
||||||
|
([v, fns]) => [v, (store) => {
|
||||||
|
logger.info(`Running ${v} migration for ${store.path}`);
|
||||||
|
|
||||||
|
for (const fn of fns) {
|
||||||
|
fn(store);
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
lifecycle: lifecycleEnum.keyedSingleton({
|
||||||
|
getInstanceKey: (di, token: InjectionToken<MigrationDeclaration, void>) => token.id,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default storeMigrationsInjectable;
|
||||||
12
src/common/base-store/save-to-file.ts
Normal file
12
src/common/base-store/save-to-file.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import type Config from "conf";
|
||||||
|
|
||||||
|
export type PersistStateToConfig = <T extends object>(config: Config<T>, state: T) => void;
|
||||||
|
|
||||||
|
export const persistStateToConfigInjectionToken = getInjectionToken<PersistStateToConfig>({
|
||||||
|
id: "persist-state-to-config-token",
|
||||||
|
});
|
||||||
@ -5,13 +5,14 @@
|
|||||||
|
|
||||||
import type { CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus, CatalogCategorySpec } from "../catalog";
|
import type { CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus, CatalogCategorySpec } from "../catalog";
|
||||||
import { CatalogEntity, CatalogCategory, categoryVersion } from "../catalog/catalog-entity";
|
import { CatalogEntity, CatalogCategory, categoryVersion } from "../catalog/catalog-entity";
|
||||||
import { ClusterStore } from "../cluster-store/cluster-store";
|
|
||||||
import { broadcastMessage } from "../ipc";
|
import { broadcastMessage } from "../ipc";
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import type { CatalogEntityConstructor, CatalogEntitySpec } from "../catalog/catalog-entity";
|
import type { CatalogEntityConstructor, CatalogEntitySpec } from "../catalog/catalog-entity";
|
||||||
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
||||||
import { requestClusterActivation, requestClusterDisconnection } from "../../renderer/ipc";
|
import { requestClusterActivation, requestClusterDisconnection } from "../../renderer/ipc";
|
||||||
import KubeClusterCategoryIcon from "./icons/kubernetes.svg";
|
import KubeClusterCategoryIcon from "./icons/kubernetes.svg";
|
||||||
|
import { asLegacyGlobalFunctionForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||||
|
import getClusterByIdInjectable from "../cluster-store/get-by-id.injectable";
|
||||||
|
|
||||||
export interface KubernetesClusterPrometheusMetrics {
|
export interface KubernetesClusterPrometheusMetrics {
|
||||||
address?: {
|
address?: {
|
||||||
@ -63,6 +64,8 @@ export function isKubernetesCluster(item: unknown): item is KubernetesCluster {
|
|||||||
return item instanceof KubernetesCluster;
|
return item instanceof KubernetesCluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getClusterById = asLegacyGlobalFunctionForExtensionApi(getClusterByIdInjectable);
|
||||||
|
|
||||||
export class KubernetesCluster<
|
export class KubernetesCluster<
|
||||||
Metadata extends KubernetesClusterMetadata = KubernetesClusterMetadata,
|
Metadata extends KubernetesClusterMetadata = KubernetesClusterMetadata,
|
||||||
Status extends KubernetesClusterStatus = KubernetesClusterStatus,
|
Status extends KubernetesClusterStatus = KubernetesClusterStatus,
|
||||||
@ -76,7 +79,7 @@ export class KubernetesCluster<
|
|||||||
|
|
||||||
async connect(): Promise<void> {
|
async connect(): Promise<void> {
|
||||||
if (app) {
|
if (app) {
|
||||||
await ClusterStore.getInstance().getById(this.getId())?.activate();
|
await getClusterById(this.getId())?.activate();
|
||||||
} else {
|
} else {
|
||||||
await requestClusterActivation(this.getId(), false);
|
await requestClusterActivation(this.getId(), false);
|
||||||
}
|
}
|
||||||
@ -84,7 +87,7 @@ export class KubernetesCluster<
|
|||||||
|
|
||||||
async disconnect(): Promise<void> {
|
async disconnect(): Promise<void> {
|
||||||
if (app) {
|
if (app) {
|
||||||
ClusterStore.getInstance().getById(this.getId())?.disconnect();
|
getClusterById(this.getId())?.disconnect();
|
||||||
} else {
|
} else {
|
||||||
await requestClusterDisconnection(this.getId(), false);
|
await requestClusterDisconnection(this.getId(), false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } fro
|
|||||||
import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
||||||
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity";
|
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity";
|
||||||
import productNameInjectable from "../vars/product-name.injectable";
|
import productNameInjectable from "../vars/product-name.injectable";
|
||||||
import weblinkStoreInjectable from "../weblink-store.injectable";
|
import weblinkStoreInjectable from "../weblinks-store/weblink-store.injectable";
|
||||||
|
|
||||||
export type WebLinkStatusPhase = "available" | "unavailable";
|
export type WebLinkStatusPhase = "available" | "unavailable";
|
||||||
|
|
||||||
@ -34,12 +34,13 @@ export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus,
|
|||||||
// NOTE: this is safe because `onContextMenuOpen` is only supposed to be called in the renderer
|
// NOTE: this is safe because `onContextMenuOpen` is only supposed to be called in the renderer
|
||||||
const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(Environments.renderer);
|
const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(Environments.renderer);
|
||||||
const productName = di.inject(productNameInjectable);
|
const productName = di.inject(productNameInjectable);
|
||||||
|
const weblinkStore = di.inject(weblinkStoreInjectable);
|
||||||
|
|
||||||
if (this.metadata.source === "local") {
|
if (this.metadata.source === "local") {
|
||||||
context.menuItems.push({
|
context.menuItems.push({
|
||||||
title: "Delete",
|
title: "Delete",
|
||||||
icon: "delete",
|
icon: "delete",
|
||||||
onClick: async () => di.inject(weblinkStoreInjectable).removeById(this.getId()),
|
onClick: async () => weblinkStore.removeById(this.getId()),
|
||||||
confirm: {
|
confirm: {
|
||||||
message: `Remove Web Link "${this.getName()}" from ${productName}?`,
|
message: `Remove Web Link "${this.getName()}" from ${productName}?`,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
|
||||||
import catalogCategoryRegistryInjectable from "./category-registry.injectable";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use `di.inject(catalogCategoryRegistryInjectable)` instead
|
|
||||||
*/
|
|
||||||
export const catalogCategoryRegistry = asLegacyGlobalForExtensionApi(catalogCategoryRegistryInjectable);
|
|
||||||
@ -3,6 +3,5 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./catalog-category-registry";
|
|
||||||
export * from "./category-registry";
|
export * from "./category-registry";
|
||||||
export * from "./catalog-entity";
|
export * from "./catalog-entity";
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 clusterStoreInjectable from "./cluster-store.injectable";
|
|
||||||
import type { Cluster } from "../cluster/cluster";
|
|
||||||
import type { ClusterStore } from "./cluster-store";
|
|
||||||
|
|
||||||
export default getGlobalOverride(
|
|
||||||
clusterStoreInjectable,
|
|
||||||
() =>
|
|
||||||
({
|
|
||||||
provideInitialFromMain: () => {},
|
|
||||||
getById: (id) => (void id, {}) as Cluster,
|
|
||||||
} as ClusterStore),
|
|
||||||
);
|
|
||||||
@ -7,21 +7,36 @@ import { ClusterStore } from "./cluster-store";
|
|||||||
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
||||||
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
|
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
|
||||||
import emitAppEventInjectable from "../app-event-bus/emit-event.injectable";
|
import emitAppEventInjectable from "../app-event-bus/emit-event.injectable";
|
||||||
|
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 loggerInjectable from "../logger.injectable";
|
||||||
|
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||||
|
import storeMigrationsInjectable from "../base-store/migrations.injectable";
|
||||||
|
import { clusterStoreMigrationInjectionToken } from "./migration-token";
|
||||||
|
import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix";
|
||||||
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
||||||
|
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
||||||
|
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
||||||
|
import { enlistMessageChannelListenerInjectionToken } from "../utils/channel/enlist-message-channel-listener-injection-token";
|
||||||
|
|
||||||
const clusterStoreInjectable = getInjectable({
|
const clusterStoreInjectable = getInjectable({
|
||||||
id: "cluster-store",
|
id: "cluster-store",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => new ClusterStore({
|
||||||
ClusterStore.resetInstance();
|
|
||||||
|
|
||||||
return ClusterStore.createInstance({
|
|
||||||
createCluster: di.inject(createClusterInjectionToken),
|
createCluster: di.inject(createClusterInjectionToken),
|
||||||
readClusterConfigSync: di.inject(readClusterConfigSyncInjectable),
|
readClusterConfigSync: di.inject(readClusterConfigSyncInjectable),
|
||||||
emitAppEvent: di.inject(emitAppEventInjectable),
|
emitAppEvent: di.inject(emitAppEventInjectable),
|
||||||
});
|
directoryForUserData: di.inject(directoryForUserDataInjectable),
|
||||||
},
|
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
causesSideEffects: true,
|
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
||||||
|
migrations: di.inject(storeMigrationsInjectable, clusterStoreMigrationInjectionToken),
|
||||||
|
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||||
|
ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken),
|
||||||
|
persistStateToConfig: di.inject(persistStateToConfigInjectionToken),
|
||||||
|
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
||||||
|
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default clusterStoreInjectable;
|
export default clusterStoreInjectable;
|
||||||
|
|||||||
@ -4,17 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { ipcMain, ipcRenderer, webFrame } from "electron";
|
import { action, comparer, computed, makeObservable, observable } from "mobx";
|
||||||
import { action, comparer, computed, makeObservable, observable, reaction } from "mobx";
|
import type { BaseStoreDependencies } from "../base-store/base-store";
|
||||||
import { BaseStore } from "../base-store";
|
import { BaseStore } from "../base-store/base-store";
|
||||||
import { Cluster } from "../cluster/cluster";
|
import { Cluster } from "../cluster/cluster";
|
||||||
import migrations from "../../migrations/cluster-store";
|
import { toJS } from "../utils";
|
||||||
import logger from "../../main/logger";
|
import type { ClusterModel, ClusterId } from "../cluster-types";
|
||||||
import { ipcMainHandle } from "../ipc";
|
|
||||||
import { disposer, toJS } from "../utils";
|
|
||||||
import type { ClusterModel, ClusterId, ClusterState } from "../cluster-types";
|
|
||||||
import { requestInitialClusterStates } from "../../renderer/ipc";
|
|
||||||
import { clusterStates } from "../ipc/cluster";
|
|
||||||
import type { CreateCluster } from "../cluster/create-cluster-injection-token";
|
import type { CreateCluster } from "../cluster/create-cluster-injection-token";
|
||||||
import type { ReadClusterConfigSync } from "./read-cluster-config.injectable";
|
import type { ReadClusterConfigSync } from "./read-cluster-config.injectable";
|
||||||
import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable";
|
import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable";
|
||||||
@ -23,76 +18,25 @@ export interface ClusterStoreModel {
|
|||||||
clusters?: ClusterModel[];
|
clusters?: ClusterModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies extends BaseStoreDependencies {
|
||||||
createCluster: CreateCluster;
|
createCluster: CreateCluster;
|
||||||
readClusterConfigSync: ReadClusterConfigSync;
|
readClusterConfigSync: ReadClusterConfigSync;
|
||||||
emitAppEvent: EmitAppEvent;
|
emitAppEvent: EmitAppEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||||
readonly displayName = "ClusterStore";
|
readonly clusters = observable.map<ClusterId, Cluster>();
|
||||||
clusters = observable.map<ClusterId, Cluster>();
|
|
||||||
|
|
||||||
protected disposer = disposer();
|
constructor(protected readonly dependencies: Dependencies) {
|
||||||
|
super(dependencies, {
|
||||||
constructor(private readonly dependencies: Dependencies) {
|
|
||||||
super({
|
|
||||||
configName: "lens-cluster-store",
|
configName: "lens-cluster-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
syncOptions: {
|
syncOptions: {
|
||||||
equals: comparer.structural,
|
equals: comparer.structural,
|
||||||
},
|
},
|
||||||
migrations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
this.load();
|
|
||||||
this.pushStateToViewsAutomatically();
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadInitialOnRenderer() {
|
|
||||||
logger.info("[CLUSTER-STORE] requesting initial state sync");
|
|
||||||
|
|
||||||
for (const { id, state } of await requestInitialClusterStates()) {
|
|
||||||
this.getById(id)?.setState(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
provideInitialFromMain() {
|
|
||||||
ipcMainHandle(clusterStates, () => (
|
|
||||||
this.clustersList.map(cluster => ({
|
|
||||||
id: cluster.id,
|
|
||||||
state: cluster.getState(),
|
|
||||||
}))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected pushStateToViewsAutomatically() {
|
|
||||||
if (ipcMain) {
|
|
||||||
this.disposer.push(
|
|
||||||
reaction(() => this.connectedClustersList, () => this.pushState()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerIpcListener() {
|
|
||||||
logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`);
|
|
||||||
const ipc = ipcMain ?? ipcRenderer;
|
|
||||||
|
|
||||||
ipc?.on("cluster:state", (event, clusterId: ClusterId, state: ClusterState) => {
|
|
||||||
this.getById(clusterId)?.setState(state);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
unregisterIpcListener() {
|
|
||||||
super.unregisterIpcListener();
|
|
||||||
this.disposer();
|
|
||||||
}
|
|
||||||
|
|
||||||
pushState() {
|
|
||||||
this.clusters.forEach((c) => {
|
|
||||||
c.pushState();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get clustersList(): Cluster[] {
|
@computed get clustersList(): Cluster[] {
|
||||||
@ -150,7 +94,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
}
|
}
|
||||||
newClusters.set(clusterModel.id, cluster);
|
newClusters.set(clusterModel.id, cluster);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn(`[CLUSTER-STORE]: Failed to update/create a cluster: ${error}`);
|
this.dependencies.logger.warn(`[CLUSTER-STORE]: Failed to update/create a cluster: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
src/common/cluster-store/migration-token.ts
Normal file
11
src/common/cluster-store/migration-token.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
import type { MigrationDeclaration } from "../base-store/migrations.injectable";
|
||||||
|
|
||||||
|
export const clusterStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
|
||||||
|
id: "cluster-store-migration",
|
||||||
|
});
|
||||||
@ -316,7 +316,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
const refreshMetadataTimer = setInterval(() => this.available && this.refreshAccessibilityAndMetadata(), 900000); // every 15 minutes
|
const refreshMetadataTimer = setInterval(() => this.available && this.refreshAccessibilityAndMetadata(), 900000); // every 15 minutes
|
||||||
|
|
||||||
this.eventsDisposer.push(
|
this.eventsDisposer.push(
|
||||||
reaction(() => this.getState(), state => this.pushState(state)),
|
|
||||||
reaction(
|
reaction(
|
||||||
() => this.prometheusPreferences,
|
() => this.prometheusPreferences,
|
||||||
prefs => this.contextHandler.setupPrometheus(prefs),
|
prefs => this.contextHandler.setupPrometheus(prefs),
|
||||||
@ -349,7 +348,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
@action
|
@action
|
||||||
async activate(force = false) {
|
async activate(force = false) {
|
||||||
if (this.activated && !force) {
|
if (this.activated && !force) {
|
||||||
return this.pushState();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dependencies.logger.info(`[CLUSTER]: activate`, this.getMeta());
|
this.dependencies.logger.info(`[CLUSTER]: activate`, this.getMeta());
|
||||||
@ -395,7 +394,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.activated = true;
|
this.activated = true;
|
||||||
this.pushState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -437,7 +435,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
this.activated = false;
|
this.activated = false;
|
||||||
this.allowedNamespaces = [];
|
this.allowedNamespaces = [];
|
||||||
this.resourceAccessStatuses.clear();
|
this.resourceAccessStatuses.clear();
|
||||||
this.pushState();
|
|
||||||
this.dependencies.logger.info(`[CLUSTER]: disconnected`, { id: this.id });
|
this.dependencies.logger.info(`[CLUSTER]: disconnected`, { id: this.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,7 +445,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
async refresh() {
|
async refresh() {
|
||||||
this.dependencies.logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
this.dependencies.logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
||||||
await this.refreshConnectionStatus();
|
await this.refreshConnectionStatus();
|
||||||
this.pushState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -614,16 +610,15 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
* @param state cluster state
|
* @param state cluster state
|
||||||
*/
|
*/
|
||||||
@action setState(state: ClusterState) {
|
@action setState(state: ClusterState) {
|
||||||
Object.assign(this, state);
|
this.accessible = state.accessible;
|
||||||
}
|
this.allowedNamespaces = state.allowedNamespaces;
|
||||||
|
this.allowedResources = state.allowedResources;
|
||||||
/**
|
this.apiUrl = state.apiUrl;
|
||||||
* @internal
|
this.disconnected = state.disconnected;
|
||||||
* @param state cluster state
|
this.isAdmin = state.isAdmin;
|
||||||
*/
|
this.isGlobalWatchEnabled = state.isGlobalWatchEnabled;
|
||||||
pushState(state = this.getState()) {
|
this.online = state.online;
|
||||||
this.dependencies.logger.silly(`[CLUSTER]: push-state`, state);
|
this.ready = state.ready;
|
||||||
this.dependencies.broadcastMessage("cluster:state", this.id, state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get cluster system meta, e.g. use in "logger"
|
// get cluster system meta, e.g. use in "logger"
|
||||||
|
|||||||
11
src/common/cluster/current-cluster-channel.ts
Normal file
11
src/common/cluster/current-cluster-channel.ts
Normal file
@ -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 currentClusterMessageChannel: MessageChannel<ClusterId> = {
|
||||||
|
id: "current-visible-cluster",
|
||||||
|
};
|
||||||
@ -3,7 +3,9 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { HttpsProxyAgent } from "hpagent";
|
||||||
import type * as FetchModule from "node-fetch";
|
import type * as FetchModule from "node-fetch";
|
||||||
|
import userStoreInjectable from "../user-store/user-store.injectable";
|
||||||
|
|
||||||
const { NodeFetch: { default: fetch }} = require("../../../build/webpack/node-fetch.bundle") as { NodeFetch: typeof FetchModule };
|
const { NodeFetch: { default: fetch }} = require("../../../build/webpack/node-fetch.bundle") as { NodeFetch: typeof FetchModule };
|
||||||
|
|
||||||
@ -14,7 +16,20 @@ export type Fetch = (url: string, init?: RequestInit) => Promise<Response>;
|
|||||||
|
|
||||||
const fetchInjectable = getInjectable({
|
const fetchInjectable = getInjectable({
|
||||||
id: "fetch",
|
id: "fetch",
|
||||||
instantiate: (): Fetch => fetch,
|
instantiate: (di): Fetch => {
|
||||||
|
const { httpsProxy, allowUntrustedCAs } = di.inject(userStoreInjectable);
|
||||||
|
const agent = httpsProxy
|
||||||
|
? new HttpsProxyAgent({
|
||||||
|
proxy: httpsProxy,
|
||||||
|
rejectUnauthorized: !allowUntrustedCAs,
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (url, init = {}) => fetch(url, {
|
||||||
|
agent,
|
||||||
|
...init,
|
||||||
|
});
|
||||||
|
},
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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,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 execFileInjectable from "./exec-file.injectable";
|
||||||
|
|
||||||
|
export default getGlobalOverrideForFunction(execFileInjectable);
|
||||||
@ -3,11 +3,61 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ReadOptions } from "fs-extra";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: Add corrisponding a corrisponding override of this injecable in `src/test-utils/override-fs-with-fakes.ts`
|
||||||
|
*/
|
||||||
const fsInjectable = getInjectable({
|
const fsInjectable = getInjectable({
|
||||||
id: "fs",
|
id: "fs",
|
||||||
instantiate: () => fse,
|
instantiate: () => {
|
||||||
|
const {
|
||||||
|
promises: {
|
||||||
|
readFile,
|
||||||
|
writeFile,
|
||||||
|
readdir,
|
||||||
|
lstat,
|
||||||
|
rm,
|
||||||
|
access,
|
||||||
|
stat,
|
||||||
|
},
|
||||||
|
ensureDir,
|
||||||
|
ensureDirSync,
|
||||||
|
readFileSync,
|
||||||
|
readJson,
|
||||||
|
writeJson,
|
||||||
|
readJsonSync,
|
||||||
|
writeFileSync,
|
||||||
|
writeJsonSync,
|
||||||
|
pathExistsSync,
|
||||||
|
pathExists,
|
||||||
|
copy,
|
||||||
|
createReadStream,
|
||||||
|
} = fse;
|
||||||
|
|
||||||
|
return {
|
||||||
|
readFile,
|
||||||
|
readJson: readJson as (file: string, options?: ReadOptions | BufferEncoding) => Promise<any>,
|
||||||
|
writeFile,
|
||||||
|
writeJson,
|
||||||
|
pathExists,
|
||||||
|
readdir,
|
||||||
|
readFileSync,
|
||||||
|
readJsonSync,
|
||||||
|
writeFileSync,
|
||||||
|
writeJsonSync,
|
||||||
|
pathExistsSync,
|
||||||
|
lstat,
|
||||||
|
rm,
|
||||||
|
access,
|
||||||
|
copy: copy as (src: string, dest: string, options?: fse.CopyOptions) => Promise<void>,
|
||||||
|
ensureDir: ensureDir as (path: string, options?: number | fse.EnsureOptions ) => Promise<void>,
|
||||||
|
ensureDirSync,
|
||||||
|
createReadStream,
|
||||||
|
stat,
|
||||||
|
};
|
||||||
|
},
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +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 { 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;
|
|
||||||
@ -5,11 +5,9 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import fsInjectable from "./fs.injectable";
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
export type DeleteFile = (filePath: string) => Promise<void>;
|
const pathExistsSyncInjectable = getInjectable({
|
||||||
|
id: "path-exists-sync",
|
||||||
const deleteFileInjectable = getInjectable({
|
instantiate: (di) => di.inject(fsInjectable).pathExistsSync,
|
||||||
id: "delete-file",
|
|
||||||
instantiate: (di): DeleteFile => di.inject(fsInjectable).unlink,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default deleteFileInjectable;
|
export default pathExistsSyncInjectable;
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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");
|
|
||||||
});
|
|
||||||
@ -14,18 +14,16 @@ export interface ReadDirectory {
|
|||||||
(
|
(
|
||||||
path: string,
|
path: string,
|
||||||
options?:
|
options?:
|
||||||
| { encoding: BufferEncoding | string | null; withFileTypes?: false | undefined }
|
| { encoding: BufferEncoding; withFileTypes?: false | undefined }
|
||||||
| BufferEncoding
|
| BufferEncoding
|
||||||
| string
|
|
||||||
| null,
|
|
||||||
): Promise<string[]>;
|
): Promise<string[]>;
|
||||||
(
|
(
|
||||||
path: string,
|
path: string,
|
||||||
options?: { encoding?: BufferEncoding | string | null | undefined; withFileTypes?: false | undefined },
|
options?: { encoding?: BufferEncoding; withFileTypes?: false | undefined },
|
||||||
): Promise<string[] | Buffer[]>;
|
): Promise<string[] | Buffer[]>;
|
||||||
(
|
(
|
||||||
path: string,
|
path: string,
|
||||||
options: { encoding?: BufferEncoding | string | null | undefined; withFileTypes: true },
|
options: { encoding?: BufferEncoding; withFileTypes: true },
|
||||||
): Promise<Dirent[]>;
|
): Promise<Dirent[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
src/common/fs/read-file-buffer-sync.injectable.ts
Normal file
19
src/common/fs/read-file-buffer-sync.injectable.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
|
export type ReadFileBufferSync = (filePath: string) => Buffer;
|
||||||
|
|
||||||
|
const readFileBufferSyncInjectable = getInjectable({
|
||||||
|
id: "read-file-buffer-sync",
|
||||||
|
instantiate: (di): ReadFileBufferSync => {
|
||||||
|
const { readFileSync } = di.inject(fsInjectable);
|
||||||
|
|
||||||
|
return (filePath) => readFileSync(filePath);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default readFileBufferSyncInjectable;
|
||||||
13
src/common/fs/read-json-sync.injectable.ts
Normal file
13
src/common/fs/read-json-sync.injectable.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
|
const readJsonSyncInjectable = getInjectable({
|
||||||
|
id: "read-json-sync",
|
||||||
|
instantiate: (di) => di.inject(fsInjectable).readJsonSync,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default readJsonSyncInjectable;
|
||||||
@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||||
import removePathInjectable from "./remove-path.injectable";
|
import removePathInjectable from "./remove.injectable";
|
||||||
|
|
||||||
export default getGlobalOverride(removePathInjectable, () => async () => {
|
export default getGlobalOverride(removePathInjectable, () => async () => {
|
||||||
throw new Error("tried to remove a path without override");
|
throw new Error("tried to remove path without override");
|
||||||
});
|
});
|
||||||
@ -5,11 +5,15 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import fsInjectable from "./fs.injectable";
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
export type RemovePath = (path: string) => Promise<void>;
|
export type RemovePath = (filePath: string) => Promise<void>;
|
||||||
|
|
||||||
const removePathInjectable = getInjectable({
|
const removePathInjectable = getInjectable({
|
||||||
id: "remove-path",
|
id: "remove-path",
|
||||||
instantiate: (di): RemovePath => di.inject(fsInjectable).remove,
|
instantiate: (di): RemovePath => {
|
||||||
|
const { rm } = di.inject(fsInjectable);
|
||||||
|
|
||||||
|
return (filePath) => rm(filePath, { force: true });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default removePathInjectable;
|
export default removePathInjectable;
|
||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { Stats } from "fs";
|
import type { Stats } from "fs";
|
||||||
import fsInjectable from "../fs.injectable";
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
export type Stat = (path: string) => Promise<Stats>;
|
export type Stat = (path: string) => Promise<Stats>;
|
||||||
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import statInjectable from "./stat.injectable";
|
|
||||||
import { getGlobalOverride } from "../../test-utils/get-global-override";
|
|
||||||
|
|
||||||
export default getGlobalOverride(statInjectable, () => () => {
|
|
||||||
throw new Error("Tried to call stat without explicit override");
|
|
||||||
});
|
|
||||||
@ -7,7 +7,7 @@ import type { AsyncResult } from "../utils/async-result";
|
|||||||
import { isErrnoException } from "../utils";
|
import { isErrnoException } from "../utils";
|
||||||
import type { Stats } from "fs-extra";
|
import type { Stats } from "fs-extra";
|
||||||
import { lowerFirst } from "lodash/fp";
|
import { lowerFirst } from "lodash/fp";
|
||||||
import statInjectable from "./stat/stat.injectable";
|
import statInjectable from "./stat.injectable";
|
||||||
|
|
||||||
export type ValidateDirectory = (path: string) => Promise<AsyncResult<undefined>>;
|
export type ValidateDirectory = (path: string) => Promise<AsyncResult<undefined>>;
|
||||||
|
|
||||||
|
|||||||
29
src/common/fs/write-file-sync.injectable.ts
Normal file
29
src/common/fs/write-file-sync.injectable.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 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 getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||||
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
|
export type WriteFileSync = (filePath: string, contents: string) => void;
|
||||||
|
|
||||||
|
const writeFileSyncInjectable = getInjectable({
|
||||||
|
id: "write-file-sync",
|
||||||
|
instantiate: (di): WriteFileSync => {
|
||||||
|
const {
|
||||||
|
writeFileSync,
|
||||||
|
ensureDirSync,
|
||||||
|
} = di.inject(fsInjectable);
|
||||||
|
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||||
|
|
||||||
|
return (filePath, contents) => {
|
||||||
|
ensureDirSync(getDirnameOfPath(filePath), {
|
||||||
|
mode: 0o755,
|
||||||
|
});
|
||||||
|
writeFileSync(filePath, contents);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default writeFileSyncInjectable;
|
||||||
@ -16,15 +16,17 @@ const writeFileInjectable = getInjectable({
|
|||||||
const { writeFile, ensureDir } = di.inject(fsInjectable);
|
const { writeFile, ensureDir } = di.inject(fsInjectable);
|
||||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||||
|
|
||||||
return async (filePath, content, opts) => {
|
return async (filePath, content, opts = {}) => {
|
||||||
await ensureDir(getDirnameOfPath(filePath), {
|
await ensureDir(getDirnameOfPath(filePath), {
|
||||||
mode: 0o755,
|
mode: 0o755,
|
||||||
...(opts ?? {}),
|
...opts,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { encoding = "utf-8", ...options } = opts;
|
||||||
|
|
||||||
await writeFile(filePath, content, {
|
await writeFile(filePath, content, {
|
||||||
encoding: "utf-8",
|
encoding: encoding as BufferEncoding,
|
||||||
...(opts ?? {}),
|
...options,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,11 +3,10 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { JsonValue } from "type-fest";
|
|
||||||
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||||
import fsInjectable from "./fs.injectable";
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
export type WriteJson = (filePath: string, contents: JsonValue) => Promise<void>;
|
export type WriteJson = (filePath: string, contents: unknown) => Promise<void>;
|
||||||
|
|
||||||
const writeJsonFileInjectable = getInjectable({
|
const writeJsonFileInjectable = getInjectable({
|
||||||
id: "write-json-file",
|
id: "write-json-file",
|
||||||
|
|||||||
31
src/common/fs/write-json-sync.injectable.ts
Normal file
31
src/common/fs/write-json-sync.injectable.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* 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 getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||||
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
|
export type WriteJsonSync = (filePath: string, contents: unknown) => void;
|
||||||
|
|
||||||
|
const writeJsonSyncInjectable = getInjectable({
|
||||||
|
id: "write-json-sync",
|
||||||
|
instantiate: (di): WriteJsonSync => {
|
||||||
|
const {
|
||||||
|
writeJsonSync,
|
||||||
|
ensureDirSync,
|
||||||
|
} = di.inject(fsInjectable);
|
||||||
|
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||||
|
|
||||||
|
return (filePath, content) => {
|
||||||
|
ensureDirSync(getDirnameOfPath(filePath), { mode: 0o755 });
|
||||||
|
|
||||||
|
writeJsonSync(filePath, content, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
spaces: 2,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default writeJsonSyncInjectable;
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import assert from "assert";
|
||||||
|
import path from "path";
|
||||||
|
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||||
|
import getConfigurationFileModelInjectable from "./get-configuration-file-model.injectable";
|
||||||
|
import type Config from "conf";
|
||||||
|
import readJsonSyncInjectable from "../fs/read-json-sync.injectable";
|
||||||
|
import writeJsonSyncInjectable from "../fs/write-json-sync.injectable";
|
||||||
|
|
||||||
|
export default getGlobalOverride(getConfigurationFileModelInjectable, (di) => {
|
||||||
|
const readJsonSync = di.inject(readJsonSyncInjectable);
|
||||||
|
const writeJsonSync = di.inject(writeJsonSyncInjectable);
|
||||||
|
|
||||||
|
return (options) => {
|
||||||
|
assert(options.cwd, "Missing options.cwd");
|
||||||
|
assert(options.configName, "Missing options.configName");
|
||||||
|
|
||||||
|
const configFilePath = path.posix.join(options.cwd, `${options.configName}.json`);
|
||||||
|
let store: object = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
store = readJsonSync(configFilePath);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
get store() {
|
||||||
|
return store;
|
||||||
|
},
|
||||||
|
path: configFilePath,
|
||||||
|
set: (key: string, value: unknown) => {
|
||||||
|
let currentState: object;
|
||||||
|
|
||||||
|
try {
|
||||||
|
currentState = readJsonSync(configFilePath);
|
||||||
|
} catch {
|
||||||
|
currentState = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJsonSync(configFilePath, {
|
||||||
|
...currentState,
|
||||||
|
[key]: value,
|
||||||
|
});
|
||||||
|
store = readJsonSync(configFilePath);
|
||||||
|
},
|
||||||
|
} as Partial<Config> as Config<any>;
|
||||||
|
};
|
||||||
|
});
|
||||||
@ -4,11 +4,13 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import Config from "conf";
|
import Config from "conf";
|
||||||
import type { BaseStoreParams } from "../base-store";
|
import type { Options as ConfOptions } from "conf/dist/source/types";
|
||||||
|
|
||||||
|
export type GetConfigurationFileModel = <T extends object>(content: ConfOptions<T>) => Config<T>;
|
||||||
|
|
||||||
const getConfigurationFileModelInjectable = getInjectable({
|
const getConfigurationFileModelInjectable = getInjectable({
|
||||||
id: "get-configuration-file-model",
|
id: "get-configuration-file-model",
|
||||||
instantiate: () => <T extends object>(content: BaseStoreParams<T>) => new Config(content),
|
instantiate: (): GetConfigurationFileModel => (content) => new Config(content),
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -2,15 +2,15 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
||||||
export type HelmRepo = {
|
export interface HelmRepo {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
cacheFilePath?: string;
|
cacheFilePath: string;
|
||||||
caFile?: string;
|
caFile?: string;
|
||||||
certFile?: string;
|
certFile?: string;
|
||||||
insecureSkipTlsVerify?: boolean;
|
insecureSkipTlsVerify?: boolean;
|
||||||
keyFile?: string;
|
keyFile?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
};
|
}
|
||||||
|
|||||||
11
src/common/hotbars/migrations-token.ts
Normal file
11
src/common/hotbars/migrations-token.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
import type { MigrationDeclaration } from "../base-store/migrations.injectable";
|
||||||
|
|
||||||
|
export const hotbarStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
|
||||||
|
id: "hotbar-store-migration-token",
|
||||||
|
});
|
||||||
@ -6,20 +6,33 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
||||||
import { HotbarStore } from "./store";
|
import { HotbarStore } from "./store";
|
||||||
import loggerInjectable from "../logger.injectable";
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
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 storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||||
|
import storeMigrationsInjectable from "../base-store/migrations.injectable";
|
||||||
|
import { hotbarStoreMigrationInjectionToken } from "./migrations-token";
|
||||||
|
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
||||||
|
import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix";
|
||||||
|
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
||||||
|
import { enlistMessageChannelListenerInjectionToken } from "../utils/channel/enlist-message-channel-listener-injection-token";
|
||||||
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
||||||
|
|
||||||
const hotbarStoreInjectable = getInjectable({
|
const hotbarStoreInjectable = getInjectable({
|
||||||
id: "hotbar-store",
|
id: "hotbar-store",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => new HotbarStore({
|
||||||
HotbarStore.resetInstance();
|
|
||||||
|
|
||||||
return HotbarStore.createInstance({
|
|
||||||
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
|
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
|
||||||
logger: di.inject(loggerInjectable),
|
logger: di.inject(loggerInjectable),
|
||||||
});
|
directoryForUserData: di.inject(directoryForUserDataInjectable),
|
||||||
},
|
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
||||||
|
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
||||||
causesSideEffects: true,
|
migrations: di.inject(storeMigrationsInjectable, hotbarStoreMigrationInjectionToken),
|
||||||
|
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||||
|
ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken),
|
||||||
|
persistStateToConfig: di.inject(persistStateToConfigInjectionToken),
|
||||||
|
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
||||||
|
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default hotbarStoreInjectable;
|
export default hotbarStoreInjectable;
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { action, comparer, observable, makeObservable, computed } from "mobx";
|
import { action, comparer, observable, makeObservable, computed } from "mobx";
|
||||||
import { BaseStore } from "../base-store";
|
import type { BaseStoreDependencies } from "../base-store/base-store";
|
||||||
import migrations from "../../migrations/hotbar-store";
|
import { BaseStore } from "../base-store/base-store";
|
||||||
import { toJS } from "../utils";
|
import { toJS } from "../utils";
|
||||||
import type { CatalogEntity } from "../catalog";
|
import type { CatalogEntity } from "../catalog";
|
||||||
import { broadcastMessage } from "../ipc";
|
import { broadcastMessage } from "../ipc";
|
||||||
@ -21,26 +21,23 @@ export interface HotbarStoreModel {
|
|||||||
activeHotbarId: string;
|
activeHotbarId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies extends BaseStoreDependencies {
|
||||||
readonly catalogCatalogEntity: GeneralEntity;
|
readonly catalogCatalogEntity: GeneralEntity;
|
||||||
readonly logger: Logger;
|
readonly logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||||
readonly displayName = "HotbarStore";
|
|
||||||
@observable hotbars: Hotbar[] = [];
|
@observable hotbars: Hotbar[] = [];
|
||||||
@observable private _activeHotbarId!: string;
|
@observable private _activeHotbarId!: string;
|
||||||
|
|
||||||
constructor(private readonly dependencies: Dependencies) {
|
constructor(protected readonly dependencies: Dependencies) {
|
||||||
super({
|
super(dependencies, {
|
||||||
configName: "lens-hotbar-store",
|
configName: "lens-hotbar-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
syncOptions: {
|
syncOptions: {
|
||||||
equals: comparer.structural,
|
equals: comparer.structural,
|
||||||
},
|
},
|
||||||
migrations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,21 +96,19 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
this.hotbars.forEach(ensureExactHotbarItemLength);
|
this.hotbars.forEach(ensureExactHotbarItemLength);
|
||||||
|
|
||||||
if (data.activeHotbarId) {
|
if (data.activeHotbarId) {
|
||||||
this.setActiveHotbar(data.activeHotbarId);
|
this._activeHotbarId = data.activeHotbarId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.activeHotbarId) {
|
if (!this._activeHotbarId) {
|
||||||
this.setActiveHotbar(0);
|
this._activeHotbarId = this.hotbars[0].id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): HotbarStoreModel {
|
toJSON(): HotbarStoreModel {
|
||||||
const model: HotbarStoreModel = {
|
return toJS({
|
||||||
hotbars: this.hotbars,
|
hotbars: this.hotbars,
|
||||||
activeHotbarId: this.activeHotbarId,
|
activeHotbarId: this.activeHotbarId,
|
||||||
};
|
});
|
||||||
|
|
||||||
return toJS(model);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getActive(): Hotbar {
|
getActive(): Hotbar {
|
||||||
@ -148,7 +143,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
const index = this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
const index = this.hotbars.findIndex((hotbar) => hotbar.id === id);
|
||||||
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
return void console.warn(
|
return this.dependencies.logger.warn(
|
||||||
`[HOTBAR-STORE]: cannot setHotbarName: unknown id`,
|
`[HOTBAR-STORE]: cannot setHotbarName: unknown id`,
|
||||||
{ id },
|
{ id },
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const openFilePickingDialogChannel = "dialog:open:file-picking";
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
export const setNativeThemeChannel = "theme:set-native-theme";
|
|
||||||
export const getNativeThemeChannel = "theme:get-native-theme";
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import request from "request";
|
|
||||||
import requestPromise from "request-promise-native";
|
|
||||||
import { UserStore } from "./user-store";
|
|
||||||
|
|
||||||
// todo: get rid of "request" (deprecated)
|
|
||||||
// https://github.com/lensapp/lens/issues/459
|
|
||||||
|
|
||||||
function getDefaultRequestOpts(): Partial<request.Options> {
|
|
||||||
const { httpsProxy, allowUntrustedCAs } = UserStore.getInstance();
|
|
||||||
|
|
||||||
return {
|
|
||||||
proxy: httpsProxy || undefined,
|
|
||||||
rejectUnauthorized: !allowUntrustedCAs,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export function customRequest(opts: request.Options) {
|
|
||||||
return request.defaults(getDefaultRequestOpts())(opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export function customRequestPromise(opts: requestPromise.Options) {
|
|
||||||
return requestPromise.defaults(getDefaultRequestOpts())(opts);
|
|
||||||
}
|
|
||||||
@ -8,6 +8,8 @@ import { createContainer, getInjectable, getInjectionToken } from "@ogre-tools/i
|
|||||||
import type { Runnable } from "./run-many-for";
|
import type { Runnable } from "./run-many-for";
|
||||||
import { runManyFor } from "./run-many-for";
|
import { runManyFor } from "./run-many-for";
|
||||||
import { getPromiseStatus } from "../test-utils/get-promise-status";
|
import { getPromiseStatus } from "../test-utils/get-promise-status";
|
||||||
|
import { runInAction } from "mobx";
|
||||||
|
import { flushPromises } from "../test-utils/flush-promises";
|
||||||
|
|
||||||
describe("runManyFor", () => {
|
describe("runManyFor", () => {
|
||||||
describe("given no hierarchy, when running many", () => {
|
describe("given no hierarchy, when running many", () => {
|
||||||
@ -223,7 +225,68 @@ describe("runManyFor", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return expect(() => runMany()).rejects.toThrow(
|
return expect(() => runMany()).rejects.toThrow(
|
||||||
/Tried to get a composite but encountered missing parent ids: "some-runnable-2".\n\nAvailable parent ids are:\n"[0-9a-z-]+",\n"some-runnable-1"/,
|
/Runnable "some-runnable-1" is unreachable for injection token "some-injection-token": run afters "some-runnable-2" are a part of different injection tokens./,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given partially incorrect hierarchy, when running runnables, throws", () => {
|
||||||
|
const rootDi = createContainer("irrelevant");
|
||||||
|
|
||||||
|
const runMock = asyncFn<(...args: unknown[]) => void>();
|
||||||
|
|
||||||
|
const someInjectionToken = getInjectionToken<Runnable>({
|
||||||
|
id: "some-injection-token",
|
||||||
|
});
|
||||||
|
|
||||||
|
const someOtherInjectionToken = getInjectionToken<Runnable>({
|
||||||
|
id: "some-other-injection-token",
|
||||||
|
});
|
||||||
|
|
||||||
|
const someInjectable = getInjectable({
|
||||||
|
id: "some-runnable-1",
|
||||||
|
|
||||||
|
instantiate: (di) => ({
|
||||||
|
id: "some-runnable-1",
|
||||||
|
run: () => runMock("some-runnable-1"),
|
||||||
|
runAfter: [
|
||||||
|
di.inject(someOtherInjectable),
|
||||||
|
di.inject(someSecondInjectable),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: someInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const someSecondInjectable = getInjectable({
|
||||||
|
id: "some-runnable-2",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
id: "some-runnable-2",
|
||||||
|
run: () => runMock("some-runnable-2"),
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: someInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const someOtherInjectable = getInjectable({
|
||||||
|
id: "some-runnable-3",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
id: "some-runnable-3",
|
||||||
|
run: () => runMock("some-runnable-3"),
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: someOtherInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
rootDi.register(someInjectable, someOtherInjectable, someSecondInjectable);
|
||||||
|
|
||||||
|
const runMany = runManyFor(rootDi)(
|
||||||
|
someInjectionToken,
|
||||||
|
);
|
||||||
|
|
||||||
|
return expect(() => runMany()).rejects.toThrow(
|
||||||
|
/Runnable "some-runnable-3" is not part of the injection token "some-injection-token"/,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -279,4 +342,319 @@ describe("runManyFor", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("given multiple runAfters", () => {
|
||||||
|
let runMock: AsyncFnMock<(...args: unknown[]) => void>;
|
||||||
|
let finishingPromise: Promise<void>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const rootDi = createContainer("irrelevant");
|
||||||
|
|
||||||
|
runMock = asyncFn<(...args: unknown[]) => void>();
|
||||||
|
|
||||||
|
const someInjectionToken = getInjectionToken<Runnable>({
|
||||||
|
id: "some-injection-token",
|
||||||
|
});
|
||||||
|
|
||||||
|
const runnableOneInjectable = getInjectable({
|
||||||
|
id: "runnable-1",
|
||||||
|
instantiate: () => ({
|
||||||
|
id: "runnable-1",
|
||||||
|
run: () => runMock("runnable-1"),
|
||||||
|
}),
|
||||||
|
injectionToken: someInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const runnableTwoInjectable = getInjectable({
|
||||||
|
id: "runnable-2",
|
||||||
|
instantiate: () => ({
|
||||||
|
id: "runnable-2",
|
||||||
|
run: () => runMock("runnable-2"),
|
||||||
|
runAfter: [], // shouldn't block being called
|
||||||
|
}),
|
||||||
|
injectionToken: someInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const runnableThreeInjectable = getInjectable({
|
||||||
|
id: "runnable-3",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
id: "runnable-3",
|
||||||
|
run: () => runMock("runnable-3"),
|
||||||
|
runAfter: di.inject(runnableOneInjectable),
|
||||||
|
}),
|
||||||
|
injectionToken: someInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const runnableFourInjectable = getInjectable({
|
||||||
|
id: "runnable-4",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
id: "runnable-4",
|
||||||
|
run: () => runMock("runnable-4"),
|
||||||
|
runAfter: [di.inject(runnableThreeInjectable)], // should be the same as an single item
|
||||||
|
}),
|
||||||
|
injectionToken: someInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const runnableFiveInjectable = getInjectable({
|
||||||
|
id: "runnable-5",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
id: "runnable-5",
|
||||||
|
run: () => runMock("runnable-5"),
|
||||||
|
runAfter: di.inject(runnableThreeInjectable),
|
||||||
|
}),
|
||||||
|
injectionToken: someInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const runnableSixInjectable = getInjectable({
|
||||||
|
id: "runnable-6",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
id: "runnable-6",
|
||||||
|
run: () => runMock("runnable-6"),
|
||||||
|
runAfter: [
|
||||||
|
di.inject(runnableFourInjectable),
|
||||||
|
di.inject(runnableFiveInjectable),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
injectionToken: someInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const runnableSevenInjectable = getInjectable({
|
||||||
|
id: "runnable-7",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
id: "runnable-7",
|
||||||
|
run: () => runMock("runnable-7"),
|
||||||
|
runAfter: [
|
||||||
|
di.inject(runnableFiveInjectable),
|
||||||
|
di.inject(runnableSixInjectable),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
injectionToken: someInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
rootDi.register(
|
||||||
|
runnableOneInjectable,
|
||||||
|
runnableTwoInjectable,
|
||||||
|
runnableThreeInjectable,
|
||||||
|
runnableFourInjectable,
|
||||||
|
runnableFiveInjectable,
|
||||||
|
runnableSixInjectable,
|
||||||
|
runnableSevenInjectable,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const runMany = runManyFor(rootDi);
|
||||||
|
const runSome = runMany(someInjectionToken);
|
||||||
|
|
||||||
|
finishingPromise = runSome();
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run 'runnable-1'", () => {
|
||||||
|
expect(runMock).toBeCalledWith("runnable-1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run 'runnable-2'", () => {
|
||||||
|
expect(runMock).toBeCalledWith("runnable-2");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not run 'runnable-3'", () => {
|
||||||
|
expect(runMock).not.toBeCalledWith("runnable-3");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-1' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run 'runnable-3'", () => {
|
||||||
|
expect(runMock).toBeCalledWith("runnable-3");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-2' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't call any more runnables", () => {
|
||||||
|
expect(runMock).toBeCalledTimes(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-3' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-3"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run 'runnable-4'", () => {
|
||||||
|
expect(runMock).toBeCalledWith("runnable-4");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run 'runnable-5'", () => {
|
||||||
|
expect(runMock).toBeCalledWith("runnable-5");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-2' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't call any more runnables", () => {
|
||||||
|
expect(runMock).toBeCalledTimes(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-4' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-4"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't call any more runnables", () => {
|
||||||
|
expect(runMock).toBeCalledTimes(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-2' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't call any more runnables", () => {
|
||||||
|
expect(runMock).toBeCalledTimes(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-5' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-5"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run 'runnable-6'", () => {
|
||||||
|
expect(runMock).toBeCalledWith("runnable-6");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-2' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't call any more runnables", () => {
|
||||||
|
expect(runMock).toBeCalledTimes(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-6' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-6"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run 'runnable-7'", () => {
|
||||||
|
expect(runMock).toBeCalledWith("runnable-7");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-2' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't call any more runnables", () => {
|
||||||
|
expect(runMock).toBeCalledTimes(7);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-7' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-7"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve the runMany promise call", async () => {
|
||||||
|
await finishingPromise;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-5' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-5"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't call any more runnables", () => {
|
||||||
|
expect(runMock).toBeCalledTimes(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-2' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't call any more runnables", () => {
|
||||||
|
expect(runMock).toBeCalledTimes(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-4' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-4"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run 'runnable-6'", () => {
|
||||||
|
expect(runMock).toBeCalledWith("runnable-6");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-2' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't call any more runnables", () => {
|
||||||
|
expect(runMock).toBeCalledTimes(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-6' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-6"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run 'runnable-7'", () => {
|
||||||
|
expect(runMock).toBeCalledWith("runnable-7");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-2' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't call any more runnables", () => {
|
||||||
|
expect(runMock).toBeCalledTimes(7);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-7' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-7"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve the runMany promise call", async () => {
|
||||||
|
await finishingPromise;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when 'runnable-2' resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runMock.resolveSpecific(["runnable-2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't call any more runnables", () => {
|
||||||
|
expect(runMock).toBeCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,46 +3,138 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { DiContainerForInjection, InjectionToken } from "@ogre-tools/injectable";
|
import type { DiContainerForInjection, InjectionToken } from "@ogre-tools/injectable";
|
||||||
import type { Composite } from "../utils/composite/get-composite/get-composite";
|
import type { SingleOrMany } from "../utils";
|
||||||
import { getCompositeFor } from "../utils/composite/get-composite/get-composite";
|
import { getOrInsert, getOrInsertSetFor, isDefined } from "../utils";
|
||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
|
import assert from "assert";
|
||||||
|
import type { Asyncify } from "type-fest";
|
||||||
|
import type TypedEventEmitter from "typed-emitter";
|
||||||
|
import EventEmitter from "events";
|
||||||
|
|
||||||
export interface Runnable<TParameter = void> {
|
export interface Runnable<TParameter = void> {
|
||||||
id: string;
|
id: string;
|
||||||
run: Run<TParameter>;
|
run: Run<TParameter>;
|
||||||
runAfter?: Runnable<TParameter>;
|
runAfter?: SingleOrMany<Runnable<TParameter>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Run<Param> = (parameter: Param) => Promise<void> | void;
|
type Run<Param> = (parameter: Param) => Promise<void> | void;
|
||||||
|
|
||||||
export type RunMany = <Param>(injectionToken: InjectionToken<Runnable<Param>, void>) => Run<Param>;
|
export type RunMany = <Param>(injectionToken: InjectionToken<Runnable<Param>, void>) => Asyncify<Run<Param>>;
|
||||||
|
|
||||||
async function runCompositeRunnables<Param>(param: Param, composite: Composite<Runnable<Param>>) {
|
const computedNextEdge = (traversed: string[], graph: Map<string, Set<string>>, currentId: string, seenIds: Set<string>) => {
|
||||||
await composite.value.run(param);
|
seenIds.add(currentId);
|
||||||
await Promise.all(composite.children.map(composite => runCompositeRunnables(param, composite)));
|
const currentNode = graph.get(currentId);
|
||||||
|
|
||||||
|
assert(currentNode, `Runnable graph does not contain node with id="${currentId}"`);
|
||||||
|
|
||||||
|
for (const nextId of currentNode.values()) {
|
||||||
|
if (traversed.includes(nextId)) {
|
||||||
|
throw new Error(`Cycle in runnable graph: "${traversed.join(`" -> "`)}" -> "${nextId}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
computedNextEdge([...traversed, nextId], graph, nextId, seenIds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyRunnablesAreDAG = <Param>(injectionToken: InjectionToken<Runnable<Param>, void>, runnables: Runnable<Param>[]) => {
|
||||||
|
const rootId = uuid.v4();
|
||||||
|
const runnableGraph = new Map<string, Set<string>>();
|
||||||
|
const seenIds = new Set<string>();
|
||||||
|
const addRunnableId = getOrInsertSetFor(runnableGraph);
|
||||||
|
|
||||||
|
// Build the Directed graph
|
||||||
|
for (const runnable of runnables) {
|
||||||
|
addRunnableId(runnable.id);
|
||||||
|
|
||||||
|
if (!runnable.runAfter || (Array.isArray(runnable.runAfter) && runnable.runAfter.length === 0)) {
|
||||||
|
addRunnableId(rootId).add(runnable.id);
|
||||||
|
} else if (Array.isArray(runnable.runAfter)) {
|
||||||
|
for (const parentRunnable of runnable.runAfter) {
|
||||||
|
addRunnableId(parentRunnable.id).add(runnable.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addRunnableId(runnable.runAfter.id).add(runnable.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addRunnableId(rootId);
|
||||||
|
|
||||||
|
// Do a DFS to find any cycles
|
||||||
|
computedNextEdge([], runnableGraph, rootId, seenIds);
|
||||||
|
|
||||||
|
for (const id of runnableGraph.keys()) {
|
||||||
|
if (!seenIds.has(id)) {
|
||||||
|
const runnable = runnables.find(runnable => runnable.id === id);
|
||||||
|
|
||||||
|
if (!runnable) {
|
||||||
|
throw new Error(`Runnable "${id}" is not part of the injection token "${injectionToken.id}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const runAfters = [runnable.runAfter]
|
||||||
|
.flat()
|
||||||
|
.filter(isDefined)
|
||||||
|
.map(runnable => runnable.id)
|
||||||
|
.join('", "');
|
||||||
|
|
||||||
|
throw new Error(`Runnable "${id}" is unreachable for injection token "${injectionToken.id}": run afters "${runAfters}" are a part of different injection tokens.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface BarrierEvent {
|
||||||
|
finish: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DynamicBarrier {
|
||||||
|
private readonly finishedIds = new Map<string, Promise<void>>();
|
||||||
|
private readonly events: TypedEventEmitter<BarrierEvent> = new EventEmitter();
|
||||||
|
|
||||||
|
private initFinishingPromise(id: string): Promise<void> {
|
||||||
|
return getOrInsert(this.finishedIds, id, new Promise(resolve => {
|
||||||
|
const handler = (finishedId: string) => {
|
||||||
|
if (finishedId === id) {
|
||||||
|
resolve();
|
||||||
|
this.events.removeListener("finish", handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.events.addListener("finish", handler);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
setFinished(id: string): void {
|
||||||
|
void this.initFinishingPromise(id);
|
||||||
|
|
||||||
|
this.events.emit("finish", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async blockOn(id: string): Promise<void> {
|
||||||
|
await this.initFinishingPromise(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const executeRunnableWith = <Param>(param: Param) => {
|
||||||
|
const barrier = new DynamicBarrier();
|
||||||
|
|
||||||
|
return async (runnable: Runnable<Param>): Promise<void> => {
|
||||||
|
const parentRunnables = [runnable.runAfter].flat().filter(isDefined);
|
||||||
|
|
||||||
|
for (const parentRunnable of parentRunnables) {
|
||||||
|
await barrier.blockOn(parentRunnable.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
await runnable.run(param);
|
||||||
|
barrier.setFinished(runnable.id);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export function runManyFor(di: DiContainerForInjection): RunMany {
|
export function runManyFor(di: DiContainerForInjection): RunMany {
|
||||||
return <Param>(injectionToken: InjectionToken<Runnable<Param>, void>) => async (param: Param) => {
|
return <Param>(injectionToken: InjectionToken<Runnable<Param>, void>) => async (param: Param) => {
|
||||||
|
const executeRunnable = executeRunnableWith(param);
|
||||||
const allRunnables = di.injectMany(injectionToken);
|
const allRunnables = di.injectMany(injectionToken);
|
||||||
const rootId = uuid.v4();
|
|
||||||
const getCompositeRunnables = getCompositeFor<Runnable<Param>>({
|
|
||||||
getId: (runnable) => runnable.id,
|
|
||||||
getParentId: (runnable) => (
|
|
||||||
runnable.id === rootId
|
|
||||||
? undefined
|
|
||||||
: runnable.runAfter?.id ?? rootId
|
|
||||||
),
|
|
||||||
});
|
|
||||||
const composite = getCompositeRunnables([
|
|
||||||
// This is a dummy runnable to conform to the requirements of `getCompositeFor` to only have one root
|
|
||||||
{
|
|
||||||
id: rootId,
|
|
||||||
run: () => {},
|
|
||||||
},
|
|
||||||
...allRunnables,
|
|
||||||
]);
|
|
||||||
|
|
||||||
await runCompositeRunnables(param, composite);
|
verifyRunnablesAreDAG(injectionToken, allRunnables);
|
||||||
|
|
||||||
|
await Promise.all(allRunnables.map(executeRunnable));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||||
import moveInjectable from "./move.injectable";
|
import currentTimezoneInjectable from "./current-timezone.injectable";
|
||||||
|
|
||||||
export default getGlobalOverride(moveInjectable, () => async () => {
|
export default getGlobalOverride(currentTimezoneInjectable, () => "Etc/GMT");
|
||||||
throw new Error("tried to move without override");
|
|
||||||
});
|
|
||||||
14
src/common/user-store/current-timezone.injectable.ts
Normal file
14
src/common/user-store/current-timezone.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import moment from "moment-timezone";
|
||||||
|
|
||||||
|
const currentTimezoneInjectable = getInjectable({
|
||||||
|
id: "current-timezone",
|
||||||
|
instantiate: () => moment.tz.guess(true),
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default currentTimezoneInjectable;
|
||||||
18
src/common/user-store/https-proxy.injectable.ts
Normal file
18
src/common/user-store/https-proxy.injectable.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import userStoreInjectable from "./user-store.injectable";
|
||||||
|
|
||||||
|
const httpsProxyConfigurationInjectable = getInjectable({
|
||||||
|
id: "https-proxy-configuration",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const userStore = di.inject(userStoreInjectable);
|
||||||
|
|
||||||
|
return computed(() => userStore.httpsProxy);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default httpsProxyConfigurationInjectable;
|
||||||
@ -7,11 +7,7 @@ import userStoreInjectable from "./user-store.injectable";
|
|||||||
|
|
||||||
const kubeconfigSyncsInjectable = getInjectable({
|
const kubeconfigSyncsInjectable = getInjectable({
|
||||||
id: "kubeconfig-syncs",
|
id: "kubeconfig-syncs",
|
||||||
instantiate: (di) => {
|
instantiate: (di) => di.inject(userStoreInjectable).syncKubeconfigEntries,
|
||||||
const store = di.inject(userStoreInjectable);
|
|
||||||
|
|
||||||
return store.syncKubeconfigEntries;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default kubeconfigSyncsInjectable;
|
export default kubeconfigSyncsInjectable;
|
||||||
|
|||||||
37
src/common/user-store/lens-color-theme.injectable.ts
Normal file
37
src/common/user-store/lens-color-theme.injectable.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import userStoreInjectable from "./user-store.injectable";
|
||||||
|
|
||||||
|
export type LensColorThemePreference = {
|
||||||
|
useSystemTheme: true;
|
||||||
|
} | {
|
||||||
|
useSystemTheme: false;
|
||||||
|
lensThemeId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const lensColorThemePreferenceInjectable = getInjectable({
|
||||||
|
id: "lens-color-theme-preference",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const userStore = di.inject(userStoreInjectable);
|
||||||
|
|
||||||
|
return computed((): LensColorThemePreference => {
|
||||||
|
// TODO: remove magic strings
|
||||||
|
if (userStore.colorTheme === "system") {
|
||||||
|
return {
|
||||||
|
useSystemTheme: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
useSystemTheme: false,
|
||||||
|
lensThemeId: userStore.colorTheme,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default lensColorThemePreferenceInjectable;
|
||||||
11
src/common/user-store/migrations-token.ts
Normal file
11
src/common/user-store/migrations-token.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
import type { MigrationDeclaration } from "../base-store/migrations.injectable";
|
||||||
|
|
||||||
|
export const userStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
|
||||||
|
id: "user-store-migration-token",
|
||||||
|
});
|
||||||
143
src/common/user-store/preference-descriptors.injectable.ts
Normal file
143
src/common/user-store/preference-descriptors.injectable.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/**
|
||||||
|
* 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 { merge } from "lodash";
|
||||||
|
import type { ObservableMap } from "mobx";
|
||||||
|
import { observable } from "mobx";
|
||||||
|
import homeDirectoryPathInjectable from "../os/home-directory-path.injectable";
|
||||||
|
import joinPathsInjectable from "../path/join-paths.injectable";
|
||||||
|
import { defaultThemeId } from "../vars";
|
||||||
|
import currentTimezoneInjectable from "./current-timezone.injectable";
|
||||||
|
import type { EditorConfiguration, ExtensionRegistry, KubeconfigSyncEntry, KubeconfigSyncValue, TerminalConfig } from "./preferences-helpers";
|
||||||
|
import { defaultExtensionRegistryUrlLocation, defaultEditorConfig, defaultTerminalConfig, defaultPackageMirror, getPreferenceDescriptor, packageMirrors } from "./preferences-helpers";
|
||||||
|
|
||||||
|
export type PreferenceDescriptors = ReturnType<typeof userStorePreferenceDescriptorsInjectable["instantiate"]>;
|
||||||
|
|
||||||
|
const userStorePreferenceDescriptorsInjectable = getInjectable({
|
||||||
|
id: "user-store-preference-descriptors",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const currentTimezone = di.inject(currentTimezoneInjectable);
|
||||||
|
const joinPaths = di.inject(joinPathsInjectable);
|
||||||
|
const homeDirectoryPath = di.inject(homeDirectoryPathInjectable);
|
||||||
|
|
||||||
|
const mainKubeFolderPath = joinPaths(homeDirectoryPath, ".kube");
|
||||||
|
|
||||||
|
return ({
|
||||||
|
httpsProxy: getPreferenceDescriptor<string | undefined>({
|
||||||
|
fromStore: val => val,
|
||||||
|
toStore: val => val || undefined,
|
||||||
|
}),
|
||||||
|
shell: getPreferenceDescriptor<string | undefined>({
|
||||||
|
fromStore: val => val,
|
||||||
|
toStore: val => val || undefined,
|
||||||
|
}),
|
||||||
|
colorTheme: getPreferenceDescriptor<string>({
|
||||||
|
fromStore: val => val || defaultThemeId,
|
||||||
|
toStore: val => !val || val === defaultThemeId
|
||||||
|
? undefined
|
||||||
|
: val,
|
||||||
|
}),
|
||||||
|
terminalTheme: getPreferenceDescriptor<string>({
|
||||||
|
fromStore: val => val || "",
|
||||||
|
toStore: val => val || undefined,
|
||||||
|
}),
|
||||||
|
localeTimezone: getPreferenceDescriptor<string>({
|
||||||
|
fromStore: val => val || currentTimezone,
|
||||||
|
toStore: val => !val || val === currentTimezone
|
||||||
|
? undefined
|
||||||
|
: val,
|
||||||
|
}),
|
||||||
|
allowUntrustedCAs: getPreferenceDescriptor<boolean>({
|
||||||
|
fromStore: val => val ?? false,
|
||||||
|
toStore: val => !val
|
||||||
|
? undefined
|
||||||
|
: val,
|
||||||
|
}),
|
||||||
|
allowErrorReporting: getPreferenceDescriptor<boolean>({
|
||||||
|
fromStore: val => val ?? true,
|
||||||
|
toStore: val => val
|
||||||
|
? undefined
|
||||||
|
: val,
|
||||||
|
}),
|
||||||
|
downloadMirror: getPreferenceDescriptor<string>({
|
||||||
|
fromStore: val => !val || !packageMirrors.has(val)
|
||||||
|
? defaultPackageMirror
|
||||||
|
: val,
|
||||||
|
toStore: val => val === defaultPackageMirror
|
||||||
|
? undefined
|
||||||
|
: val,
|
||||||
|
}),
|
||||||
|
downloadKubectlBinaries: getPreferenceDescriptor<boolean>({
|
||||||
|
fromStore: val => val ?? true,
|
||||||
|
toStore: val => val
|
||||||
|
? undefined
|
||||||
|
: val,
|
||||||
|
}),
|
||||||
|
downloadBinariesPath: getPreferenceDescriptor<string | undefined>({
|
||||||
|
fromStore: val => val,
|
||||||
|
toStore: val => val || undefined,
|
||||||
|
}),
|
||||||
|
kubectlBinariesPath: getPreferenceDescriptor<string | undefined>({
|
||||||
|
fromStore: val => val,
|
||||||
|
toStore: val => val || undefined,
|
||||||
|
}),
|
||||||
|
openAtLogin: getPreferenceDescriptor<boolean>({
|
||||||
|
fromStore: val => val ?? false,
|
||||||
|
toStore: val => !val
|
||||||
|
? undefined
|
||||||
|
: val,
|
||||||
|
}),
|
||||||
|
terminalCopyOnSelect: getPreferenceDescriptor<boolean>({
|
||||||
|
fromStore: val => val ?? false,
|
||||||
|
toStore: val => !val
|
||||||
|
? undefined
|
||||||
|
: val,
|
||||||
|
}),
|
||||||
|
hiddenTableColumns: getPreferenceDescriptor<[string, string[]][], Map<string, Set<string>>>({
|
||||||
|
fromStore: (val = []) => new Map(
|
||||||
|
val.map(([tableId, columnIds]) => [tableId, new Set(columnIds)]),
|
||||||
|
),
|
||||||
|
toStore: (val) => {
|
||||||
|
const res: [string, string[]][] = [];
|
||||||
|
|
||||||
|
for (const [table, columns] of val) {
|
||||||
|
if (columns.size) {
|
||||||
|
res.push([table, Array.from(columns)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.length ? res : undefined;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
syncKubeconfigEntries: getPreferenceDescriptor<KubeconfigSyncEntry[], ObservableMap<string, KubeconfigSyncValue>>({
|
||||||
|
fromStore: val => observable.map(
|
||||||
|
val?.map(({ filePath, ...rest }) => [filePath, rest])
|
||||||
|
?? [[mainKubeFolderPath, {}]],
|
||||||
|
),
|
||||||
|
toStore: val => val.size === 1 && val.has(mainKubeFolderPath)
|
||||||
|
? undefined
|
||||||
|
: Array.from(val, ([filePath, rest]) => ({ filePath, ...rest })),
|
||||||
|
}),
|
||||||
|
editorConfiguration: getPreferenceDescriptor<Partial<EditorConfiguration>, EditorConfiguration>({
|
||||||
|
fromStore: val => merge(defaultEditorConfig, val),
|
||||||
|
toStore: val => val,
|
||||||
|
}),
|
||||||
|
terminalConfig: getPreferenceDescriptor<Partial<TerminalConfig>, TerminalConfig>({
|
||||||
|
fromStore: val => merge(defaultTerminalConfig, val),
|
||||||
|
toStore: val => val,
|
||||||
|
}),
|
||||||
|
extensionRegistryUrl: getPreferenceDescriptor<ExtensionRegistry>({
|
||||||
|
fromStore: val => val ?? {
|
||||||
|
location: defaultExtensionRegistryUrlLocation,
|
||||||
|
},
|
||||||
|
toStore: val => val.location === defaultExtensionRegistryUrlLocation
|
||||||
|
? undefined
|
||||||
|
: val,
|
||||||
|
}),
|
||||||
|
}) as const;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default userStorePreferenceDescriptorsInjectable;
|
||||||
@ -3,14 +3,9 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import moment from "moment-timezone";
|
|
||||||
import path from "path";
|
|
||||||
import os from "os";
|
|
||||||
import type { editor } from "monaco-editor";
|
import type { editor } from "monaco-editor";
|
||||||
import merge from "lodash/merge";
|
import { defaultEditorFontFamily, defaultFontSize, defaultTerminalFontFamily } from "../vars";
|
||||||
import { defaultThemeId, defaultEditorFontFamily, defaultFontSize, defaultTerminalFontFamily } from "../vars";
|
import type { PreferenceDescriptors } from "./preference-descriptors.injectable";
|
||||||
import type { ObservableMap } from "mobx";
|
|
||||||
import { observable } from "mobx";
|
|
||||||
|
|
||||||
export interface KubeconfigSyncEntry extends KubeconfigSyncValue {
|
export interface KubeconfigSyncEntry extends KubeconfigSyncValue {
|
||||||
filePath: string;
|
filePath: string;
|
||||||
@ -54,86 +49,8 @@ export interface PreferenceDescription<T, R = T> {
|
|||||||
toStore(val: R): T | undefined;
|
toStore(val: R): T | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const httpsProxy: PreferenceDescription<string | undefined> = {
|
export const getPreferenceDescriptor = <T, R = T>(desc: PreferenceDescription<T, R>) => desc;
|
||||||
fromStore(val) {
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
return val || undefined;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const shell: PreferenceDescription<string | undefined> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
return val || undefined;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const colorTheme: PreferenceDescription<string> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return val || defaultThemeId;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (!val || val === defaultThemeId) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const terminalTheme: PreferenceDescription<string> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return val || "";
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
return val || undefined;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultLocaleTimezone = "UTC";
|
|
||||||
|
|
||||||
const localeTimezone: PreferenceDescription<string> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return val || moment.tz.guess(true) || defaultLocaleTimezone;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (!val || val === moment.tz.guess(true) || val === defaultLocaleTimezone) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const allowUntrustedCAs: PreferenceDescription<boolean> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return val ?? false;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (!val) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const allowErrorReporting: PreferenceDescription<boolean> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return val ?? true;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (val === true) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface DownloadMirror {
|
export interface DownloadMirror {
|
||||||
url: string;
|
url: string;
|
||||||
@ -157,142 +74,6 @@ export const packageMirrors = new Map<string, DownloadMirror>([
|
|||||||
}],
|
}],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const downloadMirror: PreferenceDescription<string> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return !val || !packageMirrors.has(val)
|
|
||||||
? defaultPackageMirror
|
|
||||||
: val;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (!val || val === defaultPackageMirror) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadKubectlBinaries: PreferenceDescription<boolean> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return val ?? true;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (val === true) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadBinariesPath: PreferenceDescription<string | undefined> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (!val) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const kubectlBinariesPath: PreferenceDescription<string | undefined> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (!val) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const openAtLogin: PreferenceDescription<boolean> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return val ?? false;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (!val) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const terminalCopyOnSelect: PreferenceDescription<boolean> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return val ?? false;
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (!val) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const hiddenTableColumns: PreferenceDescription<[string, string[]][], Map<string, Set<string>>> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return new Map(
|
|
||||||
(val ?? []).map(([tableId, columnIds]) => [tableId, new Set(columnIds)]),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
const res: [string, string[]][] = [];
|
|
||||||
|
|
||||||
for (const [table, columns] of val) {
|
|
||||||
if (columns.size) {
|
|
||||||
res.push([table, Array.from(columns)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.length ? res : undefined;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const mainKubeFolder = path.join(os.homedir(), ".kube");
|
|
||||||
|
|
||||||
const syncKubeconfigEntries: PreferenceDescription<KubeconfigSyncEntry[], ObservableMap<string, KubeconfigSyncValue>> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return observable.map(
|
|
||||||
val
|
|
||||||
?.map(({ filePath, ...rest }) => [filePath, rest])
|
|
||||||
?? [[mainKubeFolder, {}]],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (val.size === 1 && val.has(mainKubeFolder)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(val, ([filePath, rest]) => ({ filePath, ...rest }));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const editorConfiguration: PreferenceDescription<Partial<EditorConfiguration> | undefined, EditorConfiguration> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return merge(defaultEditorConfig, val);
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const terminalConfig: PreferenceDescription<TerminalConfig, TerminalConfig> = {
|
|
||||||
fromStore(val) {
|
|
||||||
return merge(defaultTerminalConfig, val);
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ExtensionRegistryLocation = "default" | "npmrc" | "custom";
|
export type ExtensionRegistryLocation = "default" | "npmrc" | "custom";
|
||||||
|
|
||||||
export type ExtensionRegistry = {
|
export type ExtensionRegistry = {
|
||||||
@ -306,49 +87,13 @@ export type ExtensionRegistry = {
|
|||||||
export const defaultExtensionRegistryUrlLocation = "default";
|
export const defaultExtensionRegistryUrlLocation = "default";
|
||||||
export const defaultExtensionRegistryUrl = "https://registry.npmjs.org";
|
export const defaultExtensionRegistryUrl = "https://registry.npmjs.org";
|
||||||
|
|
||||||
const extensionRegistryUrl: PreferenceDescription<ExtensionRegistry> = {
|
type PreferencesModelType<field extends keyof PreferenceDescriptors> = PreferenceDescriptors[field] extends PreferenceDescription<infer T, any> ? T : never;
|
||||||
fromStore(val) {
|
type UserStoreModelType<field extends keyof PreferenceDescriptors> = PreferenceDescriptors[field] extends PreferenceDescription<any, infer T> ? T : never;
|
||||||
return val ?? {
|
|
||||||
location: defaultExtensionRegistryUrlLocation,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
toStore(val) {
|
|
||||||
if (val.location === defaultExtensionRegistryUrlLocation) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type PreferencesModelType<field extends keyof typeof DESCRIPTORS> = typeof DESCRIPTORS[field] extends PreferenceDescription<infer T, any> ? T : never;
|
|
||||||
type UserStoreModelType<field extends keyof typeof DESCRIPTORS> = typeof DESCRIPTORS[field] extends PreferenceDescription<any, infer T> ? T : never;
|
|
||||||
|
|
||||||
export type UserStoreFlatModel = {
|
export type UserStoreFlatModel = {
|
||||||
[field in keyof typeof DESCRIPTORS]: UserStoreModelType<field>;
|
[field in keyof PreferenceDescriptors]: UserStoreModelType<field>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserPreferencesModel = {
|
export type UserPreferencesModel = {
|
||||||
[field in keyof typeof DESCRIPTORS]: PreferencesModelType<field>;
|
[field in keyof PreferenceDescriptors]: PreferencesModelType<field>;
|
||||||
} & { updateChannel: string };
|
} & { updateChannel: string };
|
||||||
|
|
||||||
export const DESCRIPTORS = {
|
|
||||||
httpsProxy,
|
|
||||||
shell,
|
|
||||||
colorTheme,
|
|
||||||
terminalTheme,
|
|
||||||
localeTimezone,
|
|
||||||
allowUntrustedCAs,
|
|
||||||
allowErrorReporting,
|
|
||||||
downloadMirror,
|
|
||||||
downloadKubectlBinaries,
|
|
||||||
downloadBinariesPath,
|
|
||||||
kubectlBinariesPath,
|
|
||||||
openAtLogin,
|
|
||||||
hiddenTableColumns,
|
|
||||||
syncKubeconfigEntries,
|
|
||||||
editorConfiguration,
|
|
||||||
terminalCopyOnSelect,
|
|
||||||
terminalConfig,
|
|
||||||
extensionRegistryUrl,
|
|
||||||
};
|
|
||||||
|
|||||||
37
src/common/user-store/terminal-theme.injectable.ts
Normal file
37
src/common/user-store/terminal-theme.injectable.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import userStoreInjectable from "./user-store.injectable";
|
||||||
|
|
||||||
|
export type TerminalThemePreference = {
|
||||||
|
matchLensTheme: true;
|
||||||
|
} | {
|
||||||
|
matchLensTheme: false;
|
||||||
|
themeId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const terminalThemePreferenceInjectable = getInjectable({
|
||||||
|
id: "terminal-theme-preference",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const userStore = di.inject(userStoreInjectable);
|
||||||
|
|
||||||
|
return computed((): TerminalThemePreference => {
|
||||||
|
// NOTE: remove use of magic strings
|
||||||
|
if (!userStore.terminalTheme) {
|
||||||
|
return {
|
||||||
|
matchLensTheme: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
matchLensTheme: false,
|
||||||
|
themeId: userStore.terminalTheme,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default terminalThemePreferenceInjectable;
|
||||||
@ -6,20 +6,37 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import { UserStore } from "./user-store";
|
import { UserStore } from "./user-store";
|
||||||
import selectedUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable";
|
import selectedUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable";
|
||||||
import emitAppEventInjectable from "../app-event-bus/emit-event.injectable";
|
import emitAppEventInjectable from "../app-event-bus/emit-event.injectable";
|
||||||
|
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 loggerInjectable from "../logger.injectable";
|
||||||
|
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||||
|
import storeMigrationsInjectable from "../base-store/migrations.injectable";
|
||||||
|
import { userStoreMigrationInjectionToken } from "./migrations-token";
|
||||||
|
import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix";
|
||||||
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
||||||
|
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
||||||
|
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
||||||
|
import { enlistMessageChannelListenerInjectionToken } from "../utils/channel/enlist-message-channel-listener-injection-token";
|
||||||
|
import userStorePreferenceDescriptorsInjectable from "./preference-descriptors.injectable";
|
||||||
|
|
||||||
const userStoreInjectable = getInjectable({
|
const userStoreInjectable = getInjectable({
|
||||||
id: "user-store",
|
id: "user-store",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => new UserStore({
|
||||||
UserStore.resetInstance();
|
|
||||||
|
|
||||||
return UserStore.createInstance({
|
|
||||||
selectedUpdateChannel: di.inject(selectedUpdateChannelInjectable),
|
selectedUpdateChannel: di.inject(selectedUpdateChannelInjectable),
|
||||||
emitAppEvent: di.inject(emitAppEventInjectable),
|
emitAppEvent: di.inject(emitAppEventInjectable),
|
||||||
});
|
directoryForUserData: di.inject(directoryForUserDataInjectable),
|
||||||
},
|
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
causesSideEffects: true,
|
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
||||||
|
migrations: di.inject(storeMigrationsInjectable, userStoreMigrationInjectionToken),
|
||||||
|
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||||
|
ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken),
|
||||||
|
persistStateToConfig: di.inject(persistStateToConfigInjectionToken),
|
||||||
|
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
||||||
|
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
||||||
|
preferenceDescriptors: di.inject(userStorePreferenceDescriptorsInjectable),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default userStoreInjectable;
|
export default userStoreInjectable;
|
||||||
|
|||||||
@ -3,44 +3,37 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { app } from "electron";
|
import { action, observable, makeObservable, isObservableArray, isObservableSet, isObservableMap } from "mobx";
|
||||||
import { action, observable, reaction, makeObservable, isObservableArray, isObservableSet, isObservableMap } from "mobx";
|
import type { BaseStoreDependencies } from "../base-store/base-store";
|
||||||
import { BaseStore } from "../base-store";
|
import { BaseStore } from "../base-store/base-store";
|
||||||
import migrations from "../../migrations/user-store";
|
|
||||||
import { getOrInsertSet, toggle, toJS, object } from "../../renderer/utils";
|
import { getOrInsertSet, toggle, toJS, object } from "../../renderer/utils";
|
||||||
import { DESCRIPTORS } from "./preferences-helpers";
|
|
||||||
import type { UserPreferencesModel, StoreType } from "./preferences-helpers";
|
import type { UserPreferencesModel, StoreType } from "./preferences-helpers";
|
||||||
import logger from "../../main/logger";
|
|
||||||
import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable";
|
import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable";
|
||||||
|
|
||||||
// TODO: Remove coupling with Feature
|
// TODO: Remove coupling with Feature
|
||||||
import type { SelectedUpdateChannel } from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable";
|
import type { SelectedUpdateChannel } from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable";
|
||||||
import type { ReleaseChannel } from "../../features/application-update/common/update-channels";
|
import type { ReleaseChannel } from "../../features/application-update/common/update-channels";
|
||||||
|
import type { PreferenceDescriptors } from "./preference-descriptors.injectable";
|
||||||
|
|
||||||
export interface UserStoreModel {
|
export interface UserStoreModel {
|
||||||
lastSeenAppVersion: string;
|
|
||||||
preferences: UserPreferencesModel;
|
preferences: UserPreferencesModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies extends BaseStoreDependencies {
|
||||||
readonly selectedUpdateChannel: SelectedUpdateChannel;
|
readonly selectedUpdateChannel: SelectedUpdateChannel;
|
||||||
|
readonly preferenceDescriptors: PreferenceDescriptors;
|
||||||
emitAppEvent: EmitAppEvent;
|
emitAppEvent: EmitAppEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UserStore extends BaseStore<UserStoreModel> /* implements UserStoreFlatModel (when strict null is enabled) */ {
|
export class UserStore extends BaseStore<UserStoreModel> /* implements UserStoreFlatModel (when strict null is enabled) */ {
|
||||||
readonly displayName = "UserStore";
|
constructor(protected readonly dependencies: Dependencies) {
|
||||||
|
super(dependencies, {
|
||||||
constructor(private readonly dependencies: Dependencies) {
|
|
||||||
super({
|
|
||||||
configName: "lens-user-store",
|
configName: "lens-user-store",
|
||||||
migrations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable lastSeenAppVersion = "0.0.0";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated No longer used
|
* @deprecated No longer used
|
||||||
*/
|
*/
|
||||||
@ -51,58 +44,45 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
|
|||||||
*/
|
*/
|
||||||
@observable newContexts = observable.set<string>();
|
@observable newContexts = observable.set<string>();
|
||||||
|
|
||||||
@observable allowErrorReporting!: StoreType<typeof DESCRIPTORS["allowErrorReporting"]>;
|
@observable allowErrorReporting!: StoreType<PreferenceDescriptors["allowErrorReporting"]>;
|
||||||
@observable allowUntrustedCAs!: StoreType<typeof DESCRIPTORS["allowUntrustedCAs"]>;
|
@observable allowUntrustedCAs!: StoreType<PreferenceDescriptors["allowUntrustedCAs"]>;
|
||||||
@observable colorTheme!: StoreType<typeof DESCRIPTORS["colorTheme"]>;
|
@observable colorTheme!: StoreType<PreferenceDescriptors["colorTheme"]>;
|
||||||
@observable terminalTheme!: StoreType<typeof DESCRIPTORS["terminalTheme"]>;
|
@observable terminalTheme!: StoreType<PreferenceDescriptors["terminalTheme"]>;
|
||||||
@observable localeTimezone!: StoreType<typeof DESCRIPTORS["localeTimezone"]>;
|
@observable localeTimezone!: StoreType<PreferenceDescriptors["localeTimezone"]>;
|
||||||
@observable downloadMirror!: StoreType<typeof DESCRIPTORS["downloadMirror"]>;
|
@observable downloadMirror!: StoreType<PreferenceDescriptors["downloadMirror"]>;
|
||||||
@observable httpsProxy!: StoreType<typeof DESCRIPTORS["httpsProxy"]>;
|
@observable httpsProxy!: StoreType<PreferenceDescriptors["httpsProxy"]>;
|
||||||
@observable shell!: StoreType<typeof DESCRIPTORS["shell"]>;
|
@observable shell!: StoreType<PreferenceDescriptors["shell"]>;
|
||||||
@observable downloadBinariesPath!: StoreType<typeof DESCRIPTORS["downloadBinariesPath"]>;
|
@observable downloadBinariesPath!: StoreType<PreferenceDescriptors["downloadBinariesPath"]>;
|
||||||
@observable kubectlBinariesPath!: StoreType<typeof DESCRIPTORS["kubectlBinariesPath"]>;
|
@observable kubectlBinariesPath!: StoreType<PreferenceDescriptors["kubectlBinariesPath"]>;
|
||||||
@observable terminalCopyOnSelect!: StoreType<typeof DESCRIPTORS["terminalCopyOnSelect"]>;
|
@observable terminalCopyOnSelect!: StoreType<PreferenceDescriptors["terminalCopyOnSelect"]>;
|
||||||
@observable terminalConfig!: StoreType<typeof DESCRIPTORS["terminalConfig"]>;
|
@observable terminalConfig!: StoreType<PreferenceDescriptors["terminalConfig"]>;
|
||||||
@observable extensionRegistryUrl!: StoreType<typeof DESCRIPTORS["extensionRegistryUrl"]>;
|
@observable extensionRegistryUrl!: StoreType<PreferenceDescriptors["extensionRegistryUrl"]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download kubectl binaries matching cluster version
|
* Download kubectl binaries matching cluster version
|
||||||
*/
|
*/
|
||||||
@observable downloadKubectlBinaries!: StoreType<typeof DESCRIPTORS["downloadKubectlBinaries"]>;
|
@observable downloadKubectlBinaries!: StoreType<PreferenceDescriptors["downloadKubectlBinaries"]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the application should open itself at login.
|
* Whether the application should open itself at login.
|
||||||
*/
|
*/
|
||||||
@observable openAtLogin!: StoreType<typeof DESCRIPTORS["openAtLogin"]>;
|
@observable openAtLogin!: StoreType<PreferenceDescriptors["openAtLogin"]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The column IDs under each configurable table ID that have been configured
|
* The column IDs under each configurable table ID that have been configured
|
||||||
* to not be shown
|
* to not be shown
|
||||||
*/
|
*/
|
||||||
@observable hiddenTableColumns!: StoreType<typeof DESCRIPTORS["hiddenTableColumns"]>;
|
@observable hiddenTableColumns!: StoreType<PreferenceDescriptors["hiddenTableColumns"]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monaco editor configs
|
* Monaco editor configs
|
||||||
*/
|
*/
|
||||||
@observable editorConfiguration!: StoreType<typeof DESCRIPTORS["editorConfiguration"]>;
|
@observable editorConfiguration!: StoreType<PreferenceDescriptors["editorConfiguration"]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The set of file/folder paths to be synced
|
* The set of file/folder paths to be synced
|
||||||
*/
|
*/
|
||||||
@observable syncKubeconfigEntries!: StoreType<typeof DESCRIPTORS["syncKubeconfigEntries"]>;
|
@observable syncKubeconfigEntries!: StoreType<PreferenceDescriptors["syncKubeconfigEntries"]>;
|
||||||
|
|
||||||
startMainReactions() {
|
|
||||||
// open at system start-up
|
|
||||||
reaction(() => this.openAtLogin, openAtLogin => {
|
|
||||||
app.setLoginItemSettings({
|
|
||||||
openAtLogin,
|
|
||||||
openAsHidden: true,
|
|
||||||
args: ["--hidden"],
|
|
||||||
});
|
|
||||||
}, {
|
|
||||||
fireImmediately: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a column (by ID) for a table (by ID) is configured to be hidden
|
* Checks if a column (by ID) for a table (by ID) is configured to be hidden
|
||||||
@ -133,18 +113,14 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
resetTheme() {
|
resetTheme() {
|
||||||
this.colorTheme = DESCRIPTORS.colorTheme.fromStore(undefined);
|
this.colorTheme = this.dependencies.preferenceDescriptors.colorTheme.fromStore(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected fromStore({ lastSeenAppVersion, preferences }: Partial<UserStoreModel> = {}) {
|
protected fromStore({ preferences }: Partial<UserStoreModel> = {}) {
|
||||||
logger.debug("UserStore.fromStore()", { lastSeenAppVersion, preferences });
|
this.dependencies.logger.debug("UserStore.fromStore()", { preferences });
|
||||||
|
|
||||||
if (lastSeenAppVersion) {
|
for (const [key, { fromStore }] of object.entries(this.dependencies.preferenceDescriptors)) {
|
||||||
this.lastSeenAppVersion = lastSeenAppVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, { fromStore }] of object.entries(DESCRIPTORS)) {
|
|
||||||
const curVal = this[key];
|
const curVal = this[key];
|
||||||
const newVal = fromStore((preferences)?.[key] as never) as never;
|
const newVal = fromStore((preferences)?.[key] as never) as never;
|
||||||
|
|
||||||
@ -165,16 +141,13 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
|
|||||||
|
|
||||||
toJSON(): UserStoreModel {
|
toJSON(): UserStoreModel {
|
||||||
const preferences = object.fromEntries(
|
const preferences = object.fromEntries(
|
||||||
object.entries(DESCRIPTORS)
|
object.entries(this.dependencies.preferenceDescriptors)
|
||||||
.map(([key, { toStore }]) => [key, toStore(this[key] as never)]),
|
.map(([key, { toStore }]) => [key, toStore(this[key] as never)]),
|
||||||
) as UserPreferencesModel;
|
) as UserPreferencesModel;
|
||||||
|
|
||||||
return toJS({
|
return toJS({
|
||||||
lastSeenAppVersion: this.lastSeenAppVersion,
|
|
||||||
|
|
||||||
preferences: {
|
preferences: {
|
||||||
...preferences,
|
...preferences,
|
||||||
|
|
||||||
updateChannel: this.dependencies.selectedUpdateChannel.value.get().id,
|
updateChannel: this.dependencies.selectedUpdateChannel.value.get().id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -48,11 +48,21 @@ export function getOrInsertSet<K, SK>(map: Map<K, Set<SK>>, key: K): Set<SK> {
|
|||||||
return getOrInsert(map, key, new Set<SK>());
|
return getOrInsert(map, key, new Set<SK>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A currying version of {@link getOrInsertSet}
|
||||||
|
*/
|
||||||
|
export function getOrInsertSetFor<K, SK>(map: Map<K, Set<SK>>): (key: K) => Set<SK> {
|
||||||
|
return (key) => getOrInsertSet(map, key);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like `getOrInsert` but with delayed creation of the item. Which is useful
|
* Like `getOrInsert` but with delayed creation of the item. Which is useful
|
||||||
* if it is very expensive to create the initial value.
|
* if it is very expensive to create the initial value.
|
||||||
*/
|
*/
|
||||||
export function getOrInsertWith<K, V>(map: Map<K, V>, key: K, builder: () => V): V {
|
export function getOrInsertWith<K, V>(map: Map<K, V>, key: K, builder: () => V): V;
|
||||||
|
export function getOrInsertWith<K extends object, V>(map: Map<K, V> | WeakMap<K, V>, key: K, builder: () => V): V;
|
||||||
|
|
||||||
|
export function getOrInsertWith<K extends object, V>(map: Map<K, V> | WeakMap<K, V>, key: K, builder: () => V): V {
|
||||||
if (!map.has(key)) {
|
if (!map.has(key)) {
|
||||||
map.set(key, builder());
|
map.set(key, builder());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 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 randomBytesInjectable from "./random-bytes.injectable";
|
||||||
|
|
||||||
|
export default getGlobalOverride(randomBytesInjectable, () => async (size) => {
|
||||||
|
const res = Buffer.alloc(size);
|
||||||
|
|
||||||
|
for (let i = 0; i < size; i += 1) {
|
||||||
|
res[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
});
|
||||||
17
src/common/utils/random-bytes.injectable.ts
Normal file
17
src/common/utils/random-bytes.injectable.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { randomBytes } from "crypto";
|
||||||
|
import { promisify } from "util";
|
||||||
|
|
||||||
|
export type RandomBytes = (size: number) => Promise<Buffer>;
|
||||||
|
|
||||||
|
const randomBytesInjectable = getInjectable({
|
||||||
|
id: "random-bytes",
|
||||||
|
instantiate: (): RandomBytes => promisify(randomBytes),
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default randomBytesInjectable;
|
||||||
@ -3,10 +3,13 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface StaticThis<T, R extends any[]> { new(...args: R): T }
|
export interface StaticThis<T, R extends any[]> { new(...args: R): T }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This is a form of global shared state
|
||||||
|
*/
|
||||||
export class Singleton {
|
export class Singleton {
|
||||||
private static instances = new WeakMap<object, Singleton>();
|
private static readonly instances = new WeakMap<object, Singleton>();
|
||||||
private static creating = "";
|
private static creating = "";
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@ -123,6 +123,10 @@ export function isDefined<T>(val: T | undefined | null): val is T {
|
|||||||
return val != null;
|
return val != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isFunction(val: unknown): val is (...args: unknown[]) => unknown {
|
||||||
|
return typeof val === "function";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the value in the second position is non-nullable
|
* Checks if the value in the second position is non-nullable
|
||||||
*/
|
*/
|
||||||
@ -146,6 +150,15 @@ export function hasDefiniteField<Field extends keyof T, T>(field: Field): (val:
|
|||||||
return (val): val is T & { [f in Field]-?: NonNullable<T[Field]> } => val[field] != null;
|
return (val): val is T & { [f in Field]-?: NonNullable<T[Field]> } => val[field] != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isPromiseLike(res: unknown): res is (Promise<unknown> | { then: (fn: (val: unknown) => any) => Promise<unknown> }) {
|
||||||
|
if (res instanceof Promise) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isObject(res)
|
||||||
|
&& hasTypedProperty(res, "then", isFunction);
|
||||||
|
}
|
||||||
|
|
||||||
export function isPromiseSettledRejected<T>(result: PromiseSettledResult<T>): result is PromiseRejectedResult {
|
export function isPromiseSettledRejected<T>(result: PromiseSettledResult<T>): result is PromiseRejectedResult {
|
||||||
return result.status === "rejected";
|
return result.status === "rejected";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// App's common configuration for any process (main, renderer, build pipeline, etc.)
|
// App's common configuration for any process (main, renderer, build pipeline, etc.)
|
||||||
import type { ThemeId } from "../renderer/themes/store";
|
import type { ThemeId } from "../renderer/themes/lens-theme";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Switch to using isMacInjectable
|
* @deprecated Switch to using isMacInjectable
|
||||||
@ -55,12 +55,4 @@ export const apiKubePrefix = "/api-kube"; // k8s cluster apis
|
|||||||
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues" as string;
|
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues" as string;
|
||||||
export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/zt-wcl8jq3k-68R5Wcmk1o95MLBE5igUDQ" as string;
|
export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/zt-wcl8jq3k-68R5Wcmk1o95MLBE5igUDQ" as string;
|
||||||
export const supportUrl = "https://docs.k8slens.dev/support/" as string;
|
export const supportUrl = "https://docs.k8slens.dev/support/" as string;
|
||||||
|
|
||||||
export const lensWebsiteWeblinkId = "lens-website-link";
|
|
||||||
export const lensDocumentationWeblinkId = "lens-documentation-link";
|
|
||||||
export const lensSlackWeblinkId = "lens-slack-link";
|
|
||||||
export const lensTwitterWeblinkId = "lens-twitter-link";
|
|
||||||
export const lensBlogWeblinkId = "lens-blog-link";
|
|
||||||
export const kubernetesDocumentationWeblinkId = "kubernetes-documentation-link";
|
|
||||||
|
|
||||||
export const docsUrl = "https://docs.k8slens.dev" as string;
|
export const docsUrl = "https://docs.k8slens.dev" as string;
|
||||||
|
|||||||
@ -1,18 +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 { WeblinkStore } from "./weblink-store";
|
|
||||||
|
|
||||||
const weblinkStoreInjectable = getInjectable({
|
|
||||||
id: "weblink-store",
|
|
||||||
|
|
||||||
instantiate: () => {
|
|
||||||
WeblinkStore.resetInstance();
|
|
||||||
|
|
||||||
return WeblinkStore.createInstance();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default weblinkStoreInjectable;
|
|
||||||
11
src/common/weblinks-store/migration-token.ts
Normal file
11
src/common/weblinks-store/migration-token.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
import type { MigrationDeclaration } from "../base-store/migrations.injectable";
|
||||||
|
|
||||||
|
export const weblinkStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
|
||||||
|
id: "weblink-store-migration-token",
|
||||||
|
});
|
||||||
35
src/common/weblinks-store/weblink-store.injectable.ts
Normal file
35
src/common/weblinks-store/weblink-store.injectable.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 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 directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix";
|
||||||
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
||||||
|
import storeMigrationsInjectable from "../base-store/migrations.injectable";
|
||||||
|
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
||||||
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
||||||
|
import { enlistMessageChannelListenerInjectionToken } from "../utils/channel/enlist-message-channel-listener-injection-token";
|
||||||
|
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||||
|
import { weblinkStoreMigrationInjectionToken } from "./migration-token";
|
||||||
|
import { WeblinkStore } from "./weblink-store";
|
||||||
|
|
||||||
|
const weblinkStoreInjectable = getInjectable({
|
||||||
|
id: "weblink-store",
|
||||||
|
instantiate: (di) => new WeblinkStore({
|
||||||
|
directoryForUserData: di.inject(directoryForUserDataInjectable),
|
||||||
|
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
|
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
||||||
|
migrations: di.inject(storeMigrationsInjectable, weblinkStoreMigrationInjectionToken),
|
||||||
|
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||||
|
ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken),
|
||||||
|
persistStateToConfig: di.inject(persistStateToConfigInjectionToken),
|
||||||
|
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
||||||
|
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default weblinkStoreInjectable;
|
||||||
@ -4,10 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { action, comparer, observable, makeObservable } from "mobx";
|
import { action, comparer, observable, makeObservable } from "mobx";
|
||||||
import { BaseStore } from "./base-store";
|
import type { BaseStoreDependencies } from "../base-store/base-store";
|
||||||
import migrations from "../migrations/weblinks-store";
|
import { BaseStore } from "../base-store/base-store";
|
||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
import { toJS } from "./utils";
|
import { toJS } from "../utils";
|
||||||
|
|
||||||
export interface WeblinkData {
|
export interface WeblinkData {
|
||||||
id: string;
|
id: string;
|
||||||
@ -27,17 +27,15 @@ export interface WeblinkStoreModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class WeblinkStore extends BaseStore<WeblinkStoreModel> {
|
export class WeblinkStore extends BaseStore<WeblinkStoreModel> {
|
||||||
readonly displayName = "WeblinkStore";
|
|
||||||
@observable weblinks: WeblinkData[] = [];
|
@observable weblinks: WeblinkData[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor(deps: BaseStoreDependencies) {
|
||||||
super({
|
super(deps, {
|
||||||
configName: "lens-weblink-store",
|
configName: "lens-weblink-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
syncOptions: {
|
syncOptions: {
|
||||||
equals: comparer.structural,
|
equals: comparer.structural,
|
||||||
},
|
},
|
||||||
migrations,
|
|
||||||
});
|
});
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
this.load();
|
this.load();
|
||||||
@ -14,6 +14,8 @@ import { delay } from "../../renderer/utils";
|
|||||||
import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting";
|
||||||
import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable";
|
import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable";
|
||||||
import type { IpcRenderer } from "electron";
|
import type { IpcRenderer } from "electron";
|
||||||
|
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import currentlyInClusterFrameInjectable from "../../renderer/routes/currently-in-cluster-frame.injectable";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
@ -28,6 +30,9 @@ describe("ExtensionLoader", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
||||||
|
di.override(currentlyInClusterFrameInjectable, () => false);
|
||||||
|
|
||||||
di.override(ipcRendererInjectable, () => ({
|
di.override(ipcRendererInjectable, () => ({
|
||||||
invoke: jest.fn(async (channel: string) => {
|
invoke: jest.fn(async (channel: string) => {
|
||||||
if (channel === "extension-loader:main:state") {
|
if (channel === "extension-loader:main:state") {
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { UserStore } from "../../common/user-store";
|
import userStoreInjectable from "../../common/user-store/user-store.injectable";
|
||||||
|
import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||||
export interface UserPreferenceExtensionItems {
|
export interface UserPreferenceExtensionItems {
|
||||||
/**
|
/**
|
||||||
* Get the configured kubectl binaries path.
|
* Get the configured kubectl binaries path.
|
||||||
@ -11,6 +12,8 @@ export interface UserPreferenceExtensionItems {
|
|||||||
getKubectlPath: () => string | undefined;
|
getKubectlPath: () => string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userStore = asLegacyGlobalForExtensionApi(userStoreInjectable);
|
||||||
|
|
||||||
export const Preferences: UserPreferenceExtensionItems = {
|
export const Preferences: UserPreferenceExtensionItems = {
|
||||||
getKubectlPath: () => UserStore.getInstance().kubectlBinariesPath,
|
getKubectlPath: () => userStore.kubectlBinariesPath,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import getBasenameOfPathInjectable from "../../common/path/get-basename.injectab
|
|||||||
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
||||||
import getRelativePathInjectable from "../../common/path/get-relative-path.injectable";
|
import getRelativePathInjectable from "../../common/path/get-relative-path.injectable";
|
||||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||||
import removePathInjectable from "../../common/fs/remove-path.injectable";
|
import removePathInjectable from "../../common/fs/remove.injectable";
|
||||||
import homeDirectoryPathInjectable from "../../common/os/home-directory-path.injectable";
|
import homeDirectoryPathInjectable from "../../common/os/home-directory-path.injectable";
|
||||||
import applicationInformationInjectable from "../../common/vars/application-information.injectable";
|
import applicationInformationInjectable from "../../common/vars/application-information.injectable";
|
||||||
import lensResourcesDirInjectable from "../../common/vars/lens-resources-dir.injectable";
|
import lensResourcesDirInjectable from "../../common/vars/lens-resources-dir.injectable";
|
||||||
|
|||||||
@ -15,10 +15,13 @@ import readJsonFileInjectable from "../../common/fs/read-json-file.injectable";
|
|||||||
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
|
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
|
||||||
import watchInjectable from "../../common/fs/watch/watch.injectable";
|
import watchInjectable from "../../common/fs/watch/watch.injectable";
|
||||||
import extensionApiVersionInjectable from "../../common/vars/extension-api-version.injectable";
|
import extensionApiVersionInjectable from "../../common/vars/extension-api-version.injectable";
|
||||||
import removePathInjectable from "../../common/fs/remove-path.injectable";
|
import removePathInjectable from "../../common/fs/remove.injectable";
|
||||||
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
||||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||||
import homeDirectoryPathInjectable from "../../common/os/home-directory-path.injectable";
|
import homeDirectoryPathInjectable from "../../common/os/home-directory-path.injectable";
|
||||||
|
import pathExistsSyncInjectable from "../../common/fs/path-exists-sync.injectable";
|
||||||
|
import readJsonSyncInjectable from "../../common/fs/read-json-sync.injectable";
|
||||||
|
import writeJsonSyncInjectable from "../../common/fs/write-json-sync.injectable";
|
||||||
|
|
||||||
describe("ExtensionDiscovery", () => {
|
describe("ExtensionDiscovery", () => {
|
||||||
let extensionDiscovery: ExtensionDiscovery;
|
let extensionDiscovery: ExtensionDiscovery;
|
||||||
@ -34,6 +37,9 @@ describe("ExtensionDiscovery", () => {
|
|||||||
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
||||||
di.override(installExtensionInjectable, () => () => Promise.resolve());
|
di.override(installExtensionInjectable, () => () => Promise.resolve());
|
||||||
di.override(extensionApiVersionInjectable, () => "5.0.0");
|
di.override(extensionApiVersionInjectable, () => "5.0.0");
|
||||||
|
di.override(pathExistsSyncInjectable, () => () => { throw new Error("tried call pathExistsSync without override"); });
|
||||||
|
di.override(readJsonSyncInjectable, () => () => { throw new Error("tried call readJsonSync without override"); });
|
||||||
|
di.override(writeJsonSyncInjectable, () => () => { throw new Error("tried call writeJsonSync without override"); });
|
||||||
|
|
||||||
joinPaths = di.inject(joinPathsInjectable);
|
joinPaths = di.inject(joinPathsInjectable);
|
||||||
homeDirectoryPath = di.inject(homeDirectoryPathInjectable);
|
homeDirectoryPath = di.inject(homeDirectoryPathInjectable);
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
|||||||
import type { GetBasenameOfPath } from "../../common/path/get-basename.injectable";
|
import type { GetBasenameOfPath } from "../../common/path/get-basename.injectable";
|
||||||
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
|
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
|
||||||
import type { GetRelativePath } from "../../common/path/get-relative-path.injectable";
|
import type { GetRelativePath } from "../../common/path/get-relative-path.injectable";
|
||||||
import type { RemovePath } from "../../common/fs/remove-path.injectable";
|
import type { RemovePath } from "../../common/fs/remove.injectable";
|
||||||
import type TypedEventEmitter from "typed-emitter";
|
import type TypedEventEmitter from "typed-emitter";
|
||||||
import type { ApplicationInformation } from "../../common/vars/application-information.injectable";
|
import type { ApplicationInformation } from "../../common/vars/application-information.injectable";
|
||||||
|
|
||||||
|
|||||||
@ -9,11 +9,8 @@ import extensionPackageRootDirectoryInjectable from "./extension-package-root-di
|
|||||||
const extensionInstallerInjectable = getInjectable({
|
const extensionInstallerInjectable = getInjectable({
|
||||||
id: "extension-installer",
|
id: "extension-installer",
|
||||||
|
|
||||||
instantiate: (di) =>
|
instantiate: (di) => new ExtensionInstaller({
|
||||||
new ExtensionInstaller({
|
extensionPackageRootDirectory: di.inject(extensionPackageRootDirectoryInjectable),
|
||||||
extensionPackageRootDirectory: di.inject(
|
|
||||||
extensionPackageRootDirectoryInjectable,
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import directoryForUserDataInjectable
|
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
|
|
||||||
const extensionPackageRootDirectoryInjectable = getInjectable({
|
const extensionPackageRootDirectoryInjectable = getInjectable({
|
||||||
id: "extension-package-root-directory",
|
id: "extension-package-root-directory",
|
||||||
|
|||||||
@ -5,19 +5,38 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { FileSystemProvisionerStore } from "./file-system-provisioner-store";
|
import { FileSystemProvisionerStore } from "./file-system-provisioner-store";
|
||||||
import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable";
|
import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable";
|
||||||
|
import ensureDirectoryInjectable from "../../../common/fs/ensure-dir.injectable";
|
||||||
|
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||||
|
import randomBytesInjectable from "../../../common/utils/random-bytes.injectable";
|
||||||
|
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
|
import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable";
|
||||||
|
import { baseStoreIpcChannelPrefixesInjectionToken } from "../../../common/base-store/channel-prefix";
|
||||||
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../../common/base-store/disable-sync";
|
||||||
|
import { persistStateToConfigInjectionToken } from "../../../common/base-store/save-to-file";
|
||||||
|
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
||||||
|
import { enlistMessageChannelListenerInjectionToken } from "../../../common/utils/channel/enlist-message-channel-listener-injection-token";
|
||||||
|
|
||||||
const fileSystemProvisionerStoreInjectable = getInjectable({
|
const fileSystemProvisionerStoreInjectable = getInjectable({
|
||||||
id: "file-system-provisioner-store",
|
id: "file-system-provisioner-store",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => new FileSystemProvisionerStore({
|
||||||
FileSystemProvisionerStore.resetInstance();
|
|
||||||
|
|
||||||
return FileSystemProvisionerStore.createInstance({
|
|
||||||
directoryForExtensionData: di.inject(directoryForExtensionDataInjectable),
|
directoryForExtensionData: di.inject(directoryForExtensionDataInjectable),
|
||||||
});
|
ensureDirectory: di.inject(ensureDirectoryInjectable),
|
||||||
},
|
joinPaths: di.inject(joinPathsInjectable),
|
||||||
|
randomBytes: di.inject(randomBytesInjectable),
|
||||||
causesSideEffects: true,
|
directoryForUserData: di.inject(directoryForUserDataInjectable),
|
||||||
|
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
|
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
||||||
|
migrations: {},
|
||||||
|
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||||
|
ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken),
|
||||||
|
persistStateToConfig: di.inject(persistStateToConfigInjectionToken),
|
||||||
|
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
||||||
|
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default fileSystemProvisionerStoreInjectable;
|
export default fileSystemProvisionerStoreInjectable;
|
||||||
|
|||||||
@ -3,35 +3,37 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { randomBytes } from "crypto";
|
|
||||||
import { SHA256 } from "crypto-js";
|
import { SHA256 } from "crypto-js";
|
||||||
import fse from "fs-extra";
|
|
||||||
import { action, makeObservable, observable } from "mobx";
|
import { action, makeObservable, observable } from "mobx";
|
||||||
import path from "path";
|
import type { BaseStoreDependencies } from "../../../common/base-store/base-store";
|
||||||
import { BaseStore } from "../../../common/base-store";
|
import { BaseStore } from "../../../common/base-store/base-store";
|
||||||
import type { LensExtensionId } from "../../lens-extension";
|
import type { LensExtensionId } from "../../lens-extension";
|
||||||
import { getOrInsertWith, toJS } from "../../../common/utils";
|
import { getOrInsertWithAsync, toJS } from "../../../common/utils";
|
||||||
|
import type { EnsureDirectory } from "../../../common/fs/ensure-dir.injectable";
|
||||||
|
import type { JoinPaths } from "../../../common/path/join-paths.injectable";
|
||||||
|
import type { RandomBytes } from "../../../common/utils/random-bytes.injectable";
|
||||||
|
|
||||||
interface FSProvisionModel {
|
interface FSProvisionModel {
|
||||||
extensions: Record<string, string>; // extension names to paths
|
extensions: Record<string, string>; // extension names to paths
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies extends BaseStoreDependencies {
|
||||||
directoryForExtensionData: string;
|
readonly directoryForExtensionData: string;
|
||||||
|
ensureDirectory: EnsureDirectory;
|
||||||
|
joinPaths: JoinPaths;
|
||||||
|
randomBytes: RandomBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||||
readonly displayName = "FilesystemProvisionerStore";
|
readonly registeredExtensions = observable.map<LensExtensionId, string>();
|
||||||
registeredExtensions = observable.map<LensExtensionId, string>();
|
|
||||||
|
|
||||||
constructor(private dependencies: Dependencies) {
|
constructor(protected readonly dependencies: Dependencies) {
|
||||||
super({
|
super(dependencies, {
|
||||||
configName: "lens-filesystem-provisioner-store",
|
configName: "lens-filesystem-provisioner-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
});
|
});
|
||||||
|
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
this.load();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,14 +43,14 @@ export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
|||||||
* @returns path to the folder that the extension can safely write files to.
|
* @returns path to the folder that the extension can safely write files to.
|
||||||
*/
|
*/
|
||||||
async requestDirectory(extensionName: string): Promise<string> {
|
async requestDirectory(extensionName: string): Promise<string> {
|
||||||
const dirPath = getOrInsertWith(this.registeredExtensions, extensionName, () => {
|
const dirPath = await getOrInsertWithAsync(this.registeredExtensions, extensionName, async () => {
|
||||||
const salt = randomBytes(32).toString("hex");
|
const salt = (await this.dependencies.randomBytes(32)).toString("hex");
|
||||||
const hashedName = SHA256(`${extensionName}/${salt}`).toString();
|
const hashedName = SHA256(`${extensionName}/${salt}`).toString();
|
||||||
|
|
||||||
return path.resolve(this.dependencies.directoryForExtensionData, hashedName);
|
return this.dependencies.joinPaths(this.dependencies.directoryForExtensionData, hashedName);
|
||||||
});
|
});
|
||||||
|
|
||||||
await fse.ensureDir(dirPath);
|
await this.dependencies.ensureDirectory(dirPath);
|
||||||
|
|
||||||
return dirPath;
|
return dirPath;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,13 +3,76 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BaseStore } from "../common/base-store";
|
import type { BaseStoreParams } from "../common/base-store/base-store";
|
||||||
|
import { BaseStore } from "../common/base-store/base-store";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import type { LensExtension } from "./lens-extension";
|
import type { LensExtension } from "./lens-extension";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
|
import type { StaticThis } from "../common/utils";
|
||||||
|
import { getOrInsertWith } from "../common/utils";
|
||||||
|
import { getLegacyGlobalDiForExtensionApi } from "./as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
|
import directoryForUserDataInjectable from "../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import getConfigurationFileModelInjectable from "../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
|
import loggerInjectable from "../common/logger.injectable";
|
||||||
|
import storeMigrationVersionInjectable from "../common/vars/store-migration-version.injectable";
|
||||||
|
import type { Migrations } from "conf/dist/source/types";
|
||||||
|
import { baseStoreIpcChannelPrefixesInjectionToken } from "../common/base-store/channel-prefix";
|
||||||
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../common/base-store/disable-sync";
|
||||||
|
import { persistStateToConfigInjectionToken } from "../common/base-store/save-to-file";
|
||||||
|
import getBasenameOfPathInjectable from "../common/path/get-basename.injectable";
|
||||||
|
import { enlistMessageChannelListenerInjectionToken } from "../common/utils/channel/enlist-message-channel-listener-injection-token";
|
||||||
|
|
||||||
|
export interface ExtensionStoreParams<T extends object> extends BaseStoreParams<T> {
|
||||||
|
migrations?: Migrations<T>;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class ExtensionStore<T extends object> extends BaseStore<T> {
|
export abstract class ExtensionStore<T extends object> extends BaseStore<T> {
|
||||||
readonly displayName = "ExtensionStore<T>";
|
private static readonly instances = new WeakMap<object, ExtensionStore<object>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This is a form of global shared state. Just call `new Store(...)`
|
||||||
|
*/
|
||||||
|
static createInstance<T extends ExtensionStore<object>, R extends any[]>(this: StaticThis<T, R>, ...args: R): T {
|
||||||
|
return getOrInsertWith(ExtensionStore.instances, this, () => new this(...args)) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This is a form of global shared state. Just call `new Store(...)`
|
||||||
|
*/
|
||||||
|
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict?: true): T;
|
||||||
|
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict: false): T | undefined;
|
||||||
|
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict = true): T | undefined {
|
||||||
|
if (!ExtensionStore.instances.has(this) && strict) {
|
||||||
|
throw new TypeError(`instance of ${this.name} is not created`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExtensionStore.instances.get(this) as (T | undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor({ migrations, ...params }: ExtensionStoreParams<T>) {
|
||||||
|
const di = getLegacyGlobalDiForExtensionApi();
|
||||||
|
|
||||||
|
super({
|
||||||
|
directoryForUserData: di.inject(directoryForUserDataInjectable),
|
||||||
|
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
|
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
||||||
|
migrations: migrations as Migrations<Record<string, unknown>>,
|
||||||
|
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||||
|
ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken),
|
||||||
|
persistStateToConfig: di.inject(persistStateToConfigInjectionToken),
|
||||||
|
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
||||||
|
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
||||||
|
}, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This is a form of global shared state. Just call `new Store(...)`
|
||||||
|
*/
|
||||||
|
static resetInstance() {
|
||||||
|
ExtensionStore.instances.delete(this);
|
||||||
|
}
|
||||||
|
|
||||||
protected extension?: LensExtension;
|
protected extension?: LensExtension;
|
||||||
|
|
||||||
loadExtension(extension: LensExtension) {
|
loadExtension(extension: LensExtension) {
|
||||||
|
|||||||
@ -3,18 +3,31 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import { baseStoreIpcChannelPrefixesInjectionToken } from "../../common/base-store/channel-prefix";
|
||||||
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../common/base-store/disable-sync";
|
||||||
|
import { persistStateToConfigInjectionToken } from "../../common/base-store/save-to-file";
|
||||||
|
import getConfigurationFileModelInjectable from "../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
|
import getBasenameOfPathInjectable from "../../common/path/get-basename.injectable";
|
||||||
|
import { enlistMessageChannelListenerInjectionToken } from "../../common/utils/channel/enlist-message-channel-listener-injection-token";
|
||||||
|
import storeMigrationVersionInjectable from "../../common/vars/store-migration-version.injectable";
|
||||||
import { ExtensionsStore } from "./extensions-store";
|
import { ExtensionsStore } from "./extensions-store";
|
||||||
|
|
||||||
const extensionsStoreInjectable = getInjectable({
|
const extensionsStoreInjectable = getInjectable({
|
||||||
id: "extensions-store",
|
id: "extensions-store",
|
||||||
|
instantiate: (di) => new ExtensionsStore({
|
||||||
instantiate: () => {
|
directoryForUserData: di.inject(directoryForUserDataInjectable),
|
||||||
ExtensionsStore.resetInstance();
|
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
return ExtensionsStore.createInstance();
|
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
||||||
},
|
migrations: {},
|
||||||
|
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||||
causesSideEffects: true,
|
ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken),
|
||||||
|
persistStateToConfig: di.inject(persistStateToConfigInjectionToken),
|
||||||
|
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
||||||
|
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default extensionsStoreInjectable;
|
export default extensionsStoreInjectable;
|
||||||
|
|||||||
@ -6,7 +6,8 @@
|
|||||||
import type { LensExtensionId } from "../lens-extension";
|
import type { LensExtensionId } from "../lens-extension";
|
||||||
import { action, computed, makeObservable, observable } from "mobx";
|
import { action, computed, makeObservable, observable } from "mobx";
|
||||||
import { toJS } from "../../common/utils";
|
import { toJS } from "../../common/utils";
|
||||||
import { BaseStore } from "../../common/base-store";
|
import type { BaseStoreDependencies } from "../../common/base-store/base-store";
|
||||||
|
import { BaseStore } from "../../common/base-store/base-store";
|
||||||
|
|
||||||
export interface LensExtensionsStoreModel {
|
export interface LensExtensionsStoreModel {
|
||||||
extensions: Record<LensExtensionId, LensExtensionState>;
|
extensions: Record<LensExtensionId, LensExtensionState>;
|
||||||
@ -23,9 +24,8 @@ export interface IsEnabledExtensionDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
||||||
readonly displayName = "ExtensionsStore";
|
constructor(deps: BaseStoreDependencies) {
|
||||||
constructor() {
|
super(deps, {
|
||||||
super({
|
|
||||||
configName: "lens-extensions",
|
configName: "lens-extensions",
|
||||||
});
|
});
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import activeThemeInjectable from "../../renderer/themes/active.injectable";
|
import activeThemeInjectable from "../../renderer/themes/active.injectable";
|
||||||
import type { LensTheme } from "../../renderer/themes/store";
|
import type { LensTheme } from "../../renderer/themes/lens-theme";
|
||||||
import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||||
|
|
||||||
export const activeTheme = asLegacyGlobalForExtensionApi(activeThemeInjectable);
|
export const activeTheme = asLegacyGlobalForExtensionApi(activeThemeInjectable);
|
||||||
|
|||||||
@ -164,7 +164,23 @@ exports[`extension special characters in page registrations renders 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="0"
|
index="0"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="1"
|
index="1"
|
||||||
@ -231,7 +247,7 @@ exports[`extension special characters in page registrations renders 1`] = `
|
|||||||
class="badge Badge small clickable"
|
class="badge Badge small clickable"
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
>
|
>
|
||||||
0
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
@ -352,7 +368,23 @@ exports[`extension special characters in page registrations when navigating to r
|
|||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="0"
|
index="0"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="1"
|
index="1"
|
||||||
@ -419,7 +451,7 @@ exports[`extension special characters in page registrations when navigating to r
|
|||||||
class="badge Badge small clickable"
|
class="badge Badge small clickable"
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
>
|
>
|
||||||
0
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
|
|||||||
@ -164,7 +164,23 @@ exports[`navigate to extension page renders 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="0"
|
index="0"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="1"
|
index="1"
|
||||||
@ -231,7 +247,7 @@ exports[`navigate to extension page renders 1`] = `
|
|||||||
class="badge Badge small clickable"
|
class="badge Badge small clickable"
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
>
|
>
|
||||||
0
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
@ -352,7 +368,23 @@ exports[`navigate to extension page when extension navigates to child route rend
|
|||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="0"
|
index="0"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="1"
|
index="1"
|
||||||
@ -419,7 +451,7 @@ exports[`navigate to extension page when extension navigates to child route rend
|
|||||||
class="badge Badge small clickable"
|
class="badge Badge small clickable"
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
>
|
>
|
||||||
0
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
@ -556,7 +588,23 @@ exports[`navigate to extension page when extension navigates to route with param
|
|||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="0"
|
index="0"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="1"
|
index="1"
|
||||||
@ -623,7 +671,7 @@ exports[`navigate to extension page when extension navigates to route with param
|
|||||||
class="badge Badge small clickable"
|
class="badge Badge small clickable"
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
>
|
>
|
||||||
0
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
@ -760,7 +808,23 @@ exports[`navigate to extension page when extension navigates to route without pa
|
|||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="0"
|
index="0"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="1"
|
index="1"
|
||||||
@ -827,7 +891,7 @@ exports[`navigate to extension page when extension navigates to route without pa
|
|||||||
class="badge Badge small clickable"
|
class="badge Badge small clickable"
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
>
|
>
|
||||||
0
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
@ -964,7 +1028,23 @@ exports[`navigate to extension page when extension navigates to route without pa
|
|||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="0"
|
index="0"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="1"
|
index="1"
|
||||||
@ -1031,7 +1111,7 @@ exports[`navigate to extension page when extension navigates to route without pa
|
|||||||
class="badge Badge small clickable"
|
class="badge Badge small clickable"
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
>
|
>
|
||||||
0
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
|
|||||||
@ -88,7 +88,23 @@ exports[`navigating between routes given route with optional path parameters whe
|
|||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="0"
|
index="0"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="1"
|
index="1"
|
||||||
@ -155,7 +171,7 @@ exports[`navigating between routes given route with optional path parameters whe
|
|||||||
class="badge Badge small clickable"
|
class="badge Badge small clickable"
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
>
|
>
|
||||||
0
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
@ -276,7 +292,23 @@ exports[`navigating between routes given route without path parameters when navi
|
|||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="0"
|
index="0"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="1"
|
index="1"
|
||||||
@ -343,7 +375,7 @@ exports[`navigating between routes given route without path parameters when navi
|
|||||||
class="badge Badge small clickable"
|
class="badge Badge small clickable"
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
>
|
>
|
||||||
0
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
|
|||||||
@ -164,7 +164,23 @@ exports[`add-cluster - navigation using application menu renders 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="0"
|
index="0"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="1"
|
index="1"
|
||||||
@ -231,7 +247,7 @@ exports[`add-cluster - navigation using application menu renders 1`] = `
|
|||||||
class="badge Badge small clickable"
|
class="badge Badge small clickable"
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
>
|
>
|
||||||
0
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
@ -436,7 +452,23 @@ exports[`add-cluster - navigation using application menu when navigating to add
|
|||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="0"
|
index="0"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
style="z-index: 12; position: absolute;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="HotbarIcon contextMenuAvailable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded disabled avatar"
|
||||||
|
id="hotbarIcon-hotbar-icon-catalog-entity"
|
||||||
|
style="width: 40px; height: 40px; background: rgb(5, 1, 130);"
|
||||||
|
>
|
||||||
|
Ca
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="HotbarCell isDraggingOwner animateDown"
|
class="HotbarCell isDraggingOwner animateDown"
|
||||||
index="1"
|
index="1"
|
||||||
@ -503,7 +535,7 @@ exports[`add-cluster - navigation using application menu when navigating to add
|
|||||||
class="badge Badge small clickable"
|
class="badge Badge small clickable"
|
||||||
id="hotbarIndex"
|
id="hotbarIndex"
|
||||||
>
|
>
|
||||||
0
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i
|
<i
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user