mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into run-tests-on-win
This commit is contained in:
commit
352dfd9b25
@ -4,6 +4,7 @@ module.exports = {
|
|||||||
ignorePatterns: [
|
ignorePatterns: [
|
||||||
"**/node_modules/**/*",
|
"**/node_modules/**/*",
|
||||||
"**/dist/**/*",
|
"**/dist/**/*",
|
||||||
|
"**/static/**/*",
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
|
|||||||
2
extensions/example-extension/package-lock.json
generated
2
extensions/example-extension/package-lock.json
generated
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "extension-example",
|
"name": "example-extension",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
|
|||||||
@ -1594,6 +1594,7 @@ msgstr "Names"
|
|||||||
#: src/renderer/components/dock/upgrade-chart.tsx:98
|
#: src/renderer/components/dock/upgrade-chart.tsx:98
|
||||||
#: src/renderer/components/item-object-list/page-filters-select.tsx:57
|
#: src/renderer/components/item-object-list/page-filters-select.tsx:57
|
||||||
#: src/renderer/components/kube-object/kube-object-meta.tsx:23
|
#: src/renderer/components/kube-object/kube-object-meta.tsx:23
|
||||||
|
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:144
|
||||||
msgid "Namespace"
|
msgid "Namespace"
|
||||||
msgstr "Namespace"
|
msgstr "Namespace"
|
||||||
|
|
||||||
@ -2003,6 +2004,10 @@ msgstr "Read-only Root Filesystem"
|
|||||||
msgid "Readiness"
|
msgid "Readiness"
|
||||||
msgstr "Readiness"
|
msgstr "Readiness"
|
||||||
|
|
||||||
|
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:145
|
||||||
|
msgid "Ready"
|
||||||
|
msgstr "Ready"
|
||||||
|
|
||||||
#: src/renderer/components/+events/event-details.tsx:32
|
#: src/renderer/components/+events/event-details.tsx:32
|
||||||
#: src/renderer/components/+workloads-pods/pod-details-container.tsx:25
|
#: src/renderer/components/+workloads-pods/pod-details-container.tsx:25
|
||||||
msgid "Reason"
|
msgid "Reason"
|
||||||
|
|||||||
@ -1585,6 +1585,7 @@ msgstr ""
|
|||||||
#: src/renderer/components/dock/upgrade-chart.tsx:98
|
#: src/renderer/components/dock/upgrade-chart.tsx:98
|
||||||
#: src/renderer/components/item-object-list/page-filters-select.tsx:57
|
#: src/renderer/components/item-object-list/page-filters-select.tsx:57
|
||||||
#: src/renderer/components/kube-object/kube-object-meta.tsx:23
|
#: src/renderer/components/kube-object/kube-object-meta.tsx:23
|
||||||
|
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:144
|
||||||
msgid "Namespace"
|
msgid "Namespace"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1986,6 +1987,10 @@ msgstr ""
|
|||||||
msgid "Readiness"
|
msgid "Readiness"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:145
|
||||||
|
msgid "Ready"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/renderer/components/+events/event-details.tsx:32
|
#: src/renderer/components/+events/event-details.tsx:32
|
||||||
#: src/renderer/components/+workloads-pods/pod-details-container.tsx:25
|
#: src/renderer/components/+workloads-pods/pod-details-container.tsx:25
|
||||||
msgid "Reason"
|
msgid "Reason"
|
||||||
|
|||||||
@ -1595,6 +1595,7 @@ msgstr ""
|
|||||||
#: src/renderer/components/dock/upgrade-chart.tsx:98
|
#: src/renderer/components/dock/upgrade-chart.tsx:98
|
||||||
#: src/renderer/components/item-object-list/page-filters-select.tsx:57
|
#: src/renderer/components/item-object-list/page-filters-select.tsx:57
|
||||||
#: src/renderer/components/kube-object/kube-object-meta.tsx:23
|
#: src/renderer/components/kube-object/kube-object-meta.tsx:23
|
||||||
|
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:144
|
||||||
msgid "Namespace"
|
msgid "Namespace"
|
||||||
msgstr "Namespace"
|
msgstr "Namespace"
|
||||||
|
|
||||||
@ -2004,6 +2005,10 @@ msgstr ""
|
|||||||
msgid "Readiness"
|
msgid "Readiness"
|
||||||
msgstr "Готовность"
|
msgstr "Готовность"
|
||||||
|
|
||||||
|
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:145
|
||||||
|
msgid "Ready"
|
||||||
|
msgstr "Готовы"
|
||||||
|
|
||||||
#: src/renderer/components/+events/event-details.tsx:32
|
#: src/renderer/components/+events/event-details.tsx:32
|
||||||
#: src/renderer/components/+workloads-pods/pod-details-container.tsx:25
|
#: src/renderer/components/+workloads-pods/pod-details-container.tsx:25
|
||||||
msgid "Reason"
|
msgid "Reason"
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
"download:kubectl": "yarn run ts-node build/download_kubectl.ts",
|
"download:kubectl": "yarn run ts-node build/download_kubectl.ts",
|
||||||
"download:helm": "yarn run ts-node build/download_helm.ts",
|
"download:helm": "yarn run ts-node build/download_helm.ts",
|
||||||
"build:tray-icons": "yarn run ts-node build/build_tray_icon.ts",
|
"build:tray-icons": "yarn run ts-node build/build_tray_icon.ts",
|
||||||
"lint": "yarn run eslint $@ --ext js,ts,tsx --max-warnings=0 src/ integration/ __mocks__/ build/ extensions/",
|
"lint": "yarn run eslint $@ --ext js,ts,tsx --max-warnings=0 .",
|
||||||
"lint:fix": "yarn run lint --fix",
|
"lint:fix": "yarn run lint --fix",
|
||||||
"mkdocs-serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
|
"mkdocs-serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
|
||||||
"verify-docs": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -v ${PWD}:/docs mkdocs-serve-local:latest build --strict",
|
"verify-docs": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -v ${PWD}:/docs mkdocs-serve-local:latest build --strict",
|
||||||
|
|||||||
@ -9,7 +9,7 @@ jest.mock(
|
|||||||
() => ({
|
() => ({
|
||||||
ipcRenderer: {
|
ipcRenderer: {
|
||||||
invoke: jest.fn(async (channel: string) => {
|
invoke: jest.fn(async (channel: string) => {
|
||||||
if (channel === "extensions:loaded") {
|
if (channel === "extensions:main") {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
manifestPath,
|
manifestPath,
|
||||||
@ -44,7 +44,7 @@ jest.mock(
|
|||||||
}),
|
}),
|
||||||
on: jest.fn(
|
on: jest.fn(
|
||||||
(channel: string, listener: (event: any, ...args: any[]) => void) => {
|
(channel: string, listener: (event: any, ...args: any[]) => void) => {
|
||||||
if (channel === "extensions:loaded") {
|
if (channel === "extensions:main") {
|
||||||
// First initialize with extensions 1 and 2
|
// First initialize with extensions 1 and 2
|
||||||
// and then broadcast event to remove extensioin 2 and add extension number 3
|
// and then broadcast event to remove extensioin 2 and add extension number 3
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { app, ipcRenderer, remote } from "electron";
|
import { app, ipcRenderer, remote } from "electron";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
import { isEqual } from "lodash";
|
||||||
import { action, computed, observable, reaction, toJS, when } from "mobx";
|
import { action, computed, observable, reaction, toJS, when } from "mobx";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { getHostedCluster } from "../common/cluster-store";
|
import { getHostedCluster } from "../common/cluster-store";
|
||||||
@ -25,7 +26,12 @@ const logModule = "[EXTENSIONS-LOADER]";
|
|||||||
export class ExtensionLoader {
|
export class ExtensionLoader {
|
||||||
protected extensions = observable.map<LensExtensionId, InstalledExtension>();
|
protected extensions = observable.map<LensExtensionId, InstalledExtension>();
|
||||||
protected instances = observable.map<LensExtensionId, LensExtension>();
|
protected instances = observable.map<LensExtensionId, LensExtension>();
|
||||||
protected readonly requestExtensionsChannel = "extensions:loaded";
|
|
||||||
|
// IPC channel to broadcast changes to extensions from main
|
||||||
|
protected static readonly extensionsMainChannel = "extensions:main";
|
||||||
|
|
||||||
|
// IPC channel to broadcast changes to extensions from renderer
|
||||||
|
protected static readonly extensionsRendererChannel = "extensions:renderer";
|
||||||
|
|
||||||
// emits event "remove" of type LensExtension when the extension is removed
|
// emits event "remove" of type LensExtension when the extension is removed
|
||||||
private events = new EventEmitter();
|
private events = new EventEmitter();
|
||||||
@ -95,28 +101,27 @@ export class ExtensionLoader {
|
|||||||
this.loadOnMain();
|
this.loadOnMain();
|
||||||
this.broadcastExtensions();
|
this.broadcastExtensions();
|
||||||
|
|
||||||
reaction(() => this.extensions.toJS(), () => {
|
reaction(() => this.toJSON(), () => {
|
||||||
this.broadcastExtensions();
|
this.broadcastExtensions();
|
||||||
});
|
});
|
||||||
|
|
||||||
handleRequest(this.requestExtensionsChannel, () => {
|
handleRequest(ExtensionLoader.extensionsMainChannel, () => {
|
||||||
return Array.from(this.toJSON());
|
return Array.from(this.toJSON());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
subscribeToBroadcast(ExtensionLoader.extensionsRendererChannel, (_event, extensions: [LensExtensionId, InstalledExtension][]) => {
|
||||||
|
this.syncExtensions(extensions);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initRenderer() {
|
protected async initRenderer() {
|
||||||
const extensionListHandler = (extensions: [LensExtensionId, InstalledExtension][]) => {
|
const extensionListHandler = (extensions: [LensExtensionId, InstalledExtension][]) => {
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
|
this.syncExtensions(extensions);
|
||||||
|
|
||||||
const receivedExtensionIds = extensions.map(([lensExtensionId]) => lensExtensionId);
|
const receivedExtensionIds = extensions.map(([lensExtensionId]) => lensExtensionId);
|
||||||
|
|
||||||
// Add new extensions
|
// Remove deleted extensions in renderer side only
|
||||||
extensions.forEach(([extId, ext]) => {
|
|
||||||
if (!this.extensions.has(extId)) {
|
|
||||||
this.extensions.set(extId, ext);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove deleted extensions
|
|
||||||
this.extensions.forEach((_, lensExtensionId) => {
|
this.extensions.forEach((_, lensExtensionId) => {
|
||||||
if (!receivedExtensionIds.includes(lensExtensionId)) {
|
if (!receivedExtensionIds.includes(lensExtensionId)) {
|
||||||
this.removeExtension(lensExtensionId);
|
this.removeExtension(lensExtensionId);
|
||||||
@ -124,14 +129,26 @@ export class ExtensionLoader {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
requestMain(this.requestExtensionsChannel).then(extensionListHandler);
|
reaction(() => this.toJSON(), () => {
|
||||||
subscribeToBroadcast(this.requestExtensionsChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => {
|
this.broadcastExtensions(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
requestMain(ExtensionLoader.extensionsMainChannel).then(extensionListHandler);
|
||||||
|
subscribeToBroadcast(ExtensionLoader.extensionsMainChannel, (_event, extensions: [LensExtensionId, InstalledExtension][]) => {
|
||||||
extensionListHandler(extensions);
|
extensionListHandler(extensions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncExtensions(extensions: [LensExtensionId, InstalledExtension][]) {
|
||||||
|
extensions.forEach(([lensExtensionId, extension]) => {
|
||||||
|
if (!isEqual(this.extensions.get(lensExtensionId), extension)) {
|
||||||
|
this.extensions.set(lensExtensionId, extension);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
loadOnMain() {
|
loadOnMain() {
|
||||||
logger.info(`${logModule}: load on main`);
|
logger.debug(`${logModule}: load on main`);
|
||||||
this.autoInitExtensions(async (extension: LensMainExtension) => {
|
this.autoInitExtensions(async (extension: LensMainExtension) => {
|
||||||
// Each .add returns a function to remove the item
|
// Each .add returns a function to remove the item
|
||||||
const removeItems = [
|
const removeItems = [
|
||||||
@ -151,7 +168,7 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterManagerRenderer() {
|
loadOnClusterManagerRenderer() {
|
||||||
logger.info(`${logModule}: load on main renderer (cluster manager)`);
|
logger.debug(`${logModule}: load on main renderer (cluster manager)`);
|
||||||
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
||||||
const removeItems = [
|
const removeItems = [
|
||||||
registries.globalPageRegistry.add(extension.globalPages, extension),
|
registries.globalPageRegistry.add(extension.globalPages, extension),
|
||||||
@ -174,7 +191,7 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterRenderer() {
|
loadOnClusterRenderer() {
|
||||||
logger.info(`${logModule}: load on cluster renderer (dashboard)`);
|
logger.debug(`${logModule}: load on cluster renderer (dashboard)`);
|
||||||
const cluster = getHostedCluster();
|
const cluster = getHostedCluster();
|
||||||
|
|
||||||
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
||||||
@ -204,26 +221,26 @@ export class ExtensionLoader {
|
|||||||
|
|
||||||
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Function[]>) {
|
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Function[]>) {
|
||||||
return reaction(() => this.toJSON(), installedExtensions => {
|
return reaction(() => this.toJSON(), installedExtensions => {
|
||||||
for (const [extId, ext] of installedExtensions) {
|
for (const [extId, extension] of installedExtensions) {
|
||||||
const alreadyInit = this.instances.has(extId);
|
const alreadyInit = this.instances.has(extId);
|
||||||
|
|
||||||
if (ext.isEnabled && !alreadyInit) {
|
if (extension.isEnabled && !alreadyInit) {
|
||||||
try {
|
try {
|
||||||
const LensExtensionClass = this.requireExtension(ext);
|
const LensExtensionClass = this.requireExtension(extension);
|
||||||
|
|
||||||
if (!LensExtensionClass) {
|
if (!LensExtensionClass) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = new LensExtensionClass(ext);
|
const instance = new LensExtensionClass(extension);
|
||||||
|
|
||||||
instance.whenEnabled(() => register(instance));
|
instance.whenEnabled(() => register(instance));
|
||||||
instance.enable();
|
instance.enable();
|
||||||
this.instances.set(extId, instance);
|
this.instances.set(extId, instance);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`${logModule}: activation extension error`, { ext, err });
|
logger.error(`${logModule}: activation extension error`, { ext: extension, err });
|
||||||
}
|
}
|
||||||
} else if (!ext.isEnabled && alreadyInit) {
|
} else if (!extension.isEnabled && alreadyInit) {
|
||||||
this.removeInstance(extId);
|
this.removeInstance(extId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,8 +279,8 @@ export class ExtensionLoader {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcastExtensions() {
|
broadcastExtensions(main = true) {
|
||||||
broadcastMessage(this.requestExtensionsChannel, Array.from(this.toJSON()));
|
broadcastMessage(main ? ExtensionLoader.extensionsMainChannel : ExtensionLoader.extensionsRendererChannel, Array.from(this.toJSON()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,17 +45,6 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
|||||||
await extensionLoader.whenLoaded;
|
await extensionLoader.whenLoaded;
|
||||||
await this.whenLoaded;
|
await this.whenLoaded;
|
||||||
|
|
||||||
// apply state on changes from store
|
|
||||||
reaction(() => this.state.toJS(), extensionsState => {
|
|
||||||
extensionsState.forEach((state, extId) => {
|
|
||||||
const ext = extensionLoader.getExtension(extId);
|
|
||||||
|
|
||||||
if (ext && !ext.isBundled) {
|
|
||||||
ext.isEnabled = state.enabled;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// save state on change `extension.isEnabled`
|
// save state on change `extension.isEnabled`
|
||||||
reaction(() => this.getState(extensionLoader), extensionsState => {
|
reaction(() => this.getState(extensionLoader), extensionsState => {
|
||||||
this.state.merge(extensionsState);
|
this.state.merge(extensionsState);
|
||||||
@ -65,7 +54,9 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
|||||||
isEnabled(extId: LensExtensionId) {
|
isEnabled(extId: LensExtensionId) {
|
||||||
const state = this.state.get(extId);
|
const state = this.state.get(extId);
|
||||||
|
|
||||||
return state && state.enabled; // by default false
|
// By default false, so that copied extensions are disabled by default.
|
||||||
|
// If user installs the extension from the UI, the Extensions component will specifically enable it.
|
||||||
|
return Boolean(state?.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export function getExtensionPageUrl<P extends object>({ extensionId, pageId = ""
|
|||||||
const extensionBaseUrl = compile(`/extension/:name`)({
|
const extensionBaseUrl = compile(`/extension/:name`)({
|
||||||
name: sanitizeExtensionName(extensionId), // compile only with extension-id first and define base path
|
name: sanitizeExtensionName(extensionId), // compile only with extension-id first and define base path
|
||||||
});
|
});
|
||||||
const extPageRoutePath = path.join(extensionBaseUrl, pageId); // page-id might contain route :param-s, so don't compile yet
|
const extPageRoutePath = path.posix.join(extensionBaseUrl, pageId);
|
||||||
|
|
||||||
if (params) {
|
if (params) {
|
||||||
return compile(extPageRoutePath)(params); // might throw error when required params not passed
|
return compile(extPageRoutePath)(params); // might throw error when required params not passed
|
||||||
|
|||||||
@ -62,6 +62,9 @@ export class Extensions extends React.Component {
|
|||||||
@observable search = "";
|
@observable search = "";
|
||||||
@observable installPath = "";
|
@observable installPath = "";
|
||||||
|
|
||||||
|
// True if the preliminary install steps have started, but unpackExtension has not started yet
|
||||||
|
@observable startingInstall = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extensions that were removed from extensions but are still in "uninstalling" state
|
* Extensions that were removed from extensions but are still in "uninstalling" state
|
||||||
*/
|
*/
|
||||||
@ -91,11 +94,20 @@ export class Extensions extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.addedInstalling.forEach(({ id, displayName }) => {
|
this.addedInstalling.forEach(({ id, displayName }) => {
|
||||||
|
const extension = this.extensions.find(extension => extension.id === id);
|
||||||
|
|
||||||
|
if (!extension) {
|
||||||
|
throw new Error("Extension not found");
|
||||||
|
}
|
||||||
|
|
||||||
Notifications.ok(
|
Notifications.ok(
|
||||||
<p>Extension <b>{displayName}</b> successfully installed!</p>
|
<p>Extension <b>{displayName}</b> successfully installed!</p>
|
||||||
);
|
);
|
||||||
this.extensionState.delete(id);
|
this.extensionState.delete(id);
|
||||||
this.installPath = "";
|
this.installPath = "";
|
||||||
|
|
||||||
|
// Enable installed extensions by default.
|
||||||
|
extension.isEnabled = true;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -152,6 +164,8 @@ export class Extensions extends React.Component {
|
|||||||
const { installPath } = this;
|
const { installPath } = this;
|
||||||
|
|
||||||
if (!installPath) return;
|
if (!installPath) return;
|
||||||
|
|
||||||
|
this.startingInstall = true;
|
||||||
const fileName = path.basename(installPath);
|
const fileName = path.basename(installPath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -161,13 +175,14 @@ export class Extensions extends React.Component {
|
|||||||
const { promise: filePromise } = downloadFile({ url: installPath, timeout: 60000 /*1m*/ });
|
const { promise: filePromise } = downloadFile({ url: installPath, timeout: 60000 /*1m*/ });
|
||||||
const data = await filePromise;
|
const data = await filePromise;
|
||||||
|
|
||||||
this.requestInstall({ fileName, data });
|
await this.requestInstall({ fileName, data });
|
||||||
}
|
}
|
||||||
// otherwise installing from system path
|
// otherwise installing from system path
|
||||||
else if (InputValidators.isPath.validate(installPath)) {
|
else if (InputValidators.isPath.validate(installPath)) {
|
||||||
this.requestInstall({ fileName, filePath: installPath });
|
await this.requestInstall({ fileName, filePath: installPath });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this.startingInstall = false;
|
||||||
Notifications.error(
|
Notifications.error(
|
||||||
<p>Installation has failed: <b>{String(error)}</b></p>
|
<p>Installation has failed: <b>{String(error)}</b></p>
|
||||||
);
|
);
|
||||||
@ -186,11 +201,11 @@ export class Extensions extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async preloadExtensions(requests: InstallRequest[], { showError = true } = {}) {
|
async preloadExtensions(requests: InstallRequest[], { showError = true } = {}) {
|
||||||
const preloadedRequests = requests.filter(req => req.data);
|
const preloadedRequests = requests.filter(request => request.data);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
requests
|
requests
|
||||||
.filter(req => !req.data && req.filePath)
|
.filter(request => !request.data && request.filePath)
|
||||||
.map(async request => {
|
.map(async request => {
|
||||||
try {
|
try {
|
||||||
const data = await fse.readFile(request.filePath);
|
const data = await fse.readFile(request.filePath);
|
||||||
@ -247,11 +262,11 @@ export class Extensions extends React.Component {
|
|||||||
// copy files to temp
|
// copy files to temp
|
||||||
await fse.ensureDir(this.getExtensionPackageTemp());
|
await fse.ensureDir(this.getExtensionPackageTemp());
|
||||||
|
|
||||||
requests.forEach(req => {
|
for (const request of requests) {
|
||||||
const tempFile = this.getExtensionPackageTemp(req.fileName);
|
const tempFile = this.getExtensionPackageTemp(request.fileName);
|
||||||
|
|
||||||
fse.writeFileSync(tempFile, req.data);
|
await fse.writeFile(tempFile, request.data);
|
||||||
});
|
}
|
||||||
|
|
||||||
// validate packages
|
// validate packages
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@ -289,15 +304,24 @@ export class Extensions extends React.Component {
|
|||||||
const preloadedRequests = await this.preloadExtensions(requests);
|
const preloadedRequests = await this.preloadExtensions(requests);
|
||||||
const validatedRequests = await this.createTempFilesAndValidate(preloadedRequests);
|
const validatedRequests = await this.createTempFilesAndValidate(preloadedRequests);
|
||||||
|
|
||||||
validatedRequests.forEach(install => {
|
// If there are no requests for installing, reset startingInstall state
|
||||||
|
if (validatedRequests.length === 0) {
|
||||||
|
this.startingInstall = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const install of validatedRequests) {
|
||||||
const { name, version, description } = install.manifest;
|
const { name, version, description } = install.manifest;
|
||||||
const extensionFolder = this.getExtensionDestFolder(name);
|
const extensionFolder = this.getExtensionDestFolder(name);
|
||||||
const folderExists = fse.existsSync(extensionFolder);
|
const folderExists = await fse.pathExists(extensionFolder);
|
||||||
|
|
||||||
if (!folderExists) {
|
if (!folderExists) {
|
||||||
// auto-install extension if not yet exists
|
// auto-install extension if not yet exists
|
||||||
this.unpackExtension(install);
|
this.unpackExtension(install);
|
||||||
} else {
|
} else {
|
||||||
|
// If we show the confirmation dialog, we stop the install spinner until user clicks ok
|
||||||
|
// and the install continues
|
||||||
|
this.startingInstall = false;
|
||||||
|
|
||||||
// otherwise confirmation required (re-install / update)
|
// otherwise confirmation required (re-install / update)
|
||||||
const removeNotification = Notifications.info(
|
const removeNotification = Notifications.info(
|
||||||
<div className="InstallingExtensionNotification flex gaps align-center">
|
<div className="InstallingExtensionNotification flex gaps align-center">
|
||||||
@ -315,21 +339,23 @@ export class Extensions extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async unpackExtension({ fileName, tempFile, manifest: { name, version } }: InstallRequestValidated) {
|
async unpackExtension({ fileName, tempFile, manifest: { name, version } }: InstallRequestValidated) {
|
||||||
const displayName = extensionDisplayName(name, version);
|
const displayName = extensionDisplayName(name, version);
|
||||||
const extensionFolder = this.getExtensionDestFolder(name);
|
|
||||||
const unpackingTempFolder = path.join(path.dirname(tempFile), `${path.basename(tempFile)}-unpacked`);
|
|
||||||
const extensionId = path.join(extensionDiscovery.nodeModulesPath, name, "package.json");
|
const extensionId = path.join(extensionDiscovery.nodeModulesPath, name, "package.json");
|
||||||
|
|
||||||
logger.info(`Unpacking extension ${displayName}`, { fileName, tempFile });
|
|
||||||
|
|
||||||
this.extensionState.set(extensionId, {
|
this.extensionState.set(extensionId, {
|
||||||
state: "installing",
|
state: "installing",
|
||||||
displayName
|
displayName
|
||||||
});
|
});
|
||||||
|
this.startingInstall = false;
|
||||||
|
|
||||||
|
const extensionFolder = this.getExtensionDestFolder(name);
|
||||||
|
const unpackingTempFolder = path.join(path.dirname(tempFile), `${path.basename(tempFile)}-unpacked`);
|
||||||
|
|
||||||
|
logger.info(`Unpacking extension ${displayName}`, { fileName, tempFile });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// extract to temp folder first
|
// extract to temp folder first
|
||||||
@ -455,7 +481,7 @@ export class Extensions extends React.Component {
|
|||||||
* True if at least one extension is in installing state
|
* True if at least one extension is in installing state
|
||||||
*/
|
*/
|
||||||
@computed get isInstalling() {
|
@computed get isInstalling() {
|
||||||
return [...this.extensionState.values()].some(extension => extension.state === "installing");
|
return this.startingInstall || [...this.extensionState.values()].some(extension => extension.state === "installing");
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -115,6 +115,7 @@ export class PodDetailsList extends React.Component<Props> {
|
|||||||
<TableCell className="name">{pod.getName()}</TableCell>
|
<TableCell className="name">{pod.getName()}</TableCell>
|
||||||
<TableCell className="warning"><KubeObjectStatusIcon key="icon" object={pod}/></TableCell>
|
<TableCell className="warning"><KubeObjectStatusIcon key="icon" object={pod}/></TableCell>
|
||||||
<TableCell className="namespace">{pod.getNs()}</TableCell>
|
<TableCell className="namespace">{pod.getNs()}</TableCell>
|
||||||
|
<TableCell className="ready">{pod.getRunningContainers().length}/{pod.getContainers().length}</TableCell>
|
||||||
<TableCell className="cpu">{this.renderCpuUsage(`cpu-${pod.getId()}`, metrics.cpu)}</TableCell>
|
<TableCell className="cpu">{this.renderCpuUsage(`cpu-${pod.getId()}`, metrics.cpu)}</TableCell>
|
||||||
<TableCell className="memory">{this.renderMemoryUsage(`memory-${pod.getId()}`, metrics.memory)}</TableCell>
|
<TableCell className="memory">{this.renderMemoryUsage(`memory-${pod.getId()}`, metrics.memory)}</TableCell>
|
||||||
<TableCell className={cssNames("status", kebabCase(pod.getStatusMessage()))}>{pod.getStatusMessage()}</TableCell>
|
<TableCell className={cssNames("status", kebabCase(pod.getStatusMessage()))}>{pod.getStatusMessage()}</TableCell>
|
||||||
@ -148,7 +149,8 @@ export class PodDetailsList extends React.Component<Props> {
|
|||||||
<TableHead>
|
<TableHead>
|
||||||
<TableCell className="name" sortBy={sortBy.name}><Trans>Name</Trans></TableCell>
|
<TableCell className="name" sortBy={sortBy.name}><Trans>Name</Trans></TableCell>
|
||||||
<TableCell className="warning"/>
|
<TableCell className="warning"/>
|
||||||
<TableCell className="namespace" sortBy={sortBy.namespace}>Namespace</TableCell>
|
<TableCell className="namespace" sortBy={sortBy.namespace}><Trans>Namespace</Trans></TableCell>
|
||||||
|
<TableCell className="ready"><Trans>Ready</Trans></TableCell>
|
||||||
<TableCell className="cpu" sortBy={sortBy.cpu}><Trans>CPU</Trans></TableCell>
|
<TableCell className="cpu" sortBy={sortBy.cpu}><Trans>CPU</Trans></TableCell>
|
||||||
<TableCell className="memory" sortBy={sortBy.memory}><Trans>Memory</Trans></TableCell>
|
<TableCell className="memory" sortBy={sortBy.memory}><Trans>Memory</Trans></TableCell>
|
||||||
<TableCell className="status"><Trans>Status</Trans></TableCell>
|
<TableCell className="status"><Trans>Status</Trans></TableCell>
|
||||||
|
|||||||
52
src/renderer/components/cluster-manager/bottom-bar.test.tsx
Normal file
52
src/renderer/components/cluster-manager/bottom-bar.test.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
|
|
||||||
|
import { BottomBar } from "./bottom-bar";
|
||||||
|
jest.mock("../../../extensions/registries");
|
||||||
|
import { statusBarRegistry } from "../../../extensions/registries";
|
||||||
|
|
||||||
|
describe("<BottomBar />", () => {
|
||||||
|
|
||||||
|
it("renders w/o errors", () => {
|
||||||
|
const { container } = render(<BottomBar />);
|
||||||
|
|
||||||
|
expect(container).toBeInstanceOf(HTMLElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
// some defensive testing
|
||||||
|
it("renders w/o errors when .getItems() returns edge cases", async () => {
|
||||||
|
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => undefined);
|
||||||
|
expect(() => render(<BottomBar />)).not.toThrow();
|
||||||
|
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => null);
|
||||||
|
expect(() => render(<BottomBar />)).not.toThrow();
|
||||||
|
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => []);
|
||||||
|
expect(() => render(<BottomBar />)).not.toThrow();
|
||||||
|
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => { return {};});
|
||||||
|
expect(() => render(<BottomBar />)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders items [{item: React.ReactNode}] (4.0.0-rc.1)", async () => {
|
||||||
|
const testId = "testId";
|
||||||
|
const text = "heee";
|
||||||
|
|
||||||
|
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => [
|
||||||
|
{ item: <span data-testid={testId} >{text}</span> }
|
||||||
|
]);
|
||||||
|
const { getByTestId } = render(<BottomBar />);
|
||||||
|
|
||||||
|
expect(await getByTestId(testId)).toHaveTextContent(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders items [{item: () => React.ReactNode}] (4.0.0-rc.1+)", async () => {
|
||||||
|
const testId = "testId";
|
||||||
|
const text = "heee";
|
||||||
|
|
||||||
|
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => [
|
||||||
|
{ item: () => <span data-testid={testId} >{text}</span> }
|
||||||
|
]);
|
||||||
|
const { getByTestId } = render(<BottomBar />);
|
||||||
|
|
||||||
|
expect(await getByTestId(testId)).toHaveTextContent(text);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -11,6 +11,8 @@ import { statusBarRegistry } from "../../../extensions/registries";
|
|||||||
export class BottomBar extends React.Component {
|
export class BottomBar extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const { currentWorkspace } = workspaceStore;
|
const { currentWorkspace } = workspaceStore;
|
||||||
|
// in case .getItems() returns undefined
|
||||||
|
const items = statusBarRegistry.getItems() ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="BottomBar flex gaps">
|
<div className="BottomBar flex gaps">
|
||||||
@ -22,10 +24,17 @@ export class BottomBar extends React.Component {
|
|||||||
htmlFor="current-workspace"
|
htmlFor="current-workspace"
|
||||||
/>
|
/>
|
||||||
<div className="extensions box grow flex gaps justify-flex-end">
|
<div className="extensions box grow flex gaps justify-flex-end">
|
||||||
{statusBarRegistry.getItems().map(({ item }, index) => {
|
{Array.isArray(items) && items.map(({ item }, index) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
return <div className="flex align-center gaps item" key={index}>{item}</div>;
|
return (
|
||||||
|
<div
|
||||||
|
className="flex align-center gaps item"
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{typeof item === "function" ? item() : item}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
2
types/dom.d.ts
vendored
2
types/dom.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
export {}
|
export {};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Element {
|
interface Element {
|
||||||
|
|||||||
10
types/font-face.d.ts
vendored
10
types/font-face.d.ts
vendored
@ -1,6 +1,6 @@
|
|||||||
// https://www.w3.org/TR/css-font-loading/
|
// https://www.w3.org/TR/css-font-loading/
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/FontFace
|
// https://developer.mozilla.org/en-US/docs/Web/API/FontFace
|
||||||
export {}
|
export {};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
const FontFace: FontFace;
|
const FontFace: FontFace;
|
||||||
@ -10,11 +10,11 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CSSOMString = string;
|
type CSSOMString = string;
|
||||||
type FontFaceLoadStatus = 'unloaded' | 'loading' | 'loaded' | 'error';
|
type FontFaceLoadStatus = "unloaded" | "loading" | "loaded" | "error";
|
||||||
type FontFaceSetStatus = 'loading' | 'loaded';
|
type FontFaceSetStatus = "loading" | "loaded";
|
||||||
|
|
||||||
interface FontFace extends FontFaceDescriptors {
|
class FontFace implements FontFaceDescriptors {
|
||||||
new(family: string, source: string | ArrayBuffer, descriptors?: FontFaceDescriptors): FontFace;
|
constructor(family: string, source: string | ArrayBuffer, descriptors?: FontFaceDescriptors);
|
||||||
readonly status: FontFaceLoadStatus;
|
readonly status: FontFaceLoadStatus;
|
||||||
readonly loaded: Promise<FontFace>;
|
readonly loaded: Promise<FontFace>;
|
||||||
variationSettings: CSSOMString;
|
variationSettings: CSSOMString;
|
||||||
|
|||||||
@ -1,18 +1,21 @@
|
|||||||
|
|
||||||
import path from 'path';
|
import path from "path";
|
||||||
import webpack from "webpack";
|
import webpack from "webpack";
|
||||||
import { sassCommonVars } from "./src/common/vars";
|
import { sassCommonVars } from "./src/common/vars";
|
||||||
|
|
||||||
export default function (): webpack.Configuration {
|
export default function (): webpack.Configuration {
|
||||||
const entry = "./src/extensions/extension-api.ts"
|
const entry = "./src/extensions/extension-api.ts";
|
||||||
const outDir = "./src/extensions/npm/extensions/dist";
|
const outDir = "./src/extensions/npm/extensions/dist";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Compile for Electron for renderer process
|
// Compile for Electron for renderer process
|
||||||
// see <https://webpack.js.org/configuration/target/>
|
// see <https://webpack.js.org/configuration/target/>
|
||||||
target: "electron-renderer",
|
target: "electron-renderer",
|
||||||
entry,
|
entry,
|
||||||
|
// this is the default mode, so we should make it explicit to silence the warning
|
||||||
|
mode: "production",
|
||||||
output: {
|
output: {
|
||||||
filename: 'extension-api.js',
|
filename: "extension-api.js",
|
||||||
// need to be an absolute path
|
// need to be an absolute path
|
||||||
path: path.resolve(__dirname, `${outDir}/src/extensions`),
|
path: path.resolve(__dirname, `${outDir}/src/extensions`),
|
||||||
// can be use in commonjs environments
|
// can be use in commonjs environments
|
||||||
@ -23,7 +26,7 @@ export default function (): webpack.Configuration {
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
loader: 'ts-loader',
|
loader: "ts-loader",
|
||||||
options: {
|
options: {
|
||||||
// !! ts-loader will use tsconfig.json at folder root
|
// !! ts-loader will use tsconfig.json at folder root
|
||||||
// !! changes in tsconfig.json may have side effects
|
// !! changes in tsconfig.json may have side effects
|
||||||
@ -70,7 +73,7 @@ export default function (): webpack.Configuration {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.tsx', '.js']
|
extensions: [".ts", ".tsx", ".js"]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
// In ts-loader's README they said to output a built .d.ts file,
|
// In ts-loader's README they said to output a built .d.ts file,
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import webpack from "webpack";
|
import webpack from "webpack";
|
||||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin"
|
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
|
||||||
import { isDevelopment, isProduction, mainDir, buildDir } from "./src/common/vars";
|
import { isDevelopment, isProduction, mainDir, buildDir } from "./src/common/vars";
|
||||||
import nodeExternals from "webpack-node-externals";
|
import nodeExternals from "webpack-node-externals";
|
||||||
import ProgressBarPlugin from "progress-bar-webpack-plugin";
|
import ProgressBarPlugin from "progress-bar-webpack-plugin";
|
||||||
|
import * as vars from "./src/common/vars";
|
||||||
|
|
||||||
export default function (): webpack.Configuration {
|
export default function (): webpack.Configuration {
|
||||||
console.info('WEBPACK:main', require("./src/common/vars"))
|
console.info("WEBPACK:main", vars);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
context: __dirname,
|
context: __dirname,
|
||||||
target: "electron-main",
|
target: "electron-main",
|
||||||
@ -21,7 +23,7 @@ export default function (): webpack.Configuration {
|
|||||||
path: buildDir,
|
path: buildDir,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.json', '.js', '.ts']
|
extensions: [".json", ".js", ".ts"]
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
nodeExternals()
|
nodeExternals()
|
||||||
@ -48,5 +50,5 @@ export default function (): webpack.Configuration {
|
|||||||
new ProgressBarPlugin(),
|
new ProgressBarPlugin(),
|
||||||
new ForkTsCheckerPlugin(),
|
new ForkTsCheckerPlugin(),
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,18 +4,20 @@ import webpack from "webpack";
|
|||||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||||
import TerserPlugin from "terser-webpack-plugin";
|
import TerserPlugin from "terser-webpack-plugin";
|
||||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin"
|
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
|
||||||
import ProgressBarPlugin from "progress-bar-webpack-plugin";
|
import ProgressBarPlugin from "progress-bar-webpack-plugin";
|
||||||
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'
|
import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
|
||||||
|
import * as vars from "./src/common/vars";
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
webpackLensRenderer
|
webpackLensRenderer
|
||||||
]
|
];
|
||||||
|
|
||||||
export function webpackLensRenderer({ showVars = true } = {}): webpack.Configuration {
|
export function webpackLensRenderer({ showVars = true } = {}): webpack.Configuration {
|
||||||
if (showVars) {
|
if (showVars) {
|
||||||
console.info('WEBPACK:renderer', require("./src/common/vars"));
|
console.info("WEBPACK:renderer", vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
context: __dirname,
|
context: __dirname,
|
||||||
target: "electron-renderer",
|
target: "electron-renderer",
|
||||||
@ -27,7 +29,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
|||||||
hot: true,
|
hot: true,
|
||||||
// to avoid cors errors when requests is from iframes
|
// to avoid cors errors when requests is from iframes
|
||||||
disableHostCheck: true,
|
disableHostCheck: true,
|
||||||
headers: { 'Access-Control-Allow-Origin': '*' },
|
headers: { "Access-Control-Allow-Origin": "*" },
|
||||||
},
|
},
|
||||||
name: "lens-app",
|
name: "lens-app",
|
||||||
mode: isProduction ? "production" : "development",
|
mode: isProduction ? "production" : "development",
|
||||||
@ -39,10 +41,10 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
|||||||
libraryTarget: "global",
|
libraryTarget: "global",
|
||||||
library: "",
|
library: "",
|
||||||
globalObject: "this",
|
globalObject: "this",
|
||||||
publicPath: publicPath,
|
publicPath,
|
||||||
path: buildDir,
|
path: buildDir,
|
||||||
filename: '[name].js',
|
filename: "[name].js",
|
||||||
chunkFilename: 'chunks/[name].js',
|
chunkFilename: "chunks/[name].js",
|
||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
warningsFilter: [
|
warningsFilter: [
|
||||||
@ -51,8 +53,8 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [
|
extensions: [
|
||||||
'.js', '.jsx', '.json',
|
".js", ".jsx", ".json",
|
||||||
'.ts', '.tsx',
|
".ts", ".tsx",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
@ -91,7 +93,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
|||||||
}],
|
}],
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
isDevelopment && require.resolve('react-refresh/babel'),
|
isDevelopment && require.resolve("react-refresh/babel"),
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -190,5 +192,5 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
|||||||
isDevelopment && new ReactRefreshWebpackPlugin(),
|
isDevelopment && new ReactRefreshWebpackPlugin(),
|
||||||
|
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user