mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into extension-auto-update
This commit is contained in:
commit
f11c51d6df
388 files changed
+11066
-4377
No files matched your search
@@ -20,12 +20,14 @@
|
||||
*/
|
||||
|
||||
import type { ExtensionLoader } from "../extension-loader";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { ExtensionsStore } from "../extensions-store";
|
||||
import { Console } from "console";
|
||||
import { stdout, stderr } from "process";
|
||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
|
||||
import { runInAction } from "mobx";
|
||||
import updateExtensionsStateInjectable
|
||||
from "../extension-loader/update-extensions-state/update-extensions-state.injectable";
|
||||
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
||||
import mockFs from "mock-fs";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
@@ -33,15 +35,6 @@ const manifestPath = "manifest/path";
|
||||
const manifestPath2 = "manifest/path2";
|
||||
const manifestPath3 = "manifest/path3";
|
||||
|
||||
jest.mock("../extensions-store", () => ({
|
||||
ExtensionsStore: {
|
||||
getInstance: () => ({
|
||||
whenLoaded: Promise.resolve(true),
|
||||
mergeState: jest.fn(),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
"electron",
|
||||
() => ({
|
||||
@@ -131,14 +124,27 @@ jest.mock(
|
||||
|
||||
describe("ExtensionLoader", () => {
|
||||
let extensionLoader: ExtensionLoader;
|
||||
let updateExtensionStateMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting();
|
||||
beforeEach(async () => {
|
||||
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
extensionLoader = di.inject(extensionLoaderInjectable);
|
||||
mockFs();
|
||||
|
||||
updateExtensionStateMock = jest.fn();
|
||||
|
||||
dis.mainDi.override(updateExtensionsStateInjectable, () => updateExtensionStateMock);
|
||||
|
||||
await dis.runSetups();
|
||||
|
||||
extensionLoader = dis.mainDi.inject(extensionLoaderInjectable);
|
||||
});
|
||||
|
||||
it.only("renderer updates extension after ipc broadcast", async done => {
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it("renderer updates extension after ipc broadcast", async done => {
|
||||
expect(extensionLoader.userExtensions).toMatchInlineSnapshot(`Map {}`);
|
||||
|
||||
await extensionLoader.init();
|
||||
@@ -177,26 +183,26 @@ describe("ExtensionLoader", () => {
|
||||
});
|
||||
|
||||
it("updates ExtensionsStore after isEnabled is changed", async () => {
|
||||
(ExtensionsStore.getInstance().mergeState as any).mockClear();
|
||||
|
||||
// Disable sending events in this test
|
||||
(ipcRenderer.on as any).mockImplementation();
|
||||
|
||||
await extensionLoader.init();
|
||||
|
||||
expect(ExtensionsStore.getInstance().mergeState).not.toHaveBeenCalled();
|
||||
expect(updateExtensionStateMock).not.toHaveBeenCalled();
|
||||
|
||||
Array.from(extensionLoader.userExtensions.values())[0].isEnabled = false;
|
||||
|
||||
expect(ExtensionsStore.getInstance().mergeState).toHaveBeenCalledWith({
|
||||
"manifest/path": {
|
||||
enabled: false,
|
||||
name: "TestExtension",
|
||||
},
|
||||
"manifest/path2": {
|
||||
enabled: true,
|
||||
name: "TestExtension2",
|
||||
},
|
||||
runInAction(() => {
|
||||
extensionLoader.setIsEnabled("manifest/path", false);
|
||||
});
|
||||
|
||||
expect(updateExtensionStateMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
"manifest/path": {
|
||||
enabled: false,
|
||||
name: "TestExtension",
|
||||
},
|
||||
|
||||
"manifest/path2": {
|
||||
enabled: true,
|
||||
name: "TestExtension2",
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -20,14 +20,13 @@
|
||||
*/
|
||||
|
||||
import { getAppVersion } from "../../common/utils";
|
||||
import { ExtensionsStore } from "../extensions-store";
|
||||
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||
import getEnabledExtensionsInjectable from "./get-enabled-extensions/get-enabled-extensions.injectable";
|
||||
import * as Preferences from "./user-preferences";
|
||||
|
||||
export const version = getAppVersion();
|
||||
export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars";
|
||||
|
||||
export function getEnabledExtensions(): string[] {
|
||||
return ExtensionsStore.getInstance().enabledExtensions;
|
||||
}
|
||||
export const getEnabledExtensions = asLegacyGlobalFunctionForExtensionApi(getEnabledExtensionsInjectable);
|
||||
|
||||
export { Preferences };
|
||||
@@ -19,5 +19,5 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export { appEventBus } from "../../common/event-bus";
|
||||
export type { AppEvent } from "../../common/event-bus";
|
||||
export { appEventBus } from "../../common/app-event-bus/event-bus";
|
||||
export type { AppEvent } from "../../common/app-event-bus/event-bus";
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import extensionsStoreInjectable from "../../extensions-store/extensions-store.injectable";
|
||||
|
||||
const getEnabledExtensionsInjectable = getInjectable({
|
||||
instantiate: (di) => () =>
|
||||
di.inject(extensionsStoreInjectable).enabledExtensions,
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default getEnabledExtensionsInjectable;
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { ExtensionDiscovery } from "./extension-discovery";
|
||||
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
|
||||
import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable";
|
||||
import isCompatibleBundledExtensionInjectable from "./is-compatible-bundled-extension/is-compatible-bundled-extension.injectable";
|
||||
import extensionsStoreInjectable from "../extensions-store/extensions-store.injectable";
|
||||
import extensionInstallationStateStoreInjectable from "../extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable";
|
||||
import extensionPackageRootDirectoryInjectable from "../extension-installer/extension-package-root-directory/extension-package-root-directory.injectable";
|
||||
import installExtensionsInjectable from "../extension-installer/install-extensions/install-extensions.injectable";
|
||||
|
||||
const extensionDiscoveryInjectable = getInjectable({
|
||||
instantiate: (di) =>
|
||||
new ExtensionDiscovery({
|
||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||
extensionsStore: di.inject(extensionsStoreInjectable),
|
||||
|
||||
extensionInstallationStateStore: di.inject(
|
||||
extensionInstallationStateStoreInjectable,
|
||||
),
|
||||
|
||||
isCompatibleBundledExtension: di.inject(
|
||||
isCompatibleBundledExtensionInjectable,
|
||||
),
|
||||
|
||||
isCompatibleExtension: di.inject(isCompatibleExtensionInjectable),
|
||||
|
||||
installExtension: di.inject(installExtensionInjectable),
|
||||
installExtensions: di.inject(installExtensionsInjectable),
|
||||
|
||||
extensionPackageRootDirectory: di.inject(
|
||||
extensionPackageRootDirectoryInjectable,
|
||||
),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default extensionDiscoveryInjectable;
|
||||
+25
-29
@@ -20,16 +20,18 @@
|
||||
*/
|
||||
|
||||
import { watch } from "chokidar";
|
||||
import { ExtensionsStore } from "../extensions-store";
|
||||
import path from "path";
|
||||
import { ExtensionDiscovery } from "../extension-discovery";
|
||||
import os from "os";
|
||||
import { Console } from "console";
|
||||
import { AppPaths } from "../../common/app-paths";
|
||||
import type { ExtensionLoader } from "../extension-loader";
|
||||
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
|
||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||
import * as fse from "fs-extra";
|
||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||
import extensionDiscoveryInjectable from "../extension-discovery/extension-discovery.injectable";
|
||||
import type { ExtensionDiscovery } from "../extension-discovery/extension-discovery";
|
||||
import installExtensionInjectable
|
||||
from "../extension-installer/install-extension/install-extension.injectable";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import mockFs from "mock-fs";
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
@@ -37,12 +39,7 @@ jest.mock("../../common/ipc");
|
||||
jest.mock("chokidar", () => ({
|
||||
watch: jest.fn(),
|
||||
}));
|
||||
jest.mock("../extension-installer", () => ({
|
||||
extensionInstaller: {
|
||||
extensionPackagesRoot: "",
|
||||
installPackage: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("fs-extra");
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
@@ -60,23 +57,28 @@ jest.mock("electron", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
AppPaths.init();
|
||||
|
||||
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||
const mockedWatch = watch as jest.MockedFunction<typeof watch>;
|
||||
const mockedFse = fse as jest.Mocked<typeof fse>;
|
||||
|
||||
describe("ExtensionDiscovery", () => {
|
||||
let extensionLoader: ExtensionLoader;
|
||||
let extensionDiscovery: ExtensionDiscovery;
|
||||
|
||||
beforeEach(() => {
|
||||
ExtensionDiscovery.resetInstance();
|
||||
ExtensionsStore.resetInstance();
|
||||
ExtensionsStore.createInstance();
|
||||
beforeEach(async () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
const di = getDiForUnitTesting();
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
di.override(installExtensionInjectable, () => () => Promise.resolve());
|
||||
|
||||
extensionLoader = di.inject(extensionLoaderInjectable);
|
||||
mockFs();
|
||||
|
||||
await di.runSetups();
|
||||
|
||||
extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it("emits add for added extension", async (done) => {
|
||||
@@ -106,9 +108,6 @@ describe("ExtensionDiscovery", () => {
|
||||
mockedWatch.mockImplementationOnce(() =>
|
||||
(mockWatchInstance) as any,
|
||||
);
|
||||
const extensionDiscovery = ExtensionDiscovery.createInstance(
|
||||
extensionLoader,
|
||||
);
|
||||
|
||||
// Need to force isLoaded to be true so that the file watching is started
|
||||
extensionDiscovery.isLoaded = true;
|
||||
@@ -118,7 +117,7 @@ describe("ExtensionDiscovery", () => {
|
||||
extensionDiscovery.events.on("add", extension => {
|
||||
expect(extension).toEqual({
|
||||
absolutePath: expect.any(String),
|
||||
id: path.normalize("node_modules/my-extension/package.json"),
|
||||
id: path.normalize("some-directory-for-user-data/node_modules/my-extension/package.json"),
|
||||
isBundled: false,
|
||||
isEnabled: false,
|
||||
isCompatible: false,
|
||||
@@ -126,7 +125,7 @@ describe("ExtensionDiscovery", () => {
|
||||
name: "my-extension",
|
||||
version: "1.0.0",
|
||||
},
|
||||
manifestPath: path.normalize("node_modules/my-extension/package.json"),
|
||||
manifestPath: path.normalize("some-directory-for-user-data/node_modules/my-extension/package.json"),
|
||||
availableUpdate: null,
|
||||
});
|
||||
done();
|
||||
@@ -151,9 +150,6 @@ describe("ExtensionDiscovery", () => {
|
||||
mockedWatch.mockImplementationOnce(() =>
|
||||
(mockWatchInstance) as any,
|
||||
);
|
||||
const extensionDiscovery = ExtensionDiscovery.createInstance(
|
||||
extensionLoader,
|
||||
);
|
||||
|
||||
// Need to force isLoaded to be true so that the file watching is started
|
||||
extensionDiscovery.isLoaded = true;
|
||||
+46
-32
@@ -23,19 +23,37 @@ import { watch } from "chokidar";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { EventEmitter } from "events";
|
||||
import fse from "fs-extra";
|
||||
import { observable, reaction, when, makeObservable } from "mobx";
|
||||
import { makeObservable, observable, reaction, when } from "mobx";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { broadcastMessage, ipcMainHandle, ipcRendererOn, requestMain } from "../common/ipc";
|
||||
import { Singleton, toJS } from "../common/utils";
|
||||
import logger from "../main/logger";
|
||||
import { ExtensionInstallationStateStore } from "../renderer/components/+extensions/extension-install.store";
|
||||
import { extensionInstaller } from "./extension-installer";
|
||||
import { ExtensionsStore } from "./extensions-store";
|
||||
import type { ExtensionLoader } from "./extension-loader";
|
||||
import type { LensExtensionId, LensExtensionManifest } from "./lens-extension";
|
||||
import { isProduction } from "../common/vars";
|
||||
import { isCompatibleBundledExtension, isCompatibleExtension } from "./extension-compatibility";
|
||||
import {
|
||||
broadcastMessage,
|
||||
ipcMainHandle,
|
||||
ipcRendererOn,
|
||||
requestMain,
|
||||
} from "../../common/ipc";
|
||||
import { toJS } from "../../common/utils";
|
||||
import logger from "../../main/logger";
|
||||
import type { ExtensionsStore } from "../extensions-store/extensions-store";
|
||||
import type { ExtensionLoader } from "../extension-loader";
|
||||
import type { LensExtensionId, LensExtensionManifest } from "../lens-extension";
|
||||
import { isProduction } from "../../common/vars";
|
||||
import type { ExtensionInstallationStateStore } from "../extension-installation-state-store/extension-installation-state-store";
|
||||
import type { PackageJson } from "type-fest";
|
||||
|
||||
interface Dependencies {
|
||||
extensionLoader: ExtensionLoader;
|
||||
extensionsStore: ExtensionsStore;
|
||||
|
||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
|
||||
isCompatibleBundledExtension: (manifest: LensExtensionManifest) => boolean;
|
||||
isCompatibleExtension: (manifest: LensExtensionManifest) => boolean;
|
||||
|
||||
installExtension: (name: string) => Promise<void>;
|
||||
installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>
|
||||
extensionPackageRootDirectory: string;
|
||||
}
|
||||
|
||||
export interface InstalledExtension {
|
||||
id: LensExtensionId;
|
||||
@@ -85,7 +103,7 @@ interface LoadFromFolderOptions {
|
||||
* - "add": When extension is added. The event is of type InstalledExtension
|
||||
* - "remove": When extension is removed. The event is of type LensExtensionId
|
||||
*/
|
||||
export class ExtensionDiscovery extends Singleton {
|
||||
export class ExtensionDiscovery {
|
||||
protected bundledFolderPath: string;
|
||||
|
||||
private loadStarted = false;
|
||||
@@ -103,9 +121,7 @@ export class ExtensionDiscovery extends Singleton {
|
||||
|
||||
public events = new EventEmitter();
|
||||
|
||||
constructor(protected extensionLoader: ExtensionLoader) {
|
||||
super();
|
||||
|
||||
constructor(protected dependencies : Dependencies) {
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@@ -114,11 +130,11 @@ export class ExtensionDiscovery extends Singleton {
|
||||
}
|
||||
|
||||
get packageJsonPath(): string {
|
||||
return path.join(extensionInstaller.extensionPackagesRoot, manifestFilename);
|
||||
return path.join(this.dependencies.extensionPackageRootDirectory, manifestFilename);
|
||||
}
|
||||
|
||||
get inTreeTargetPath(): string {
|
||||
return path.join(extensionInstaller.extensionPackagesRoot, "extensions");
|
||||
return path.join(this.dependencies.extensionPackageRootDirectory, "extensions");
|
||||
}
|
||||
|
||||
get inTreeFolderPath(): string {
|
||||
@@ -126,7 +142,7 @@ export class ExtensionDiscovery extends Singleton {
|
||||
}
|
||||
|
||||
get nodeModulesPath(): string {
|
||||
return path.join(extensionInstaller.extensionPackagesRoot, "node_modules");
|
||||
return path.join(this.dependencies.extensionPackageRootDirectory, "node_modules");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,7 +217,7 @@ export class ExtensionDiscovery extends Singleton {
|
||||
|
||||
if (path.basename(manifestPath) === manifestFilename && isUnderLocalFolderPath) {
|
||||
try {
|
||||
ExtensionInstallationStateStore.setInstallingFromMain(manifestPath);
|
||||
this.dependencies.extensionInstallationStateStore.setInstallingFromMain(manifestPath);
|
||||
const absPath = path.dirname(manifestPath);
|
||||
|
||||
// this.loadExtensionFromPath updates this.packagesJson
|
||||
@@ -212,7 +228,7 @@ export class ExtensionDiscovery extends Singleton {
|
||||
await fse.remove(extension.manifestPath);
|
||||
|
||||
// Install dependencies for the new extension
|
||||
await this.installPackage(extension.absolutePath);
|
||||
await this.dependencies.installExtension(extension.absolutePath);
|
||||
|
||||
this.extensions.set(extension.id, extension);
|
||||
logger.info(`${logModule} Added extension ${extension.manifest.name}`);
|
||||
@@ -221,7 +237,7 @@ export class ExtensionDiscovery extends Singleton {
|
||||
} catch (error) {
|
||||
logger.error(`${logModule}: failed to add extension: ${error}`, { error });
|
||||
} finally {
|
||||
ExtensionInstallationStateStore.clearInstallingFromMain(manifestPath);
|
||||
this.dependencies.extensionInstallationStateStore.clearInstallingFromMain(manifestPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -281,7 +297,7 @@ export class ExtensionDiscovery extends Singleton {
|
||||
* @param extensionId The ID of the extension to uninstall.
|
||||
*/
|
||||
async uninstallExtension(extensionId: LensExtensionId): Promise<void> {
|
||||
const { manifest, absolutePath } = this.extensions.get(extensionId) ?? this.extensionLoader.getExtension(extensionId);
|
||||
const { manifest, absolutePath } = this.extensions.get(extensionId) ?? this.dependencies.extensionLoader.getExtension(extensionId);
|
||||
|
||||
logger.info(`${logModule} Uninstalling ${manifest.name}`);
|
||||
|
||||
@@ -299,10 +315,12 @@ export class ExtensionDiscovery extends Singleton {
|
||||
|
||||
this.loadStarted = true;
|
||||
|
||||
logger.info(`${logModule} loading extensions from ${extensionInstaller.extensionPackagesRoot}`);
|
||||
logger.info(
|
||||
`${logModule} loading extensions from ${this.dependencies.extensionPackageRootDirectory}`,
|
||||
);
|
||||
|
||||
// fs.remove won't throw if path is missing
|
||||
await fse.remove(path.join(extensionInstaller.extensionPackagesRoot, "package-lock.json"));
|
||||
await fse.remove(path.join(this.dependencies.extensionPackageRootDirectory, "package-lock.json"));
|
||||
|
||||
try {
|
||||
// Verify write access to static/extensions, which is needed for symlinking
|
||||
@@ -361,11 +379,11 @@ export class ExtensionDiscovery extends Singleton {
|
||||
try {
|
||||
const manifest = await fse.readJson(manifestPath) as LensExtensionManifest;
|
||||
const id = this.getInstalledManifestPath(manifest.name);
|
||||
const isEnabled = ExtensionsStore.getInstance().isEnabled({ id, isBundled });
|
||||
const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled });
|
||||
const extensionDir = path.dirname(manifestPath);
|
||||
const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`);
|
||||
const absolutePath = (isProduction && await fse.pathExists(npmPackage)) ? npmPackage : extensionDir;
|
||||
const isCompatible = (isBundled && isCompatibleBundledExtension(manifest)) || isCompatibleExtension(manifest);
|
||||
const isCompatible = (isBundled && this.dependencies.isCompatibleBundledExtension(manifest)) || this.dependencies.isCompatibleExtension(manifest);
|
||||
|
||||
return {
|
||||
id,
|
||||
@@ -399,7 +417,7 @@ export class ExtensionDiscovery extends Singleton {
|
||||
for (const extension of userExtensions) {
|
||||
if ((await fse.pathExists(extension.manifestPath)) === false) {
|
||||
try {
|
||||
await this.installPackage(extension.absolutePath);
|
||||
await this.dependencies.installExtension(extension.absolutePath);
|
||||
} catch (error) {
|
||||
const message = error.message || error || "unknown error";
|
||||
const { name, version } = extension.manifest;
|
||||
@@ -422,11 +440,7 @@ export class ExtensionDiscovery extends Singleton {
|
||||
extensions.map(extension => [extension.manifest.name, extension.absolutePath]),
|
||||
);
|
||||
|
||||
return extensionInstaller.installPackages(packageJsonPath, { dependencies });
|
||||
}
|
||||
|
||||
async installPackage(name: string): Promise<void> {
|
||||
return extensionInstaller.installPackage(name);
|
||||
return this.dependencies.installExtensions(packageJsonPath, { dependencies });
|
||||
}
|
||||
|
||||
async loadBundledExtensions(): Promise<InstalledExtension[]> {
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { appSemVer } from "../../../common/vars";
|
||||
import { isCompatibleBundledExtension } from "./is-compatible-bundled-extension";
|
||||
|
||||
const isCompatibleBundledExtensionInjectable = getInjectable({
|
||||
instantiate: () => isCompatibleBundledExtension({ appSemVer }),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default isCompatibleBundledExtensionInjectable;
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import type { LensExtensionManifest } from "../../lens-extension";
|
||||
import { isProduction } from "../../../common/vars";
|
||||
import type { SemVer } from "semver";
|
||||
|
||||
interface Dependencies {
|
||||
appSemVer: SemVer;
|
||||
}
|
||||
|
||||
export const isCompatibleBundledExtension =
|
||||
({ appSemVer }: Dependencies) =>
|
||||
(manifest: LensExtensionManifest): boolean =>
|
||||
!isProduction || manifest.version === appSemVer.raw;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { appSemVer } from "../../../common/vars";
|
||||
import { isCompatibleExtension } from "./is-compatible-extension";
|
||||
|
||||
const isCompatibleExtensionInjectable = getInjectable({
|
||||
instantiate: () => isCompatibleExtension({ appSemVer }),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default isCompatibleExtensionInjectable;
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import semver, { SemVer } from "semver";
|
||||
import type { LensExtensionManifest } from "../../lens-extension";
|
||||
|
||||
interface Dependencies {
|
||||
appSemVer: SemVer;
|
||||
}
|
||||
|
||||
export const isCompatibleExtension = ({
|
||||
appSemVer,
|
||||
}: Dependencies): ((manifest: LensExtensionManifest) => boolean) => {
|
||||
const { major, minor, patch, prerelease: oldPrelease } = appSemVer;
|
||||
let prerelease = "";
|
||||
|
||||
if (oldPrelease.length > 0) {
|
||||
const [first] = oldPrelease;
|
||||
|
||||
if (first === "alpha" || first === "beta" || first === "rc") {
|
||||
/**
|
||||
* Strip the build IDs and "latest" prerelease tag as that is not really
|
||||
* a part of API version
|
||||
*/
|
||||
prerelease = `-${oldPrelease.slice(0, 2).join(".")}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We unfortunately have to format as string because the constructor only
|
||||
* takes an instance or a string.
|
||||
*/
|
||||
const strippedVersion = new SemVer(
|
||||
`${major}.${minor}.${patch}${prerelease}`,
|
||||
{ includePrerelease: true },
|
||||
);
|
||||
|
||||
return (manifest: LensExtensionManifest): boolean => {
|
||||
if (manifest.engines?.lens) {
|
||||
/**
|
||||
* include Lens's prerelease tag in the matching so the extension's
|
||||
* compatibility is not limited by it
|
||||
*/
|
||||
return semver.satisfies(strippedVersion, manifest.engines.lens, {
|
||||
includePrerelease: true,
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { ExtensionInstallationStateStore } from "./extension-installation-state-store";
|
||||
|
||||
const extensionInstallationStateStoreInjectable = getInjectable({
|
||||
instantiate: () => new ExtensionInstallationStateStore(),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default extensionInstallationStateStoreInjectable;
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { action, computed, observable } from "mobx";
|
||||
import logger from "../../main/logger";
|
||||
import { disposer } from "../../renderer/utils";
|
||||
import type { ExtendableDisposer } from "../../renderer/utils";
|
||||
import * as uuid from "uuid";
|
||||
import { broadcastMessage } from "../../common/ipc";
|
||||
import { ipcRenderer } from "electron";
|
||||
|
||||
export enum ExtensionInstallationState {
|
||||
INSTALLING = "installing",
|
||||
UNINSTALLING = "uninstalling",
|
||||
IDLE = "idle",
|
||||
}
|
||||
|
||||
const Prefix = "[ExtensionInstallationStore]";
|
||||
|
||||
export class ExtensionInstallationStateStore {
|
||||
private InstallingFromMainChannel =
|
||||
"extension-installation-state-store:install";
|
||||
|
||||
private ClearInstallingFromMainChannel =
|
||||
"extension-installation-state-store:clear-install";
|
||||
|
||||
private PreInstallIds = observable.set<string>();
|
||||
private UninstallingExtensions = observable.set<string>();
|
||||
private InstallingExtensions = observable.set<string>();
|
||||
|
||||
bindIpcListeners = () => {
|
||||
ipcRenderer
|
||||
.on(this.InstallingFromMainChannel, (event, extId) => {
|
||||
this.setInstalling(extId);
|
||||
})
|
||||
|
||||
.on(this.ClearInstallingFromMainChannel, (event, extId) => {
|
||||
this.clearInstalling(extId);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Strictly transitions an extension from not installing to installing
|
||||
* @param extId the ID of the extension
|
||||
* @throws if state is not IDLE
|
||||
*/
|
||||
@action setInstalling = (extId: string): void => {
|
||||
logger.debug(`${Prefix}: trying to set ${extId} as installing`);
|
||||
|
||||
const curState = this.getInstallationState(extId);
|
||||
|
||||
if (curState !== ExtensionInstallationState.IDLE) {
|
||||
throw new Error(
|
||||
`${Prefix}: cannot set ${extId} as installing. Is currently ${curState}.`,
|
||||
);
|
||||
}
|
||||
|
||||
this.InstallingExtensions.add(extId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Broadcasts that an extension is being installed by the main process
|
||||
* @param extId the ID of the extension
|
||||
*/
|
||||
setInstallingFromMain = (extId: string): void => {
|
||||
broadcastMessage(this.InstallingFromMainChannel, extId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Broadcasts that an extension is no longer being installed by the main process
|
||||
* @param extId the ID of the extension
|
||||
*/
|
||||
clearInstallingFromMain = (extId: string): void => {
|
||||
broadcastMessage(this.ClearInstallingFromMainChannel, extId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Marks the start of a pre-install phase of an extension installation. The
|
||||
* part of the installation before the tarball has been unpacked and the ID
|
||||
* determined.
|
||||
* @returns a disposer which should be called to mark the end of the install phase
|
||||
*/
|
||||
@action startPreInstall = (): ExtendableDisposer => {
|
||||
const preInstallStepId = uuid.v4();
|
||||
|
||||
logger.debug(
|
||||
`${Prefix}: starting a new preinstall phase: ${preInstallStepId}`,
|
||||
);
|
||||
this.PreInstallIds.add(preInstallStepId);
|
||||
|
||||
return disposer(() => {
|
||||
this.PreInstallIds.delete(preInstallStepId);
|
||||
logger.debug(`${Prefix}: ending a preinstall phase: ${preInstallStepId}`);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Strictly transitions an extension from not uninstalling to uninstalling
|
||||
* @param extId the ID of the extension
|
||||
* @throws if state is not IDLE
|
||||
*/
|
||||
@action setUninstalling = (extId: string): void => {
|
||||
logger.debug(`${Prefix}: trying to set ${extId} as uninstalling`);
|
||||
|
||||
const curState = this.getInstallationState(extId);
|
||||
|
||||
if (curState !== ExtensionInstallationState.IDLE) {
|
||||
throw new Error(
|
||||
`${Prefix}: cannot set ${extId} as uninstalling. Is currently ${curState}.`,
|
||||
);
|
||||
}
|
||||
|
||||
this.UninstallingExtensions.add(extId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Strictly clears the INSTALLING state of an extension
|
||||
* @param extId The ID of the extension
|
||||
* @throws if state is not INSTALLING
|
||||
*/
|
||||
@action clearInstalling = (extId: string): void => {
|
||||
logger.debug(`${Prefix}: trying to clear ${extId} as installing`);
|
||||
|
||||
const curState = this.getInstallationState(extId);
|
||||
|
||||
switch (curState) {
|
||||
case ExtensionInstallationState.INSTALLING:
|
||||
return void this.InstallingExtensions.delete(extId);
|
||||
default:
|
||||
throw new Error(
|
||||
`${Prefix}: cannot clear INSTALLING state for ${extId}, it is currently ${curState}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Strictly clears the UNINSTALLING state of an extension
|
||||
* @param extId The ID of the extension
|
||||
* @throws if state is not UNINSTALLING
|
||||
*/
|
||||
@action clearUninstalling = (extId: string): void => {
|
||||
logger.debug(`${Prefix}: trying to clear ${extId} as uninstalling`);
|
||||
|
||||
const curState = this.getInstallationState(extId);
|
||||
|
||||
switch (curState) {
|
||||
case ExtensionInstallationState.UNINSTALLING:
|
||||
return void this.UninstallingExtensions.delete(extId);
|
||||
default:
|
||||
throw new Error(
|
||||
`${Prefix}: cannot clear UNINSTALLING state for ${extId}, it is currently ${curState}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current state of the extension. IDLE is default value.
|
||||
* @param extId The ID of the extension
|
||||
*/
|
||||
getInstallationState = (extId: string): ExtensionInstallationState => {
|
||||
if (this.InstallingExtensions.has(extId)) {
|
||||
return ExtensionInstallationState.INSTALLING;
|
||||
}
|
||||
|
||||
if (this.UninstallingExtensions.has(extId)) {
|
||||
return ExtensionInstallationState.UNINSTALLING;
|
||||
}
|
||||
|
||||
return ExtensionInstallationState.IDLE;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the extension is currently INSTALLING
|
||||
* @param extId The ID of the extension
|
||||
*/
|
||||
isExtensionInstalling = (extId: string): boolean =>
|
||||
this.getInstallationState(extId) === ExtensionInstallationState.INSTALLING;
|
||||
|
||||
/**
|
||||
* Returns true if the extension is currently UNINSTALLING
|
||||
* @param extId The ID of the extension
|
||||
*/
|
||||
isExtensionUninstalling = (extId: string): boolean =>
|
||||
this.getInstallationState(extId) ===
|
||||
ExtensionInstallationState.UNINSTALLING;
|
||||
|
||||
/**
|
||||
* Returns true if the extension is currently IDLE
|
||||
* @param extId The ID of the extension
|
||||
*/
|
||||
isExtensionIdle = (extId: string): boolean =>
|
||||
this.getInstallationState(extId) === ExtensionInstallationState.IDLE;
|
||||
|
||||
/**
|
||||
* The current number of extensions installing
|
||||
*/
|
||||
@computed get installing(): number {
|
||||
return this.InstallingExtensions.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current number of extensions uninstalling
|
||||
*/
|
||||
get uninstalling(): number {
|
||||
return this.UninstallingExtensions.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is at least one extension currently installing
|
||||
*/
|
||||
get anyInstalling(): boolean {
|
||||
return this.installing > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is at least one extension currently uninstalling
|
||||
*/
|
||||
get anyUninstalling(): boolean {
|
||||
return this.uninstalling > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current number of extensions preinstalling
|
||||
*/
|
||||
get preinstalling(): number {
|
||||
return this.PreInstallIds.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is at least one extension currently downloading
|
||||
*/
|
||||
get anyPreinstalling(): boolean {
|
||||
return this.preinstalling > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is at least one installing or preinstalling step taking place
|
||||
*/
|
||||
get anyPreInstallingOrInstalling(): boolean {
|
||||
return this.anyInstalling || this.anyPreinstalling;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { ExtensionInstaller } from "./extension-installer";
|
||||
import extensionPackageRootDirectoryInjectable from "./extension-package-root-directory/extension-package-root-directory.injectable";
|
||||
|
||||
const extensionInstallerInjectable = getInjectable({
|
||||
instantiate: (di) =>
|
||||
new ExtensionInstaller({
|
||||
extensionPackageRootDirectory: di.inject(
|
||||
extensionPackageRootDirectoryInjectable,
|
||||
),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default extensionInstallerInjectable;
|
||||
+14
-16
@@ -23,12 +23,14 @@ import AwaitLock from "await-lock";
|
||||
import child_process from "child_process";
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import logger from "../main/logger";
|
||||
import { extensionPackagesRoot } from "./extension-loader";
|
||||
import logger from "../../main/logger";
|
||||
import type { PackageJson } from "type-fest";
|
||||
|
||||
const logModule = "[EXTENSION-INSTALLER]";
|
||||
|
||||
interface Dependencies {
|
||||
extensionPackageRootDirectory: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs dependencies for extensions
|
||||
@@ -36,9 +38,7 @@ const logModule = "[EXTENSION-INSTALLER]";
|
||||
export class ExtensionInstaller {
|
||||
private installLock = new AwaitLock();
|
||||
|
||||
get extensionPackagesRoot() {
|
||||
return extensionPackagesRoot();
|
||||
}
|
||||
constructor(private dependencies: Dependencies) {}
|
||||
|
||||
get npmPath() {
|
||||
return __non_webpack_require__.resolve("npm/bin/npm-cli");
|
||||
@@ -47,7 +47,7 @@ export class ExtensionInstaller {
|
||||
/**
|
||||
* Write package.json to the file system and execute npm install for it.
|
||||
*/
|
||||
async installPackages(packageJsonPath: string, packagesJson: PackageJson): Promise<void> {
|
||||
installPackages = async (packageJsonPath: string, packagesJson: PackageJson): Promise<void> => {
|
||||
// Mutual exclusion to install packages in sequence
|
||||
await this.installLock.acquireAsync();
|
||||
|
||||
@@ -57,34 +57,34 @@ export class ExtensionInstaller {
|
||||
mode: 0o600,
|
||||
});
|
||||
|
||||
logger.info(`${logModule} installing dependencies at ${extensionPackagesRoot()}`);
|
||||
logger.info(`${logModule} installing dependencies at ${this.dependencies.extensionPackageRootDirectory}`);
|
||||
await this.npm(["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock"]);
|
||||
logger.info(`${logModule} dependencies installed at ${extensionPackagesRoot()}`);
|
||||
logger.info(`${logModule} dependencies installed at ${this.dependencies.extensionPackageRootDirectory}`);
|
||||
} finally {
|
||||
this.installLock.release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Install single package using npm
|
||||
*/
|
||||
async installPackage(name: string): Promise<void> {
|
||||
installPackage = async (name: string): Promise<void> => {
|
||||
// Mutual exclusion to install packages in sequence
|
||||
await this.installLock.acquireAsync();
|
||||
|
||||
try {
|
||||
logger.info(`${logModule} installing package from ${name} to ${extensionPackagesRoot()}`);
|
||||
logger.info(`${logModule} installing package from ${name} to ${this.dependencies.extensionPackageRootDirectory}`);
|
||||
await this.npm(["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock", "--no-save", name]);
|
||||
logger.info(`${logModule} package ${name} installed to ${extensionPackagesRoot()}`);
|
||||
logger.info(`${logModule} package ${name} installed to ${this.dependencies.extensionPackageRootDirectory}`);
|
||||
} finally {
|
||||
this.installLock.release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private npm(args: string[]): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = child_process.fork(this.npmPath, args, {
|
||||
cwd: extensionPackagesRoot(),
|
||||
cwd: this.dependencies.extensionPackageRootDirectory,
|
||||
silent: true,
|
||||
env: {},
|
||||
});
|
||||
@@ -108,5 +108,3 @@ export class ExtensionInstaller {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const extensionInstaller = new ExtensionInstaller();
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
|
||||
const extensionPackageRootDirectoryInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(directoryForUserDataInjectable),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default extensionPackageRootDirectoryInjectable;
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import extensionInstallerInjectable from "../extension-installer.injectable";
|
||||
|
||||
const installExtensionInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(extensionInstallerInjectable).installPackage,
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default installExtensionInjectable;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import extensionInstallerInjectable from "../extension-installer.injectable";
|
||||
|
||||
const installExtensionsInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(extensionInstallerInjectable).installPackages,
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default installExtensionsInjectable;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { createExtensionInstance } from "./create-extension-instance";
|
||||
import fileSystemProvisionerStoreInjectable from "./file-system-provisioner-store/file-system-provisioner-store.injectable";
|
||||
|
||||
const createExtensionInstanceInjectable = getInjectable({
|
||||
instantiate: (di) => createExtensionInstance({
|
||||
fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createExtensionInstanceInjectable;
|
||||
+12
-29
@@ -18,36 +18,19 @@
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import glob from "glob";
|
||||
import { memoize } from "lodash/fp";
|
||||
|
||||
import type { LensExtensionConstructor } from "../../lens-extension";
|
||||
import type { InstalledExtension } from "../../extension-discovery/extension-discovery";
|
||||
import {
|
||||
createContainer,
|
||||
ConfigurableDependencyInjectionContainer,
|
||||
} from "@ogre-tools/injectable";
|
||||
LensExtensionDependencies,
|
||||
setLensExtensionDependencies,
|
||||
} from "../../lens-extension-set-dependencies";
|
||||
|
||||
export const getDiForUnitTesting = () => {
|
||||
const di: ConfigurableDependencyInjectionContainer = createContainer();
|
||||
export const createExtensionInstance =
|
||||
(dependencies: LensExtensionDependencies) =>
|
||||
(ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => {
|
||||
const instance = new ExtensionClass(extension);
|
||||
|
||||
getInjectableFilePaths()
|
||||
.map(key => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const injectable = require(key).default;
|
||||
instance[setLensExtensionDependencies](dependencies);
|
||||
|
||||
return {
|
||||
id: key,
|
||||
...injectable,
|
||||
aliases: [injectable, ...(injectable.aliases || [])],
|
||||
};
|
||||
})
|
||||
.forEach(injectable => di.register(injectable));
|
||||
|
||||
di.preventSideEffects();
|
||||
|
||||
return di;
|
||||
};
|
||||
|
||||
const getInjectableFilePaths = memoize(() => [
|
||||
...glob.sync("./**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||
]);
|
||||
return instance;
|
||||
};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
|
||||
const directoryForExtensionDataInjectable = getInjectable({
|
||||
instantiate: (di) =>
|
||||
path.join(di.inject(directoryForUserDataInjectable), "extension_data"),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default directoryForExtensionDataInjectable;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { FileSystemProvisionerStore } from "./file-system-provisioner-store";
|
||||
import directoryForExtensionDataInjectable from "./directory-for-extension-data/directory-for-extension-data.injectable";
|
||||
|
||||
const fileSystemProvisionerStoreInjectable = getInjectable({
|
||||
instantiate: (di) =>
|
||||
FileSystemProvisionerStore.createInstance({
|
||||
directoryForExtensionData: di.inject(
|
||||
directoryForExtensionDataInjectable,
|
||||
),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default fileSystemProvisionerStoreInjectable;
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { randomBytes } from "crypto";
|
||||
import { SHA256 } from "crypto-js";
|
||||
import fse from "fs-extra";
|
||||
import { action, makeObservable, observable } from "mobx";
|
||||
import path from "path";
|
||||
import { BaseStore } from "../../../../common/base-store";
|
||||
import type { LensExtensionId } from "../../../lens-extension";
|
||||
import { toJS } from "../../../../common/utils";
|
||||
|
||||
interface FSProvisionModel {
|
||||
extensions: Record<string, string>; // extension names to paths
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
directoryForExtensionData: string
|
||||
}
|
||||
|
||||
export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||
readonly displayName = "FilesystemProvisionerStore";
|
||||
registeredExtensions = observable.map<LensExtensionId, string>();
|
||||
|
||||
constructor(private dependencies: Dependencies) {
|
||||
super({
|
||||
configName: "lens-filesystem-provisioner-store",
|
||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||
});
|
||||
|
||||
makeObservable(this);
|
||||
this.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function retrieves the saved path to the folder which the extension
|
||||
* can saves files to. If the folder is not present then it is created.
|
||||
* @param extensionName the name of the extension requesting the path
|
||||
* @returns path to the folder that the extension can safely write files to.
|
||||
*/
|
||||
async requestDirectory(extensionName: string): Promise<string> {
|
||||
if (!this.registeredExtensions.has(extensionName)) {
|
||||
const salt = randomBytes(32).toString("hex");
|
||||
const hashedName = SHA256(`${extensionName}/${salt}`).toString();
|
||||
|
||||
const dirPath = path.resolve(this.dependencies.directoryForExtensionData, hashedName);
|
||||
|
||||
this.registeredExtensions.set(extensionName, dirPath);
|
||||
}
|
||||
|
||||
const dirPath = this.registeredExtensions.get(extensionName);
|
||||
|
||||
await fse.ensureDir(dirPath);
|
||||
|
||||
return dirPath;
|
||||
}
|
||||
|
||||
@action
|
||||
protected fromStore({ extensions }: FSProvisionModel = { extensions: {}}): void {
|
||||
this.registeredExtensions.merge(extensions);
|
||||
}
|
||||
|
||||
toJSON(): FSProvisionModel {
|
||||
return toJS({
|
||||
extensions: Object.fromEntries(this.registeredExtensions),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,17 @@
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { ExtensionLoader } from "./extension-loader";
|
||||
import updateExtensionsStateInjectable from "./update-extensions-state/update-extensions-state.injectable";
|
||||
import createExtensionInstanceInjectable
|
||||
from "./create-extension-instance/create-extension-instance.injectable";
|
||||
|
||||
const extensionLoaderInjectable = getInjectable({
|
||||
instantiate: () => new ExtensionLoader(),
|
||||
instantiate: (di) =>
|
||||
new ExtensionLoader({
|
||||
updateExtensionsState: di.inject(updateExtensionsStateInjectable),
|
||||
createExtensionInstance: di.inject(createExtensionInstanceInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
|
||||
@@ -24,27 +24,32 @@ import { EventEmitter } from "events";
|
||||
import { isEqual } from "lodash";
|
||||
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
|
||||
import path from "path";
|
||||
import { AppPaths } from "../../common/app-paths";
|
||||
import { broadcastMessage, ipcMainOn, ipcRendererOn, requestMain, ipcMainHandle } from "../../common/ipc";
|
||||
import { Disposer, toJS } from "../../common/utils";
|
||||
import logger from "../../main/logger";
|
||||
import type { KubernetesCluster } from "../common-api/catalog";
|
||||
import type { InstalledExtension } from "../extension-discovery";
|
||||
import { ExtensionsStore } from "../extensions-store";
|
||||
import { GitHubVersionChecker } from "../github-latest-version-checker";
|
||||
import type { InstalledExtension } from "../extension-discovery/extension-discovery";
|
||||
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "../lens-extension";
|
||||
import { LensExtensionUpdateChecker } from "../lens-extension-update-checker";
|
||||
import type { LensMainExtension } from "../lens-main-extension";
|
||||
import type { LensRendererExtension } from "../lens-renderer-extension";
|
||||
import { NpmJsVersionChecker } from "../npmjs-latest-version.checker";
|
||||
import * as registries from "../registries";
|
||||
|
||||
export function extensionPackagesRoot() {
|
||||
return path.join(AppPaths.get("userData"));
|
||||
}
|
||||
import type { LensExtensionState } from "../extensions-store/extensions-store";
|
||||
|
||||
const logModule = "[EXTENSIONS-LOADER]";
|
||||
|
||||
interface Dependencies {
|
||||
updateExtensionsState: (extensionsState: Record<LensExtensionId, LensExtensionState>) => void
|
||||
createExtensionInstance: (ExtensionClass: LensExtensionConstructor, extension: InstalledExtension, updateChecker: LensExtensionUpdateChecker) => LensExtension,
|
||||
}
|
||||
|
||||
export interface ExtensionLoading {
|
||||
isBundled: boolean,
|
||||
loaded: Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads installed extensions to the Lens application
|
||||
*/
|
||||
@@ -85,9 +90,10 @@ export class ExtensionLoader {
|
||||
return when(() => this.isLoaded);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
constructor(protected dependencies : Dependencies) {
|
||||
this.extensionUpdateChecker = new LensExtensionUpdateChecker(this.extensionUpdateSources);
|
||||
makeObservable(this);
|
||||
|
||||
observe(this.instances, change => {
|
||||
switch (change.type) {
|
||||
case "add":
|
||||
@@ -165,10 +171,13 @@ export class ExtensionLoader {
|
||||
fireImmediately: true,
|
||||
});
|
||||
|
||||
// save state on change `extension.isEnabled`
|
||||
reaction(() => this.storeState, extensionsState => {
|
||||
ExtensionsStore.getInstance().mergeState(extensionsState);
|
||||
});
|
||||
reaction(
|
||||
() => this.storeState,
|
||||
|
||||
(state) => {
|
||||
this.dependencies.updateExtensionsState(state);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
initExtensions(extensions?: Map<LensExtensionId, InstalledExtension>) {
|
||||
@@ -270,7 +279,7 @@ export class ExtensionLoader {
|
||||
});
|
||||
}
|
||||
|
||||
loadOnClusterManagerRenderer() {
|
||||
loadOnClusterManagerRenderer = () => {
|
||||
logger.debug(`${logModule}: load on main renderer (cluster manager)`);
|
||||
|
||||
return this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
||||
@@ -294,9 +303,9 @@ export class ExtensionLoader {
|
||||
|
||||
return removeItems;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
loadOnClusterRenderer(entity: KubernetesCluster) {
|
||||
loadOnClusterRenderer = (entity: KubernetesCluster) => {
|
||||
logger.debug(`${logModule}: load on cluster renderer (dashboard)`);
|
||||
|
||||
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
||||
@@ -323,16 +332,16 @@ export class ExtensionLoader {
|
||||
|
||||
return removeItems;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
protected async checkForExtensionUpdate(extension: LensExtension) {
|
||||
this.extensions.get(extension.id).availableUpdate = await extension.checkForUpdate();
|
||||
}
|
||||
|
||||
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Disposer[]>) {
|
||||
const loadingExtensions: { isBundled: boolean, loaded: Promise<void> }[] = [];
|
||||
const loadingExtensions: ExtensionLoading[] = [];
|
||||
|
||||
reaction(() => this.toJSON(), installedExtensions => {
|
||||
reaction(() => this.toJSON(), async installedExtensions => {
|
||||
for (const [extId, extension] of installedExtensions) {
|
||||
const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name);
|
||||
|
||||
@@ -345,7 +354,12 @@ export class ExtensionLoader {
|
||||
continue;
|
||||
}
|
||||
|
||||
const instance = new LensExtensionClass(extension, this.extensionUpdateChecker);
|
||||
// const instance = new LensExtensionClass(extension, this.extensionUpdateChecker);
|
||||
const instance = this.dependencies.createExtensionInstance(
|
||||
LensExtensionClass,
|
||||
extension,
|
||||
this.extensionUpdateChecker
|
||||
);
|
||||
|
||||
const loaded = instance.enable(register).catch((err) => {
|
||||
logger.error(`${logModule}: failed to enable`, { ext: extension, err });
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import extensionsStoreInjectable from "../../extensions-store/extensions-store.injectable";
|
||||
|
||||
const updateExtensionsStateInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(extensionsStoreInjectable).mergeState,
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default updateExtensionsStateInjectable;
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
|
||||
const extensionPackagesRootInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(directoryForUserDataInjectable),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default extensionPackagesRootInjectable;
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { ExtensionsStore } from "./extensions-store";
|
||||
|
||||
const extensionsStoreInjectable = getInjectable({
|
||||
instantiate: () => ExtensionsStore.createInstance(),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default extensionsStoreInjectable;
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import type { LensExtensionId } from "./lens-extension";
|
||||
import { BaseStore } from "../common/base-store";
|
||||
import { action, computed, observable, makeObservable } from "mobx";
|
||||
import { toJS } from "../common/utils";
|
||||
import type { LensExtensionId } from "../lens-extension";
|
||||
import { action, computed, makeObservable, observable } from "mobx";
|
||||
import { toJS } from "../../common/utils";
|
||||
import { BaseStore } from "../../common/base-store";
|
||||
|
||||
export interface LensExtensionsStoreModel {
|
||||
extensions: Record<LensExtensionId, LensExtensionState>;
|
||||
@@ -59,9 +59,9 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
mergeState(extensionsState: Record<LensExtensionId, LensExtensionState>) {
|
||||
mergeState = (extensionsState: Record<LensExtensionId, LensExtensionState>) => {
|
||||
this.state.merge(extensionsState);
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
protected fromStore({ extensions }: LensExtensionsStoreModel) {
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import type { FileSystemProvisionerStore } from "./extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store";
|
||||
|
||||
// This symbol encapsulates setting of dependencies to only happen locally in Lens Core
|
||||
// and not by e.g. authors of extensions
|
||||
export const setLensExtensionDependencies = Symbol("set-lens-extension-dependencies");
|
||||
|
||||
export interface LensExtensionDependencies {
|
||||
fileSystemProvisionerStore: FileSystemProvisionerStore
|
||||
}
|
||||
@@ -19,14 +19,17 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import type { InstalledExtension } from "./extension-discovery";
|
||||
import type { InstalledExtension } from "./extension-discovery/extension-discovery";
|
||||
import { action, observable, makeObservable, computed } from "mobx";
|
||||
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
|
||||
import logger from "../main/logger";
|
||||
import type { ProtocolHandlerRegistration } from "./registries";
|
||||
import type { PackageJson } from "type-fest";
|
||||
import { Disposer, disposer } from "../common/utils";
|
||||
import type { LensExtensionUpdateChecker } from "./lens-extension-update-checker";
|
||||
import {
|
||||
LensExtensionDependencies,
|
||||
setLensExtensionDependencies,
|
||||
} from "./lens-extension-set-dependencies";
|
||||
|
||||
export type LensExtensionId = string; // path to manifest (package.json)
|
||||
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
|
||||
@@ -80,6 +83,12 @@ export class LensExtension {
|
||||
return this.manifest.description;
|
||||
}
|
||||
|
||||
private dependencies: LensExtensionDependencies;
|
||||
|
||||
[setLensExtensionDependencies] = (dependencies: LensExtensionDependencies) => {
|
||||
this.dependencies = dependencies;
|
||||
};
|
||||
|
||||
/**
|
||||
* getExtensionFileFolder returns the path to an already created folder. This
|
||||
* folder is for the sole use of this extension.
|
||||
@@ -88,7 +97,7 @@ export class LensExtension {
|
||||
* folder name.
|
||||
*/
|
||||
async getExtensionFileFolder(): Promise<string> {
|
||||
return FilesystemProvisionerStore.getInstance().requestDirectory(this.id);
|
||||
return this.dependencies.fileSystemProvisionerStore.requestDirectory(this.id);
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { observable } from "mobx"
|
||||
import { PrometheusProvider } from "../main/prometheus/provider-registry";
|
||||
|
||||
export enum MetricsProviderType {
|
||||
PROMETHEUS = "prometheus",
|
||||
CUSTOM = "custom",
|
||||
}
|
||||
|
||||
export interface MetricsProviderRegistration {
|
||||
id: string;
|
||||
external?: boolean;
|
||||
type: MetricsProviderType;
|
||||
provider: PrometheusProvider;
|
||||
}
|
||||
|
||||
export class MetricsProviderRegistry {
|
||||
registry = observable.array<MetricsProviderRegistration>([], { deep: false });
|
||||
|
||||
add(provider: MetricsProviderRegistration) {
|
||||
console.log("Adding provider" + provider.id)
|
||||
this.registry.push(provider)
|
||||
return () => {
|
||||
this.registry.replace(
|
||||
this.registry.filter(p => p.id !== provider.id)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
JSON.stringify(this.registry)
|
||||
}
|
||||
|
||||
getInternalPrometheusProviders(): MetricsProviderRegistration[] {
|
||||
console.log(this.registry.toJSON())
|
||||
return this.registry.filter(p => p.type === MetricsProviderType.PROMETHEUS && !p.external)
|
||||
}
|
||||
}
|
||||
|
||||
export const metricsProviderRegistry = new MetricsProviderRegistry()
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import _ from "lodash";
|
||||
import { SemVer } from "semver";
|
||||
import URLParse from "url-parse";
|
||||
import { DownloadFileOptions, downloadJson } from "../common/utils";
|
||||
import type { LensExtensionManifest } from "./lens-extension";
|
||||
import type { LensExtensionLatestVersionChecker } from "./lens-extension-latest-version-checker";
|
||||
|
||||
export class NpmJsVersionChecker implements LensExtensionLatestVersionChecker {
|
||||
protected downloadJson;
|
||||
|
||||
constructor(downloadJsonOverride?: (args: DownloadFileOptions) => any) {
|
||||
this.downloadJson = downloadJsonOverride || downloadJson;
|
||||
}
|
||||
|
||||
public async getLatestVersion(manifest: LensExtensionManifest, isBundled: boolean) {
|
||||
if (!isBundled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { name } = manifest;
|
||||
const registryUrl = new URLParse("https://registry.npmjs.com").set("pathname", name).toString();
|
||||
const json = await this.getJson(registryUrl);
|
||||
|
||||
if (!json || json.error || typeof json.versions !== "object" || !json.versions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO refactor into helpoer method
|
||||
const versions = Object.keys(json.versions)
|
||||
.map(version => new SemVer(version, { loose: true, includePrerelease: true }))
|
||||
// ignore pre-releases for auto picking the version
|
||||
.filter(version => version.prerelease.length === 0);
|
||||
|
||||
const version = _.reduce(versions, (prev, curr) => (
|
||||
prev.compareMain(curr) === -1
|
||||
? curr
|
||||
: prev
|
||||
)).format();
|
||||
|
||||
return {
|
||||
input: name,
|
||||
version,
|
||||
};
|
||||
}
|
||||
|
||||
protected async getJson(url: string) {
|
||||
const { promise } = this.downloadJson({ url });
|
||||
const json = await promise.catch(() => {
|
||||
// do nothing
|
||||
});
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,10 @@ import React from "react";
|
||||
import fse from "fs-extra";
|
||||
import { Console } from "console";
|
||||
import { stderr, stdout } from "process";
|
||||
import { TerminalStore } from "../../../renderer/components/dock/terminal.store";
|
||||
import { ThemeStore } from "../../../renderer/theme.store";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import { AppPaths } from "../../../common/app-paths";
|
||||
import { getDisForUnitTesting } from "../../../test-utils/get-dis-for-unit-testing";
|
||||
import mockFs from "mock-fs";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
@@ -47,14 +47,18 @@ jest.mock("electron", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
AppPaths.init();
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
let ext: LensExtension = null;
|
||||
|
||||
describe("page registry tests", () => {
|
||||
beforeEach(async () => {
|
||||
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
mockFs();
|
||||
|
||||
await dis.runSetups();
|
||||
|
||||
ext = new LensExtension({
|
||||
manifest: {
|
||||
name: "foo-bar",
|
||||
@@ -69,7 +73,6 @@ describe("page registry tests", () => {
|
||||
});
|
||||
UserStore.createInstance();
|
||||
ThemeStore.createInstance();
|
||||
TerminalStore.createInstance();
|
||||
ClusterPageRegistry.createInstance();
|
||||
GlobalPageRegistry.createInstance().add({
|
||||
id: "page-with-params",
|
||||
@@ -105,10 +108,10 @@ describe("page registry tests", () => {
|
||||
afterEach(() => {
|
||||
GlobalPageRegistry.resetInstance();
|
||||
ClusterPageRegistry.resetInstance();
|
||||
TerminalStore.resetInstance();
|
||||
ThemeStore.resetInstance();
|
||||
UserStore.resetInstance();
|
||||
fse.remove("tmp");
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
describe("getPageUrl", () => {
|
||||
|
||||
@@ -18,9 +18,15 @@
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||
import createTerminalTabInjectable from "../../renderer/components/dock/create-terminal-tab/create-terminal-tab.injectable";
|
||||
import terminalStoreInjectable from "../../renderer/components/dock/terminal-store/terminal-store.injectable";
|
||||
import { asLegacyGlobalObjectForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import logTabStoreInjectable from "../../renderer/components/dock/log-tab-store/log-tab-store.injectable";
|
||||
import { asLegacyGlobalSingletonForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-singleton-for-extension-api";
|
||||
import { TerminalStore as TerminalStoreClass } from "../../renderer/components/dock/terminal-store/terminal.store";
|
||||
|
||||
import commandOverlayInjectable from "../../renderer/components/command-palette/command-overlay.injectable";
|
||||
import { asLegacyGlobalObjectForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
|
||||
// layouts
|
||||
export * from "../../renderer/components/layout/main-layout";
|
||||
@@ -74,5 +80,9 @@ export * from "../../renderer/components/+events/kube-event-details";
|
||||
|
||||
// specific exports
|
||||
export * from "../../renderer/components/status-brick";
|
||||
export { terminalStore, createTerminalTab, TerminalStore } from "../../renderer/components/dock/terminal.store";
|
||||
export { logTabStore } from "../../renderer/components/dock/log-tab.store";
|
||||
|
||||
export const createTerminalTab = asLegacyGlobalFunctionForExtensionApi(createTerminalTabInjectable);
|
||||
export const TerminalStore = asLegacyGlobalSingletonForExtensionApi(TerminalStoreClass, terminalStoreInjectable);
|
||||
export const terminalStore = asLegacyGlobalObjectForExtensionApi(terminalStoreInjectable);
|
||||
export const logTabStore = asLegacyGlobalObjectForExtensionApi(logTabStoreInjectable);
|
||||
|
||||
@@ -86,7 +86,7 @@ export type { NetworkPolicyStore } from "../../renderer/components/+network-poli
|
||||
export type { PersistentVolumesStore } from "../../renderer/components/+storage-volumes/volumes.store";
|
||||
export type { VolumeClaimStore } from "../../renderer/components/+storage-volume-claims/volume-claim.store";
|
||||
export type { StorageClassStore } from "../../renderer/components/+storage-classes/storage-class.store";
|
||||
export type { NamespaceStore } from "../../renderer/components/+namespaces/namespace.store";
|
||||
export type { NamespaceStore } from "../../renderer/components/+namespaces/namespace-store/namespace.store";
|
||||
export type { ServiceAccountsStore } from "../../renderer/components/+user-management/+service-accounts/store";
|
||||
export type { RolesStore } from "../../renderer/components/+user-management/+roles/store";
|
||||
export type { RoleBindingsStore } from "../../renderer/components/+user-management/+role-bindings/store";
|
||||
|
||||
Reference in new issue
Block a user