mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into fix-misleading-no-metrics-message
This commit is contained in:
commit
995d4e8418
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -67,6 +67,10 @@ jobs:
|
||||
- run: make test-extensions
|
||||
name: Run In-tree Extension tests
|
||||
|
||||
- run: make ci-validate-dev
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'dependencies') }}
|
||||
name: Validate dev mode will work
|
||||
|
||||
- name: Install integration test dependencies
|
||||
id: minikube
|
||||
uses: medyagh/setup-minikube@5a9a7104d7322fa40424de8855c84685e89cefd7
|
||||
|
||||
3
Makefile
3
Makefile
@ -28,6 +28,9 @@ compile-dev: node_modules
|
||||
yarn compile:main --cache
|
||||
yarn compile:renderer --cache
|
||||
|
||||
.PHONY: validate-dev
|
||||
ci-validate-dev: binaries/client build-extensions compile-dev
|
||||
|
||||
.PHONY: dev
|
||||
dev: binaries/client build-extensions
|
||||
rm -rf static/build/
|
||||
|
||||
@ -183,9 +183,7 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
||||
}
|
||||
|
||||
const testPodName = "nginx-create-pod-test";
|
||||
const monacoEditor = await frame.waitForSelector(
|
||||
`.Dock.isOpen [data-test-component="monaco-editor"]`,
|
||||
);
|
||||
const monacoEditor = await frame.waitForSelector(`.Dock.isOpen [data-test-id="monaco-editor"]`);
|
||||
|
||||
await monacoEditor.click();
|
||||
await monacoEditor.type("apiVersion: v1", { delay: 10 });
|
||||
|
||||
31
package.json
31
package.json
@ -17,11 +17,11 @@
|
||||
"debug-build": "concurrently yarn:compile:main yarn:compile:extension-types",
|
||||
"dev-run": "nodemon --watch ./static/build/main.js --exec \"electron --remote-debugging-port=9223 --inspect .\"",
|
||||
"dev:main": "yarn run compile:main --watch --progress",
|
||||
"dev:renderer": "yarn run ts-node webpack.dev-server.ts",
|
||||
"dev:renderer": "yarn run ts-node webpack/dev-server.ts",
|
||||
"compile": "env NODE_ENV=production concurrently yarn:compile:*",
|
||||
"compile:main": "yarn run webpack --config webpack.main.ts",
|
||||
"compile:renderer": "yarn run webpack --config webpack.renderer.ts",
|
||||
"compile:extension-types": "yarn run webpack --config webpack.extensions.ts",
|
||||
"compile:main": "yarn run webpack --config webpack/main.ts",
|
||||
"compile:renderer": "yarn run webpack --config webpack/renderer.ts",
|
||||
"compile:extension-types": "yarn run webpack --config webpack/extensions.ts",
|
||||
"npm:fix-build-version": "yarn run ts-node build/set_build_version.ts",
|
||||
"npm:fix-package-version": "yarn run ts-node build/set_npm_version.ts",
|
||||
"build:linux": "yarn run compile && electron-builder --linux --dir",
|
||||
@ -199,6 +199,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@astronautlabs/jsonpath": "^1.1.0",
|
||||
"@hapi/call": "^8.0.1",
|
||||
"@hapi/subtext": "^7.0.3",
|
||||
"@kubernetes/client-node": "^0.16.3",
|
||||
@ -229,7 +230,6 @@
|
||||
"joi": "^17.6.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsdom": "^16.7.0",
|
||||
"jsonpath": "^1.1.1",
|
||||
"lodash": "^4.17.15",
|
||||
"mac-ca": "^1.0.6",
|
||||
"marked": "^4.0.12",
|
||||
@ -239,7 +239,7 @@
|
||||
"mobx-react": "^7.3.0",
|
||||
"mobx-utils": "^6.0.4",
|
||||
"mock-fs": "^5.1.2",
|
||||
"moment": "^2.29.1",
|
||||
"moment": "^2.29.3",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"monaco-editor": "^0.29.1",
|
||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||
@ -259,7 +259,7 @@
|
||||
"request-promise-native": "^1.0.9",
|
||||
"rfc6902": "^4.0.2",
|
||||
"selfsigned": "^2.0.1",
|
||||
"semver": "^7.3.2",
|
||||
"semver": "^7.3.7",
|
||||
"shell-env": "^3.0.1",
|
||||
"spdy": "^4.0.2",
|
||||
"tar": "^6.1.11",
|
||||
@ -278,7 +278,7 @@
|
||||
"@material-ui/core": "^4.12.3",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
|
||||
"@sentry/types": "^6.19.3",
|
||||
"@testing-library/jest-dom": "^5.16.3",
|
||||
"@testing-library/react": "^12.1.4",
|
||||
@ -299,7 +299,6 @@
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsdom": "^16.2.14",
|
||||
"@types/jsonpath": "^0.2.0",
|
||||
"@types/lodash": "^4.14.181",
|
||||
"@types/marked": "^4.0.3",
|
||||
"@types/md5-file": "^4.0.2",
|
||||
@ -313,7 +312,7 @@
|
||||
"@types/react": "^17.0.43",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-dom": "^17.0.14",
|
||||
"@types/react-router-dom": "^5.3.2",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-select": "3.1.2",
|
||||
"@types/react-table": "^7.7.9",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
@ -347,15 +346,15 @@
|
||||
"deepdash": "^5.3.9",
|
||||
"dompurify": "^2.3.6",
|
||||
"electron": "^14.2.9",
|
||||
"electron-builder": "^22.14.13",
|
||||
"electron-builder": "^23.0.3",
|
||||
"electron-notarize": "^0.3.0",
|
||||
"esbuild": "^0.14.34",
|
||||
"esbuild": "^0.14.38",
|
||||
"esbuild-loader": "^2.18.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-react-hooks": "^4.4.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"flex.box": "^3.4.4",
|
||||
"fork-ts-checker-webpack-plugin": "^6.5.0",
|
||||
@ -380,9 +379,9 @@
|
||||
"postcss-loader": "^6.2.1",
|
||||
"randomcolor": "^0.6.2",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-refresh": "^0.11.0",
|
||||
"react-refresh": "^0.12.0",
|
||||
"react-refresh-typescript": "^2.0.4",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-router-dom": "^5.3.1",
|
||||
"react-select": "3.2.0",
|
||||
"react-select-event": "^5.3.0",
|
||||
"react-table": "^7.7.0",
|
||||
@ -403,7 +402,7 @@
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"typescript": "^4.5.5",
|
||||
"typescript-plugin-css-modules": "^3.4.0",
|
||||
"webpack": "^5.69.0",
|
||||
"webpack": "^5.72.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.8.1",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Setup variable in global scope (top-level object)
|
||||
// Global type definition must be added separately to `mocks.d.ts` in form:
|
||||
// declare const __globalName: any;
|
||||
|
||||
export function defineGlobal(propName: string, descriptor: PropertyDescriptor) {
|
||||
const scope = typeof global !== "undefined" ? global : window;
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(scope, propName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(scope, propName, descriptor);
|
||||
}
|
||||
@ -20,7 +20,6 @@ export * from "./collection-functions";
|
||||
export * from "./convertCpu";
|
||||
export * from "./convertMemory";
|
||||
export * from "./debouncePromise";
|
||||
export * from "./defineGlobal";
|
||||
export * from "./delay";
|
||||
export * from "./disposer";
|
||||
export * from "./downloadFile";
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
import path from "path";
|
||||
import { SemVer } from "semver";
|
||||
import packageInfo from "../../package.json";
|
||||
import { defineGlobal } from "./utils/defineGlobal";
|
||||
import { lazyInitialized } from "./utils/lazy-initialized";
|
||||
|
||||
export const isMac = process.platform === "darwin";
|
||||
@ -101,26 +100,12 @@ export const kubectlBinaryName = getBinaryName("kubectl");
|
||||
* @deprecated for being explicit side effect.
|
||||
*/
|
||||
export const kubectlBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), kubectlBinaryName));
|
||||
|
||||
// Webpack build paths
|
||||
export const contextDir = process.cwd();
|
||||
export const buildDir = path.join(contextDir, "static", publicPath);
|
||||
export const preloadEntrypoint = path.join(contextDir, "src/preload.ts");
|
||||
export const mainDir = path.join(contextDir, "src/main");
|
||||
export const rendererDir = path.join(contextDir, "src/renderer");
|
||||
export const htmlTemplate = path.resolve(rendererDir, "template.html");
|
||||
export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");
|
||||
|
||||
// Special runtime paths
|
||||
defineGlobal("__static", {
|
||||
get() {
|
||||
const root = isDevelopment
|
||||
? contextDir
|
||||
: (process.resourcesPath ?? contextDir);
|
||||
|
||||
return path.resolve(root, "static");
|
||||
},
|
||||
});
|
||||
export const staticFilesDirectory = path.resolve(
|
||||
!isProduction
|
||||
? process.cwd()
|
||||
: process.resourcesPath,
|
||||
"static",
|
||||
);
|
||||
|
||||
// Apis
|
||||
export const apiPrefix = "/api" as string; // local router apis
|
||||
@ -142,5 +127,3 @@ export const appSemVer = new SemVer(packageInfo.version);
|
||||
export const docsUrl = "https://docs.k8slens.dev/main/" as string;
|
||||
|
||||
export const sentryDsn = packageInfo.config?.sentryDsn ?? "";
|
||||
|
||||
export const webpackDevServerPort: number = Number(process.env.WEBPACK_DEV_SERVER_PORT) || 9191;
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { contextDir } from "../vars";
|
||||
|
||||
const contextDirInjectable = getInjectable({
|
||||
id: "context-dir",
|
||||
instantiate: () => contextDir,
|
||||
});
|
||||
|
||||
export default contextDirInjectable;
|
||||
@ -16,7 +16,7 @@ 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 { isProduction, staticFilesDirectory } from "../../common/vars";
|
||||
import type { ExtensionInstallationStateStore } from "../extension-installation-state-store/extension-installation-state-store";
|
||||
import type { PackageJson } from "type-fest";
|
||||
import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling";
|
||||
@ -112,7 +112,7 @@ export class ExtensionDiscovery {
|
||||
}
|
||||
|
||||
get inTreeFolderPath(): string {
|
||||
return path.resolve(__static, "../extensions");
|
||||
return path.resolve(staticFilesDirectory, "../extensions");
|
||||
}
|
||||
|
||||
get nodeModulesPath(): string {
|
||||
|
||||
@ -64,6 +64,11 @@ export * from "../../renderer/components/layout/sub-title";
|
||||
export * from "../../renderer/components/input/search-input";
|
||||
export * from "../../renderer/components/chart/bar-chart";
|
||||
export * from "../../renderer/components/chart/pie-chart";
|
||||
export {
|
||||
MonacoEditor,
|
||||
type MonacoEditorProps, type MonacoEditorId,
|
||||
type MonacoTheme, type MonacoCustomTheme,
|
||||
} from "../../renderer/components/monaco-editor";
|
||||
|
||||
// kube helpers
|
||||
export * from "../../renderer/components/kube-detail-params";
|
||||
|
||||
@ -11,7 +11,7 @@ import httpProxy from "http-proxy";
|
||||
import * as LensExtensionsCommonApi from "../extensions/common-api";
|
||||
import * as LensExtensionsMainApi from "../extensions/main-api";
|
||||
import { app, autoUpdater, dialog, powerMonitor } from "electron";
|
||||
import { appName, isIntegrationTesting, isMac, isWindows, productName } from "../common/vars";
|
||||
import { appName, isIntegrationTesting, isMac, isWindows, productName, staticFilesDirectory } from "../common/vars";
|
||||
import { LensProxy } from "./lens-proxy";
|
||||
import { WindowManager } from "./window-manager";
|
||||
import { ClusterManager } from "./cluster-manager";
|
||||
@ -198,7 +198,7 @@ async function main(di: DiContainer) {
|
||||
|
||||
powerMonitor.on("shutdown", () => app.exit());
|
||||
|
||||
registerFileProtocol("static", __static);
|
||||
registerFileProtocol("static", staticFilesDirectory);
|
||||
|
||||
PrometheusProviderRegistry.createInstance();
|
||||
initializers.initPrometheusProviderRegistry();
|
||||
|
||||
@ -11,9 +11,6 @@ import type { Cluster } from "../../common/cluster/cluster";
|
||||
import type { LensApiResultContentType } from "./router-content-types";
|
||||
import { contentTypes } from "./router-content-types";
|
||||
|
||||
// TODO: Import causes side effect, sets value for __static
|
||||
import "../../common/vars";
|
||||
|
||||
export interface RouterRequestOpts {
|
||||
req: http.IncomingMessage;
|
||||
res: http.ServerResponse;
|
||||
|
||||
@ -8,7 +8,7 @@ import type { SupportedFileExtension } from "../router/router-content-types";
|
||||
import { contentTypes } from "../router/router-content-types";
|
||||
import logger from "../logger";
|
||||
import { routeInjectionToken } from "../router/router.injectable";
|
||||
import { appName, publicPath, webpackDevServerPort } from "../../common/vars";
|
||||
import { appName, publicPath, staticFilesDirectory } from "../../common/vars";
|
||||
import path from "path";
|
||||
import isDevelopmentInjectable from "../../common/vars/is-development.injectable";
|
||||
import httpProxy from "http-proxy";
|
||||
@ -17,6 +17,7 @@ import type { GetAbsolutePath } from "../../common/path/get-absolute-path.inject
|
||||
import getAbsolutePathInjectable from "../../common/path/get-absolute-path.injectable";
|
||||
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import { webpackDevServerPort } from "../../../webpack/vars";
|
||||
|
||||
interface ProductionDependencies {
|
||||
readFileBuffer: (path: string) => Promise<Buffer>;
|
||||
@ -27,7 +28,7 @@ interface ProductionDependencies {
|
||||
const handleStaticFileInProduction =
|
||||
({ readFileBuffer, getAbsolutePath, joinPaths }: ProductionDependencies) =>
|
||||
async ({ params }: LensApiRequest) => {
|
||||
const staticPath = getAbsolutePath(__static);
|
||||
const staticPath = getAbsolutePath(staticFilesDirectory);
|
||||
let filePath = params.path;
|
||||
|
||||
for (let retryCount = 0; retryCount < 5; retryCount += 1) {
|
||||
|
||||
@ -12,7 +12,7 @@ import { showAbout } from "../menu/menu";
|
||||
import { checkForUpdates, isAutoUpdateEnabled } from "../app-updater";
|
||||
import type { WindowManager } from "../window-manager";
|
||||
import logger from "../logger";
|
||||
import { isDevelopment, isWindows, productName } from "../../common/vars";
|
||||
import { isDevelopment, isWindows, productName, staticFilesDirectory } from "../../common/vars";
|
||||
import { exitApp } from "../exit-app";
|
||||
import { toJS } from "../../common/utils";
|
||||
import type { TrayMenuRegistration } from "./tray-menu-registration";
|
||||
@ -25,7 +25,7 @@ export let tray: Tray;
|
||||
|
||||
export function getTrayIcon(): string {
|
||||
return path.resolve(
|
||||
__static,
|
||||
staticFilesDirectory,
|
||||
isDevelopment ? "../build/tray" : "icons", // copied within electron-builder extras
|
||||
"trayIconTemplate.png",
|
||||
);
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
import "./crd-resource-details.scss";
|
||||
|
||||
import React from "react";
|
||||
import jsonPath from "jsonpath";
|
||||
import { observer } from "mobx-react";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Badge } from "../badge";
|
||||
@ -16,10 +15,11 @@ import { KubeObjectMeta } from "../kube-object-meta";
|
||||
import { Input } from "../input";
|
||||
import type { AdditionalPrinterColumnsV1 } from "../../../common/k8s-api/endpoints/crd.api";
|
||||
import { CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
|
||||
import { parseJsonPath } from "../../utils/jsonPath";
|
||||
import { convertKubectlJsonPathToNodeJsonPath } from "../../utils/jsonPath";
|
||||
import type { KubeObjectMetadata, KubeObjectStatus } from "../../../common/k8s-api/kube-object";
|
||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import logger from "../../../common/logger";
|
||||
import { JSONPath } from "@astronautlabs/jsonpath";
|
||||
|
||||
export interface CustomResourceDetailsProps extends KubeObjectDetailsProps<KubeObject> {
|
||||
crd: CustomResourceDefinition;
|
||||
@ -48,9 +48,9 @@ function convertSpecValue(value: any): any {
|
||||
@observer
|
||||
export class CustomResourceDetails extends React.Component<CustomResourceDetailsProps> {
|
||||
renderAdditionalColumns(resource: KubeObject, columns: AdditionalPrinterColumnsV1[]) {
|
||||
return columns.map(({ name, jsonPath: jp }) => (
|
||||
return columns.map(({ name, jsonPath }) => (
|
||||
<DrawerItem key={name} name={name} renderBoolean>
|
||||
{convertSpecValue(jsonPath.value(resource, parseJsonPath(jp.slice(1))))}
|
||||
{convertSpecValue(JSONPath.query(resource, convertKubectlJsonPathToNodeJsonPath(jsonPath)))}
|
||||
</DrawerItem>
|
||||
));
|
||||
}
|
||||
|
||||
@ -6,14 +6,13 @@
|
||||
import "./crd-resources.scss";
|
||||
|
||||
import React from "react";
|
||||
import { value } from "jsonpath";
|
||||
import { observer } from "mobx-react";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { computed, makeObservable } from "mobx";
|
||||
import { crdStore } from "./crd.store";
|
||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||
import { parseJsonPath } from "../../utils/jsonPath";
|
||||
import { safeJSONPathValue } from "../../utils/jsonPath";
|
||||
import { TabLayout } from "../layout/tab-layout-2";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import customResourcesRouteParametersInjectable from "./custom-resources-route-parameters.injectable";
|
||||
@ -70,7 +69,7 @@ class NonInjectedCrdResources extends React.Component<Dependencies> {
|
||||
[columnId.age]: customResource => -customResource.getCreationTimestamp(),
|
||||
...Object.fromEntries(extraColumns.map(({ name, jsonPath }) => [
|
||||
name,
|
||||
customResource => value(customResource, parseJsonPath(jsonPath.slice(1))),
|
||||
customResource => safeJSONPathValue(customResource, jsonPath),
|
||||
])),
|
||||
}}
|
||||
searchFilters={[
|
||||
@ -95,22 +94,11 @@ class NonInjectedCrdResources extends React.Component<Dependencies> {
|
||||
})),
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
]}
|
||||
renderTableContents={crdInstance => [
|
||||
crdInstance.getName(),
|
||||
isNamespaced && crdInstance.getNs(),
|
||||
...extraColumns.map((column) => {
|
||||
let rawValue = value(crdInstance, parseJsonPath(column.jsonPath.slice(1)));
|
||||
|
||||
if (Array.isArray(rawValue) || typeof rawValue === "object") {
|
||||
rawValue = JSON.stringify(rawValue);
|
||||
}
|
||||
|
||||
return {
|
||||
renderBoolean: true,
|
||||
children: rawValue,
|
||||
};
|
||||
}),
|
||||
<KubeObjectAge key="age" object={crdInstance} />,
|
||||
renderTableContents={customResource => [
|
||||
customResource.getName(),
|
||||
isNamespaced && customResource.getNs(),
|
||||
...extraColumns.map((column) => safeJSONPathValue(customResource, column.jsonPath)),
|
||||
<KubeObjectAge key="age" object={customResource} />,
|
||||
]}
|
||||
failedToLoadMessage={(
|
||||
<>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import { hasCorrectExtension } from "./has-correct-extension";
|
||||
import "../../../../common/vars";
|
||||
import { staticFilesDirectory } from "../../../../common/vars";
|
||||
import readFileInjectable from "../../../../common/fs/read-file.injectable";
|
||||
import readDirInjectable from "../../../../common/fs/read-dir.injectable";
|
||||
import type { RawTemplates } from "./create-resource-templates.injectable";
|
||||
@ -19,7 +19,7 @@ interface Dependencies {
|
||||
}
|
||||
|
||||
async function getTemplates({ readDir, readFile, getAbsolutePath }: Dependencies) {
|
||||
const templatesFolder = getAbsolutePath(__static, "../templates/create-resource");
|
||||
const templatesFolder = getAbsolutePath(staticFilesDirectory, "../templates/create-resource");
|
||||
|
||||
/**
|
||||
* Mapping between file names and their contents
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
&:first-of-type {
|
||||
padding-left: 1.5rem;
|
||||
|
||||
& > * {
|
||||
& > *:not(:empty) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
@ -34,7 +34,7 @@
|
||||
&:last-of-type {
|
||||
padding-right: 1.5rem;
|
||||
|
||||
& > * {
|
||||
& > *:not(:empty) {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,12 +9,12 @@ import { observer } from "mobx-react";
|
||||
import { action, computed, makeObservable, observable, reaction } from "mobx";
|
||||
import { editor, Uri } from "monaco-editor";
|
||||
import type { MonacoTheme } from "./monaco-themes";
|
||||
import type { MonacoValidator } from "./monaco-validators";
|
||||
import { monacoValidators } from "./monaco-validators";
|
||||
import { type MonacoValidator, monacoValidators } from "./monaco-validators";
|
||||
import { debounce, merge } from "lodash";
|
||||
import { cssNames, disposer } from "../../utils";
|
||||
import { autoBind, cssNames, disposer } from "../../utils";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
import logger from "../../../main/logger";
|
||||
|
||||
export type MonacoEditorId = string;
|
||||
|
||||
@ -45,32 +45,28 @@ export const defaultEditorProps: Partial<MonacoEditorProps> = {
|
||||
|
||||
@observer
|
||||
export class MonacoEditor extends React.Component<MonacoEditorProps> {
|
||||
static defaultProps = defaultEditorProps as object;
|
||||
static viewStates = new WeakMap<Uri, editor.ICodeEditorViewState>();
|
||||
static readonly defaultProps = defaultEditorProps as object;
|
||||
static readonly viewStates = new WeakMap<Uri, editor.ICodeEditorViewState>();
|
||||
|
||||
static createUri(id: MonacoEditorId): Uri {
|
||||
return Uri.file(`/monaco-editor/${id}`);
|
||||
}
|
||||
|
||||
public staticId = `editor-id#${Math.round(1e7 * Math.random())}`;
|
||||
public dispose = disposer();
|
||||
|
||||
// TODO: investigate how to replace with "common/logger"
|
||||
// currently leads for stucking UI forever & infinite loop.
|
||||
// e.g. happens on tab change/create, maybe some other cases too.
|
||||
logger = console;
|
||||
private staticId = `editor-id#${Math.round(1e7 * Math.random())}`;
|
||||
private dispose = disposer();
|
||||
|
||||
@observable.ref containerElem: HTMLElement;
|
||||
@observable.ref editor: editor.IStandaloneCodeEditor;
|
||||
@observable dimensions: { width?: number; height?: number } = {};
|
||||
@observable unmounting = false;
|
||||
@observable readonly dimensions: { width?: number; height?: number } = {};
|
||||
@observable private unmounting = false;
|
||||
|
||||
constructor(props: MonacoEditorProps) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
@computed get id() {
|
||||
@computed get id(): MonacoEditorId {
|
||||
return this.props.id ?? this.staticId;
|
||||
}
|
||||
|
||||
@ -94,7 +90,8 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
|
||||
);
|
||||
}
|
||||
|
||||
@computed get logMetadata() {
|
||||
@computed
|
||||
private get logMetadata() {
|
||||
return {
|
||||
editorId: this.id,
|
||||
model: this.model,
|
||||
@ -121,8 +118,8 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
|
||||
return () => resizeObserver.unobserve(containerElem);
|
||||
}
|
||||
|
||||
onModelChange = (model: editor.ITextModel, oldModel?: editor.ITextModel) => {
|
||||
this.logger?.info("[MONACO]: model change", { model, oldModel }, this.logMetadata);
|
||||
protected onModelChange(model: editor.ITextModel, oldModel?: editor.ITextModel) {
|
||||
logger.info("[MONACO]: model change", { model, oldModel }, this.logMetadata);
|
||||
|
||||
if (oldModel) {
|
||||
this.saveViewState(oldModel);
|
||||
@ -134,18 +131,17 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
|
||||
this.editor.focus(); // keep focus in editor, e.g. when clicking between dock-tabs
|
||||
this.props.onModelChange?.(model, oldModel);
|
||||
this.validateLazy();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current view-model state in the editor.
|
||||
* This will allow restore cursor position, selected text, etc.
|
||||
* @param {editor.ITextModel} model
|
||||
*/
|
||||
private saveViewState(model: editor.ITextModel) {
|
||||
protected saveViewState(model: editor.ITextModel) {
|
||||
MonacoEditor.viewStates.set(model.uri, this.editor.saveViewState());
|
||||
}
|
||||
|
||||
private restoreViewState(model: editor.ITextModel) {
|
||||
protected restoreViewState(model: editor.ITextModel) {
|
||||
const viewState = MonacoEditor.viewStates.get(model.uri);
|
||||
|
||||
if (viewState) {
|
||||
@ -156,9 +152,9 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
|
||||
componentDidMount() {
|
||||
try {
|
||||
this.createEditor();
|
||||
this.logger?.info(`[MONACO]: editor did mount`, this.logMetadata);
|
||||
logger.info(`[MONACO]: editor did mount`, this.logMetadata);
|
||||
} catch (error) {
|
||||
this.logger?.error(`[MONACO]: mounting failed: ${error}`, this.logMetadata);
|
||||
logger.error(`[MONACO]: mounting failed: ${error}`, this.logMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,7 +164,7 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
private createEditor() {
|
||||
protected createEditor() {
|
||||
if (!this.containerElem || this.editor || this.unmounting) {
|
||||
return;
|
||||
}
|
||||
@ -184,7 +180,7 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
|
||||
...this.options,
|
||||
});
|
||||
|
||||
this.logger?.info(`[MONACO]: editor created for language=${language}, theme=${theme}`, this.logMetadata);
|
||||
logger.info(`[MONACO]: editor created for language=${language}, theme=${theme}`, this.logMetadata);
|
||||
this.validateLazy(); // validate initial value
|
||||
this.restoreViewState(this.model); // restore previous state if any
|
||||
|
||||
@ -250,7 +246,8 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
|
||||
this.editor?.focus();
|
||||
}
|
||||
|
||||
validate = action((value = this.getValue()) => {
|
||||
@action
|
||||
validate(value = this.getValue()) {
|
||||
const validators: MonacoValidator[] = [
|
||||
monacoValidators[this.props.language], // parsing syntax check
|
||||
].filter(Boolean);
|
||||
@ -262,19 +259,21 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
|
||||
this.props.onError?.(error); // emit error outside
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// avoid excessive validations during typing
|
||||
validateLazy = debounce(this.validate, 250);
|
||||
|
||||
bindRef = (elem: HTMLElement) => this.containerElem = elem;
|
||||
protected bindRef(elem: HTMLElement) {
|
||||
this.containerElem = elem;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-test-component="monaco-editor"
|
||||
data-test-id="monaco-editor"
|
||||
className={cssNames(styles.MonacoEditor, className)}
|
||||
style={style}
|
||||
ref={this.bindRef}
|
||||
|
||||
@ -3,44 +3,166 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { parseJsonPath } from "../jsonPath";
|
||||
import { convertKubectlJsonPathToNodeJsonPath, safeJSONPathValue } from "../jsonPath";
|
||||
|
||||
describe("parseJsonPath", () => {
|
||||
test("should convert \\. to use indexed notation", () => {
|
||||
const res = parseJsonPath(".metadata.labels.kubesphere\\.io/alias-name");
|
||||
it("should convert \\. to use indexed notation", () => {
|
||||
const res = convertKubectlJsonPathToNodeJsonPath(".metadata.labels.kubesphere\\.io/alias-name");
|
||||
|
||||
expect(res).toBe(".metadata.labels['kubesphere.io/alias-name']");
|
||||
expect(res).toBe("$.metadata.labels['kubesphere.io/alias-name']");
|
||||
});
|
||||
|
||||
test("should convert keys with escaped characters to use indexed notation", () => {
|
||||
const res = parseJsonPath(".metadata.labels.kubesphere\\\"io/alias-name");
|
||||
it("should convert keys with escaped characters to use indexed notation", () => {
|
||||
const res = convertKubectlJsonPathToNodeJsonPath(".metadata.labels.kubesphere\\\"io/alias-name");
|
||||
|
||||
expect(res).toBe(".metadata.labels['kubesphere\"io/alias-name']");
|
||||
expect(res).toBe("$.metadata.labels['kubesphere\"io/alias-name']");
|
||||
});
|
||||
|
||||
test("should convert '-' to use indexed notation", () => {
|
||||
const res = parseJsonPath(".metadata.labels.alias-name");
|
||||
it("should convert '-' to use indexed notation", () => {
|
||||
const res = convertKubectlJsonPathToNodeJsonPath(".metadata.labels.alias-name");
|
||||
|
||||
expect(res).toBe(".metadata.labels['alias-name']");
|
||||
expect(res).toBe("$.metadata.labels['alias-name']");
|
||||
});
|
||||
|
||||
test("should handle scenario when both \\. and indexed notation are present", () => {
|
||||
const rest = parseJsonPath(".metadata.labels\\.serving['some.other.item']");
|
||||
it("should drop leading dot if first group is converted to index notation", () => {
|
||||
const res = convertKubectlJsonPathToNodeJsonPath(".metadata\\.labels.alias-name");
|
||||
|
||||
expect(rest).toBe(".metadata['labels.serving']['some.other.item']");
|
||||
expect(res).toBe("$['metadata.labels']['alias-name']");
|
||||
});
|
||||
|
||||
it("should handle scenario when both \\. and indexed notation are present", () => {
|
||||
const rest = convertKubectlJsonPathToNodeJsonPath(".metadata.labels\\.serving['some.other.item']");
|
||||
|
||||
test("should not touch given jsonPath if no invalid characters present", () => {
|
||||
const res = parseJsonPath(".status.conditions[?(@.type=='Ready')].status");
|
||||
|
||||
expect(res).toBe(".status.conditions[?(@.type=='Ready')].status");
|
||||
expect(rest).toBe("$.metadata['labels.serving']['some.other.item']");
|
||||
});
|
||||
|
||||
test("strips '\\' away from the result", () => {
|
||||
const res = parseJsonPath(".metadata.labels['serving\\.knative\\.dev/configuration']");
|
||||
it("should not touch given jsonPath if no invalid characters present", () => {
|
||||
const res = convertKubectlJsonPathToNodeJsonPath(".status.conditions[?(@.type=='Ready')].status");
|
||||
|
||||
expect(res).toBe(".metadata.labels['serving.knative.dev/configuration']");
|
||||
expect(res).toBe("$.status.conditions[?(@.type=='Ready')].status");
|
||||
});
|
||||
|
||||
it("strips '\\' away from the result", () => {
|
||||
const res = convertKubectlJsonPathToNodeJsonPath(".metadata.labels['serving\\.knative\\.dev/configuration']");
|
||||
|
||||
expect(res).toBe("$.metadata.labels['serving.knative.dev/configuration']");
|
||||
});
|
||||
|
||||
it("converts all [] to [0]", () => {
|
||||
const res = convertKubectlJsonPathToNodeJsonPath(".metadata.labels[].foo[]");
|
||||
|
||||
expect(res).toBe("$.metadata.labels[0].foo[0]");
|
||||
});
|
||||
|
||||
it("converts ending .. to ..*", () => {
|
||||
const res = convertKubectlJsonPathToNodeJsonPath(".metadata.labels[]..");
|
||||
|
||||
expect(res).toBe("$.metadata.labels[0]");
|
||||
});
|
||||
|
||||
it("converts ending ...name to ..name", () => {
|
||||
const res = convertKubectlJsonPathToNodeJsonPath(".metadata.labels[]...name");
|
||||
|
||||
expect(res).toBe("$.metadata.labels[0]..name");
|
||||
});
|
||||
});
|
||||
|
||||
describe("safeJSONPathValue", () => {
|
||||
let oldWarn: typeof console["warn"];
|
||||
|
||||
beforeEach(() => {
|
||||
oldWarn = console.warn;
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
console.warn = oldWarn;
|
||||
});
|
||||
|
||||
it("should convert boolean values to strings", () => {
|
||||
const res = safeJSONPathValue({ bar: false }, ".bar");
|
||||
|
||||
expect(res).toBe("false");
|
||||
});
|
||||
|
||||
it("should convert number values to strings", () => {
|
||||
const res = safeJSONPathValue({ bar: 0 }, ".bar");
|
||||
|
||||
expect(res).toBe("0");
|
||||
});
|
||||
|
||||
it("should join sliced entries with commas only", () => {
|
||||
const res = safeJSONPathValue({
|
||||
bar: [
|
||||
{
|
||||
foo: 1,
|
||||
},
|
||||
{
|
||||
foo: "hello",
|
||||
},
|
||||
],
|
||||
}, ".bar[].foo");
|
||||
|
||||
expect(res).toBe("1");
|
||||
});
|
||||
|
||||
it("should join an array of values using JSON.stringify", () => {
|
||||
const res = safeJSONPathValue({
|
||||
bar: [
|
||||
"world",
|
||||
"hello",
|
||||
],
|
||||
}, ".bar");
|
||||
|
||||
expect(res).toBe(`["world","hello"]`);
|
||||
});
|
||||
|
||||
it("should stringify an object value", () => {
|
||||
const res = safeJSONPathValue({
|
||||
foo: { bar: "bat" },
|
||||
}, ".foo");
|
||||
|
||||
expect(res).toBe(`{"bar":"bat"}`);
|
||||
});
|
||||
|
||||
it("should use convertKubectlJsonPathToNodeJsonPath", () => {
|
||||
const res = safeJSONPathValue({
|
||||
foo: { "hello.world": "bat" },
|
||||
}, ".foo.hello\\.world");
|
||||
|
||||
expect(res).toBe("bat");
|
||||
});
|
||||
|
||||
it("should not throw when given '.spec.metrics[*].external.highWatermark..'", () => {
|
||||
const obj = {
|
||||
spec: {
|
||||
metrics: [
|
||||
{
|
||||
external: {
|
||||
metricName: "cpu",
|
||||
highWatermark: "100",
|
||||
},
|
||||
},
|
||||
{
|
||||
external: {
|
||||
metricName: "memory",
|
||||
highWatermark: "100",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const res = safeJSONPathValue(obj, ".spec.metrics[*].external.highWatermark..");
|
||||
|
||||
expect(res).toBe("100, 100");
|
||||
});
|
||||
|
||||
it("should not throw if path is invalid jsonpath", () => {
|
||||
const res = safeJSONPathValue({
|
||||
foo: { "hello.world": "bat" },
|
||||
}, "asd[");
|
||||
|
||||
expect(res).toBe("<unknown>");
|
||||
});
|
||||
});
|
||||
|
||||
@ -3,28 +3,63 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Helper to convert strings used for jsonPath where \. or - is present to use indexed notation,
|
||||
// for example: .metadata.labels.kubesphere\.io/alias-name -> .metadata.labels['kubesphere\.io/alias-name']
|
||||
import { JSONPath } from "@astronautlabs/jsonpath";
|
||||
|
||||
export function parseJsonPath(jsonPath: string) {
|
||||
let pathExpression = jsonPath;
|
||||
const slashDashSearch = /[\\-]/g;
|
||||
const pathByBareDots = /(?<=\w)\./;
|
||||
const textBeforeFirstSquare = /^.*(?=\[)/g;
|
||||
const backSlash = /\\/g;
|
||||
const kubectlOptionPrefix = /^\$?\.?(?<pathExpression>.*)/;
|
||||
const sliceVersion = /\[]/g;
|
||||
const tripleDotName = /\.\.\.(?<trailing>.)/g;
|
||||
const trailingDotDot = /\.\.$/;
|
||||
|
||||
if (jsonPath.match(/[\\-]/g)) { // search for '\' and '-'
|
||||
const [first, ...rest] = jsonPath.split(/(?<=\w)\./); // split jsonPath by '.' (\. cases are ignored)
|
||||
/**
|
||||
* The GO package that kubectl and kubernetes uses for its JSONpath implementation has some
|
||||
* shorthand conveniences that are not part of the official spec. This function tries to convert
|
||||
* those shorthands to the official spec.
|
||||
*
|
||||
* Known shorthands:
|
||||
* - Leading `$` is optional (but implied)
|
||||
* - The string `\.` is used to denote the "value of '.'" and not "next key"
|
||||
* - The string `-` can be used while not in quotes
|
||||
* - `[]` as shorthand for `[0]`
|
||||
* - Remove `..` at the end of a path, we will just format it slightly differently
|
||||
* - Allow `...foo` as well as `..foo`
|
||||
*/
|
||||
export function convertKubectlJsonPathToNodeJsonPath(jsonPath: string) {
|
||||
const startMatch = jsonPath.match(kubectlOptionPrefix);
|
||||
let start = "$";
|
||||
|
||||
if (!startMatch) {
|
||||
return start;
|
||||
}
|
||||
|
||||
let { pathExpression } = startMatch.groups;
|
||||
|
||||
if (pathExpression.match(slashDashSearch)) {
|
||||
const [first, ...rest] = pathExpression.split(pathByBareDots);
|
||||
|
||||
pathExpression = `${convertToIndexNotation(first, true)}${rest.map(value => convertToIndexNotation(value)).join("")}`;
|
||||
}
|
||||
|
||||
pathExpression = pathExpression.replace(trailingDotDot, "");
|
||||
pathExpression = pathExpression.replace(tripleDotName, "..$<trailing>");
|
||||
|
||||
if (!pathExpression.startsWith("[")) {
|
||||
start += ".";
|
||||
}
|
||||
|
||||
// strip '\' characters from the result
|
||||
return pathExpression.replace(/\\/g, "");
|
||||
return `${start}${pathExpression.replace(backSlash, "").replace(sliceVersion, "[0]")}`;
|
||||
}
|
||||
|
||||
function convertToIndexNotation(key: string, firstItem = false) {
|
||||
if (key.match(/[\\-]/g)) { // check if found '\' and '-' in key
|
||||
if (key.match(slashDashSearch)) {
|
||||
if (key.includes("[")) { // handle cases where key contains [...]
|
||||
const keyToConvert = key.match(/^.*(?=\[)/g); // get the text from the key before '['
|
||||
const keyToConvert = key.match(textBeforeFirstSquare); // get the text from the key before '['
|
||||
|
||||
if (keyToConvert && keyToConvert[0].match(/[\\-]/g)) { // check if that part contains illegal characters
|
||||
if (keyToConvert && keyToConvert[0].match(slashDashSearch)) { // check if that part contains illegal characters
|
||||
return key.replace(keyToConvert[0], `['${keyToConvert[0]}']`); // surround key with '[' and ']'
|
||||
} else {
|
||||
return `.${key}`; // otherwise return as is with leading '.'
|
||||
@ -38,3 +73,35 @@ function convertToIndexNotation(key: string, firstItem = false) {
|
||||
return `${prefix}${key}`;
|
||||
}
|
||||
}
|
||||
|
||||
function formatJSONValue(value: unknown) {
|
||||
if (typeof value === "object") {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
return String(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is a safer version of `JSONPath.value(obj, path)` with untrusted jsonpath strings
|
||||
*
|
||||
* This function will also stringify the value retreived from the object
|
||||
*/
|
||||
export function safeJSONPathValue(obj: object, path: string): string {
|
||||
try {
|
||||
const parsedPath = JSONPath.parse(convertKubectlJsonPathToNodeJsonPath(path));
|
||||
const isSlice = parsedPath.some((exp: any) => exp.expression.type === "slice" || "wildcard");
|
||||
const value = JSONPath.query(obj, JSONPath.stringify(parsedPath), isSlice ? Infinity : 1);
|
||||
|
||||
if (isSlice) {
|
||||
return value.map(formatJSONValue).join(", ");
|
||||
}
|
||||
|
||||
return formatJSONValue(value[0]);
|
||||
} catch (error) {
|
||||
// something failed
|
||||
console.warn("[JSON-PATH]: failed to parse jsonpath", error);
|
||||
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,11 @@
|
||||
"types/*"
|
||||
]
|
||||
},
|
||||
"plugins": [{ "name": "typescript-plugin-css-modules" }]
|
||||
"plugins": [
|
||||
{
|
||||
"name": "typescript-plugin-css-modules"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ts-node": {
|
||||
"transpileOnly": true,
|
||||
@ -42,7 +46,7 @@
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"types/*.d.ts"
|
||||
"types/*.d.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
||||
3
types/mocks.d.ts
vendored
3
types/mocks.d.ts
vendored
@ -8,9 +8,6 @@ declare module "win-ca/api"
|
||||
declare module "@hapi/call"
|
||||
declare module "@hapi/subtext"
|
||||
|
||||
// Global path to static assets
|
||||
declare const __static: string;
|
||||
|
||||
// Support import for custom module extensions
|
||||
// https://www.typescriptlang.org/docs/handbook/modules.html#wildcard-module-declarations
|
||||
declare module "*.module.scss" {
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import Webpack from "webpack";
|
||||
import WebpackDevServer from "webpack-dev-server";
|
||||
import { webpackLensRenderer } from "./webpack.renderer";
|
||||
import { buildDir, webpackDevServerPort } from "./src/common/vars";
|
||||
import logger from "./src/common/logger";
|
||||
|
||||
/**
|
||||
* Creates `webpack-dev-server`
|
||||
* API docs:
|
||||
* @url https://webpack.js.org/configuration/dev-server/
|
||||
* @url https://github.com/chimurai/http-proxy-middleware
|
||||
*/
|
||||
function createDevServer(): WebpackDevServer {
|
||||
const config = webpackLensRenderer({ showVars: false });
|
||||
const compiler = Webpack(config);
|
||||
|
||||
const server = new WebpackDevServer({
|
||||
setupExitSignals: true,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
allowedHosts: "all",
|
||||
host: "localhost",
|
||||
port: webpackDevServerPort,
|
||||
static: buildDir, // aka `devServer.contentBase` in webpack@4
|
||||
hot: "only", // use HMR only without errors
|
||||
liveReload: false,
|
||||
devMiddleware: {
|
||||
writeToDisk: false,
|
||||
index: "OpenLensDev.html",
|
||||
publicPath: "/build",
|
||||
},
|
||||
proxy: {
|
||||
"^/$": "/build/",
|
||||
},
|
||||
client: {
|
||||
overlay: false, // don't show warnings and errors on top of rendered app view
|
||||
logging: "error",
|
||||
},
|
||||
}, compiler);
|
||||
|
||||
logger.info(`[WEBPACK-DEV-SERVER]: created with options`, server.options);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
const server = createDevServer();
|
||||
|
||||
server.start();
|
||||
47
webpack/dev-server.ts
Normal file
47
webpack/dev-server.ts
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import Webpack from "webpack";
|
||||
import WebpackDevServer from "webpack-dev-server";
|
||||
import { webpackLensRenderer } from "./renderer";
|
||||
import logger from "../src/common/logger";
|
||||
import { buildDir, webpackDevServerPort } from "./vars";
|
||||
|
||||
/**
|
||||
* API docs:
|
||||
* @url https://webpack.js.org/configuration/dev-server/
|
||||
* @url https://github.com/chimurai/http-proxy-middleware
|
||||
*/
|
||||
const config = webpackLensRenderer({ showVars: false });
|
||||
const compiler = Webpack(config);
|
||||
|
||||
const server = new WebpackDevServer({
|
||||
setupExitSignals: true,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
allowedHosts: "all",
|
||||
host: "localhost",
|
||||
port: webpackDevServerPort,
|
||||
static: buildDir, // aka `devServer.contentBase` in webpack@4
|
||||
hot: "only", // use HMR only without errors
|
||||
liveReload: false,
|
||||
devMiddleware: {
|
||||
writeToDisk: false,
|
||||
index: "OpenLensDev.html",
|
||||
publicPath: "/build",
|
||||
},
|
||||
proxy: {
|
||||
"^/$": "/build/",
|
||||
},
|
||||
client: {
|
||||
overlay: false, // don't show warnings and errors on top of rendered app view
|
||||
logging: "error",
|
||||
},
|
||||
}, compiler);
|
||||
|
||||
logger.info(`[WEBPACK-DEV-SERVER]: created with options`, server.options);
|
||||
|
||||
server.start();
|
||||
@ -6,25 +6,21 @@
|
||||
|
||||
import path from "path";
|
||||
import type webpack from "webpack";
|
||||
import * as vars from "./src/common/vars";
|
||||
import { cssModulesWebpackRule, fontsLoaderWebpackRules, iconsAndImagesWebpackRules } from "./webpack.renderer";
|
||||
import { cssModulesWebpackRule, fontsLoaderWebpackRules, iconsAndImagesWebpackRules } from "./renderer";
|
||||
import { extensionEntry, extensionOutDir, isDevelopment } from "./vars";
|
||||
|
||||
export default function generateExtensionTypes(): webpack.Configuration {
|
||||
const { isDevelopment } = vars;
|
||||
const entry = "./src/extensions/extension-api.ts";
|
||||
const outDir = "./src/extensions/npm/extensions/dist";
|
||||
|
||||
return {
|
||||
// Compile for Electron for renderer process
|
||||
// see <https://webpack.js.org/configuration/target/>
|
||||
target: "electron-renderer",
|
||||
entry,
|
||||
entry: extensionEntry,
|
||||
// this is the default mode, so we should make it explicit to silence the warning
|
||||
mode: isDevelopment ? "development" : "production",
|
||||
output: {
|
||||
filename: "extension-api.js",
|
||||
// need to be an absolute path
|
||||
path: path.resolve(__dirname, `${outDir}/src/extensions`),
|
||||
path: path.resolve(extensionOutDir, "src", "extensions"),
|
||||
// can be use in commonjs environments
|
||||
// e.g. require('@k8slens/extensions')
|
||||
libraryTarget: "commonjs",
|
||||
@ -54,7 +50,7 @@ export default function generateExtensionTypes(): webpack.Configuration {
|
||||
compilerOptions: {
|
||||
declaration: true, // output .d.ts
|
||||
sourceMap: false, // to override sourceMap: true in tsconfig.json
|
||||
outDir, // where the .d.ts should be located
|
||||
outDir: extensionOutDir, // where the .d.ts should be located
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -11,7 +11,7 @@ import type { Options as TSLoaderOptions } from "ts-loader";
|
||||
* depends on env LENS_DEV_USE_ESBUILD_LOADER to use esbuild-loader (faster) or good-old ts-loader
|
||||
* @returns ts/tsx webpack loader configuration object
|
||||
*/
|
||||
const getTSLoader = (options: Partial<TSLoaderOptions> = {}, testRegExp?: RegExp) => {
|
||||
export default function getTypescriptLoader(options: Partial<TSLoaderOptions> = {}, testRegExp?: RegExp) {
|
||||
testRegExp ??= /\.tsx?$/; // by default covers react/jsx-stuff
|
||||
options.transpileOnly ??= true;
|
||||
|
||||
@ -37,6 +37,4 @@ const getTSLoader = (options: Partial<TSLoaderOptions> = {}, testRegExp?: RegExp
|
||||
options,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default getTSLoader;
|
||||
}
|
||||
@ -7,16 +7,20 @@ import path from "path";
|
||||
import type webpack from "webpack";
|
||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import nodeExternals from "webpack-node-externals";
|
||||
import * as vars from "./src/common/vars";
|
||||
import getTSLoader from "./src/common/getTSLoader";
|
||||
import getTypeScriptLoader from "./get-typescript-loader";
|
||||
import CircularDependencyPlugin from "circular-dependency-plugin";
|
||||
import { iconsAndImagesWebpackRules } from "./webpack.renderer";
|
||||
import { iconsAndImagesWebpackRules } from "./renderer";
|
||||
import type { WebpackPluginInstance } from "webpack";
|
||||
import { buildDir, isDevelopment, mainDir } from "./vars";
|
||||
|
||||
const configs: { (): webpack.Configuration }[] = [];
|
||||
|
||||
configs.push((): webpack.Configuration => {
|
||||
console.info("WEBPACK:main", { ...vars });
|
||||
const { mainDir, buildDir, isDevelopment } = vars;
|
||||
console.info("WEBPACK:main", {
|
||||
isDevelopment,
|
||||
mainDir,
|
||||
buildDir,
|
||||
});
|
||||
|
||||
return {
|
||||
name: "lens-app-main",
|
||||
@ -44,7 +48,7 @@ configs.push((): webpack.Configuration => {
|
||||
test: /\.node$/,
|
||||
use: "node-loader",
|
||||
},
|
||||
getTSLoader({}, /\.ts$/),
|
||||
getTypeScriptLoader({}, /\.ts$/),
|
||||
...iconsAndImagesWebpackRules(),
|
||||
],
|
||||
},
|
||||
@ -54,8 +58,8 @@ configs.push((): webpack.Configuration => {
|
||||
cwd: __dirname,
|
||||
exclude: /node_modules/,
|
||||
failOnError: true,
|
||||
}),
|
||||
].filter(Boolean),
|
||||
}) as unknown as WebpackPluginInstance,
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
@ -3,25 +3,31 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import * as vars from "./src/common/vars";
|
||||
import path from "path";
|
||||
import type webpack from "webpack";
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import MonacoWebpackPlugin from "monaco-editor-webpack-plugin";
|
||||
import getTSLoader from "./src/common/getTSLoader";
|
||||
import CircularDependencyPlugin from "circular-dependency-plugin";
|
||||
import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
|
||||
import type { WebpackPluginInstance } from "webpack";
|
||||
import getTypescriptLoader from "./get-typescript-loader";
|
||||
import { assetsFolderName, isDevelopment, rendererDir, buildDir, appName, htmlTemplate, publicPath, sassCommonVars } from "./vars";
|
||||
|
||||
export function webpackLensRenderer({ showVars = true } = {}): webpack.Configuration {
|
||||
if (showVars) {
|
||||
console.info("WEBPACK:renderer", { ...vars });
|
||||
console.info("WEBPACK:renderer", {
|
||||
assetsFolderName,
|
||||
isDevelopment,
|
||||
rendererDir,
|
||||
buildDir,
|
||||
appName,
|
||||
htmlTemplate,
|
||||
publicPath,
|
||||
});
|
||||
}
|
||||
|
||||
const assetsFolderName = "assets";
|
||||
const { appName, buildDir, htmlTemplate, isDevelopment, publicPath, rendererDir } = vars;
|
||||
|
||||
return {
|
||||
target: "electron-renderer",
|
||||
name: "lens-app-renderer",
|
||||
@ -67,7 +73,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
||||
test: /\.node$/,
|
||||
use: "node-loader",
|
||||
},
|
||||
getTSLoader({
|
||||
getTypescriptLoader({
|
||||
getCustomTransformers: () => ({
|
||||
before: isDevelopment ? [require("react-refresh-typescript")()] : [],
|
||||
}),
|
||||
@ -103,14 +109,18 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
||||
cwd: __dirname,
|
||||
exclude: /node_modules/,
|
||||
failOnError: true,
|
||||
}),
|
||||
}) as unknown as WebpackPluginInstance,
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "[name].css",
|
||||
}),
|
||||
|
||||
isDevelopment && new ReactRefreshWebpackPlugin(),
|
||||
].filter(Boolean),
|
||||
...(
|
||||
isDevelopment
|
||||
? [new ReactRefreshWebpackPlugin()]
|
||||
: []
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@ -143,15 +153,17 @@ export function fontsLoaderWebpackRules(): webpack.RuleSetRule[] {
|
||||
];
|
||||
}
|
||||
|
||||
export interface CssModulesWebpackRuleOptions {
|
||||
styleLoader?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import CSS or SASS styles with modules support (*.module.scss)
|
||||
* @param {string} styleLoader
|
||||
*/
|
||||
export function cssModulesWebpackRule(
|
||||
{
|
||||
styleLoader = vars.isDevelopment ? "style-loader" : MiniCssExtractPlugin.loader,
|
||||
} = {}): webpack.RuleSetRule {
|
||||
const { isDevelopment, sassCommonVars } = vars;
|
||||
export function cssModulesWebpackRule({ styleLoader }: CssModulesWebpackRuleOptions = {}): webpack.RuleSetRule {
|
||||
styleLoader ??= isDevelopment
|
||||
? "style-loader"
|
||||
: MiniCssExtractPlugin.loader;
|
||||
|
||||
return {
|
||||
test: /\.s?css$/,
|
||||
3
webpack/tsconfig.json
Normal file
3
webpack/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../tsconfig.json"
|
||||
}
|
||||
25
webpack/vars.ts
Normal file
25
webpack/vars.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import assert from "assert";
|
||||
import path from "path";
|
||||
import packageInfo from "../package.json";
|
||||
|
||||
export const isDevelopment = process.env.NODE_ENV !== "production";
|
||||
export const mainDir = path.join(process.cwd(), "src", "main");
|
||||
export const buildDir = path.join(process.cwd(), "static", "build");
|
||||
export const extensionEntry = path.join(process.cwd(), "src", "extensions", "extension-api.ts");
|
||||
export const extensionOutDir = path.join(process.cwd(), "src", "extensions", "npm", "extensions", "dist");
|
||||
export const assetsFolderName = "assets";
|
||||
export const rendererDir = path.join(process.cwd(), "src", "renderer");
|
||||
export const appName = isDevelopment
|
||||
? `${packageInfo.productName}Dev`
|
||||
: packageInfo.productName;
|
||||
export const htmlTemplate = path.resolve(rendererDir, "template.html");
|
||||
export const publicPath = "/build/";
|
||||
export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");
|
||||
export const webpackDevServerPort = Number(process.env.WEBPACK_DEV_SERVER_PORT) || 9191;
|
||||
|
||||
assert(Number.isInteger(webpackDevServerPort), "WEBPACK_DEV_SERVER_PORT environment variable must only be an integer");
|
||||
Loading…
Reference in New Issue
Block a user