Restructure to monorepo (#6907)

* wip: restructure to monorepo

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* refactor create-release-pr to a package

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* build fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* github workflow fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix typo

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* add webpack-env types to core

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix github workflows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* refactor/fix integration tests

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* lint fix

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* yarn run dev

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* eslint settings for vscode

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* move templates to right package

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* open-lens build fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* integration test fix

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix nx task dependencies

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* use bash shell for unit tests in test workflow

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix test:unit for windows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix win-ca webpack error in open-lens

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix win-ca webpack error in open-lens

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix build:app on windows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* remove ELECTRON_BUILDER_EXTRA_ARGS

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* sync src/ from master

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* remove Makefile from core

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen authored and GitHub committed 2023-01-24 10:46:26 -08:00
1 parent 0066ca4ea2
commit 2657df2293
3321 files changed
+28699 -11447

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
static/build
binaries/
dist/
node_modules/
+3
View File
@@ -0,0 +1,3 @@
disturl "https://electronjs.org/headers"
target "19.0.4"
runtime "electron"
@@ -0,0 +1,280 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import packageInfo from "../package.json";
import type { FileHandle } from "fs/promises";
import { open } from "fs/promises";
import type { WriteStream } from "fs-extra";
import { constants, ensureDir, unlink } from "fs-extra";
import path from "path";
import type * as FetchModule from "node-fetch";
import { promisify } from "util";
import { pipeline as _pipeline, Transform, Writable } from "stream";
import type { SingleBar } from "cli-progress";
import { MultiBar } from "cli-progress";
import { extract } from "tar-stream";
import gunzip from "gunzip-maybe";
import { isErrnoException, setTimeoutFor } from "../../core/src/common/utils";
import AbortController from "abort-controller";
type Response = FetchModule.Response;
type RequestInfo = FetchModule.RequestInfo;
type RequestInit = FetchModule.RequestInit;
const pipeline = promisify(_pipeline);
const getBinaryName = (binaryName: string, { forPlatform }: { forPlatform : string }) => {
if (forPlatform === "windows") {
return `${binaryName}.exe`;
}
return binaryName;
};
interface BinaryDownloaderArgs {
readonly version: string;
readonly platform: SupportedPlatform;
readonly downloadArch: string;
readonly fileArch: string;
readonly binaryName: string;
readonly baseDir: string;
}
interface BinaryDownloaderDependencies {
fetch: (url: RequestInfo, init?: RequestInit) => Promise<Response>;
}
abstract class BinaryDownloader {
protected abstract readonly url: string;
protected readonly bar: SingleBar;
protected readonly target: string;
protected getTransformStreams(file: Writable): (NodeJS.ReadWriteStream | NodeJS.WritableStream)[] {
return [file];
}
constructor(protected readonly dependencies: BinaryDownloaderDependencies, public readonly args: BinaryDownloaderArgs, multiBar: MultiBar) {
this.bar = multiBar.create(1, 0, args);
this.target = path.join(args.baseDir, args.platform, args.fileArch, args.binaryName);
}
async ensureBinary(): Promise<void> {
if (process.env.LENS_SKIP_DOWNLOAD_BINARIES === "true") {
return;
}
const controller = new AbortController();
setTimeoutFor(controller, 15 * 60 * 1000);
const stream = await this.dependencies.fetch(this.url, {
signal: controller.signal,
});
const total = Number(stream.headers.get("content-length"));
const bar = this.bar;
let fileHandle: FileHandle | undefined = undefined;
if (isNaN(total)) {
throw new Error("no content-length header was present");
}
bar.setTotal(total);
await ensureDir(path.dirname(this.target), 0o755);
try {
/**
* This is necessary because for some reason `createWriteStream({ flags: "wx" })`
* was throwing someplace else and not here
*/
const handle = fileHandle = await open(this.target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
if (!stream.body) {
throw new Error("no body on stream");
}
await pipeline(
stream.body,
new Transform({
transform(chunk, encoding, callback) {
bar.increment(chunk.length);
this.push(chunk);
callback();
},
}),
...this.getTransformStreams(new Writable({
write(chunk, encoding, cb) {
handle.write(chunk)
.then(() => cb())
.catch(cb);
},
})),
);
await fileHandle.chmod(0o755);
await fileHandle.close();
} catch (error) {
await fileHandle?.close();
if (isErrnoException(error) && error.code === "EEXIST") {
bar.increment(total); // mark as finished
controller.abort(); // stop trying to download
} else {
await unlink(this.target);
throw error;
}
}
}
}
class LensK8sProxyDownloader extends BinaryDownloader {
protected readonly url: string;
constructor(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
const binaryName = getBinaryName("lens-k8s-proxy", { forPlatform: args.platform });
super(deps, { ...args, binaryName }, bar);
this.url = `https://github.com/lensapp/lens-k8s-proxy/releases/download/v${args.version}/lens-k8s-proxy-${args.platform}-${args.downloadArch}`;
}
}
class KubectlDownloader extends BinaryDownloader {
protected readonly url: string;
constructor(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
const binaryName = getBinaryName("kubectl", { forPlatform: args.platform });
super(deps, { ...args, binaryName }, bar);
this.url = `https://storage.googleapis.com/kubernetes-release/release/v${args.version}/bin/${args.platform}/${args.downloadArch}/${binaryName}`;
}
}
class HelmDownloader extends BinaryDownloader {
protected readonly url: string;
constructor(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
const binaryName = getBinaryName("helm", { forPlatform: args.platform });
super(deps, { ...args, binaryName }, bar);
this.url = `https://get.helm.sh/helm-v${args.version}-${args.platform}-${args.downloadArch}.tar.gz`;
}
protected getTransformStreams(file: WriteStream) {
const extracting = extract({
allowUnknownFormat: false,
});
extracting.on("entry", (headers, stream, next) => {
if (headers.name.endsWith(this.args.binaryName)) {
stream
.pipe(file)
.once("finish", () => next())
.once("error", next);
} else {
stream.resume();
next();
}
});
return [gunzip(3), extracting];
}
}
type SupportedPlatform = "darwin" | "linux" | "windows";
const importFetchModule = new Function('return import("node-fetch")') as () => Promise<typeof FetchModule>;
async function main() {
const deps: BinaryDownloaderDependencies = {
fetch: (await importFetchModule()).default,
};
const normalizedPlatform = (() => {
switch (process.platform) {
case "darwin":
return "darwin";
case "linux":
return "linux";
case "win32":
return "windows";
default:
throw new Error(`platform=${process.platform} is unsupported`);
}
})();
const multiBar = new MultiBar({
align: "left",
clearOnComplete: false,
hideCursor: true,
autopadding: true,
noTTYOutput: true,
format: "[{bar}] {percentage}% | {downloadArch} {binaryName}",
});
const baseDir = path.join(process.cwd(), "binaries", "client");
const downloaders: BinaryDownloader[] = [
new LensK8sProxyDownloader(deps, {
version: packageInfo.config.k8sProxyVersion,
platform: normalizedPlatform,
downloadArch: "amd64",
fileArch: "x64",
baseDir,
}, multiBar),
new KubectlDownloader(deps, {
version: packageInfo.config.bundledKubectlVersion,
platform: normalizedPlatform,
downloadArch: "amd64",
fileArch: "x64",
baseDir,
}, multiBar),
new HelmDownloader(deps, {
version: packageInfo.config.bundledHelmVersion,
platform: normalizedPlatform,
downloadArch: "amd64",
fileArch: "x64",
baseDir,
}, multiBar),
];
if (normalizedPlatform !== "windows") {
downloaders.push(
new LensK8sProxyDownloader(deps, {
version: packageInfo.config.k8sProxyVersion,
platform: normalizedPlatform,
downloadArch: "arm64",
fileArch: "arm64",
baseDir,
}, multiBar),
new KubectlDownloader(deps, {
version: packageInfo.config.bundledKubectlVersion,
platform: normalizedPlatform,
downloadArch: "arm64",
fileArch: "arm64",
baseDir,
}, multiBar),
new HelmDownloader(deps, {
version: packageInfo.config.bundledHelmVersion,
platform: normalizedPlatform,
downloadArch: "arm64",
fileArch: "arm64",
baseDir,
}, multiBar),
);
}
const settledResults = await Promise.allSettled(downloaders.map(downloader => (
downloader.ensureBinary()
.catch(error => {
throw new Error(`Failed to download ${downloader.args.binaryName} for ${downloader.args.platform}/${downloader.args.downloadArch}: ${error}`);
})
)));
multiBar.stop();
const errorResult = settledResults.find(res => res.status === "rejected") as PromiseRejectedResult | undefined;
if (errorResult) {
console.error("234", String(errorResult.reason));
process.exit(1);
}
process.exit(0);
}
main().catch(error => console.error("from main", error));
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,139 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { ensureDir, readFile } from "fs-extra";
import { JSDOM } from "jsdom";
import path from "path";
import sharp from "sharp";
const size = Number(process.env.OUTPUT_SIZE || "16");
const outputFolder = process.env.OUTPUT_DIR || "./static/build/tray";
const inputFile = process.env.INPUT_SVG_PATH || path.resolve(__dirname, "../../core/src/renderer/components/icon/logo-lens.svg");
const noticeFile = process.env.NOTICE_SVG_PATH || path.resolve(__dirname, "../../core/src/renderer/components/icon/notice.svg");
const spinnerFile = process.env.SPINNER_SVG_PATH || path.resolve(__dirname, "../../core/src/renderer/components/icon/arrow-spinner.svg");
async function ensureOutputFoler() {
await ensureDir(outputFolder);
}
function getSvgStyling(colouring: "dark" | "light"): string {
return `
<style>
ellipse {
stroke: ${colouring === "dark" ? "white" : "black"} !important;
}
path, rect {
fill: ${colouring === "dark" ? "white" : "black"} !important;
}
</style>
`;
}
type TargetSystems = "macos" | "windows-or-linux";
async function getBaseIconImage(system: TargetSystems) {
const svgData = await readFile(inputFile, { encoding: "utf-8" });
const dom = new JSDOM(`<body>${svgData}</body>`);
const root = dom.window.document.body.getElementsByTagName("svg")[0];
root.innerHTML += getSvgStyling(system === "macos" ? "light" : "dark");
return Buffer.from(root.outerHTML);
}
async function generateImage(image: Buffer, size: number, namePrefix: string) {
sharp(image)
.resize({ width: size, height: size })
.png()
.toFile(path.join(outputFolder, `${namePrefix}.png`));
}
async function generateImages(image: Buffer, size: number, name: string) {
await Promise.all([
generateImage(image, size, name),
generateImage(image, size*2, `${name}@2x`),
generateImage(image, size*3, `${name}@3x`),
generateImage(image, size*4, `${name}@4x`),
]);
}
async function generateImageWithSvg(baseImage: Buffer, system: TargetSystems, filePath: string) {
const svgFile = await getIconImage(system, filePath);
const circleBuffer = await sharp(Buffer.from(`
<svg viewBox="0 0 64 64">
<circle cx="32" cy="32" r="32" fill="black" />
</svg>
`))
.toBuffer();
return sharp(baseImage)
.resize({ width: 128, height: 128 })
.composite([
{
input: circleBuffer,
top: 64,
left: 64,
blend: "dest-out",
},
{
input: (
await sharp(svgFile)
.resize({
width: 60,
height: 60,
})
.toBuffer()
),
top: 66,
left: 66,
},
])
.toBuffer();
}
async function getIconImage(system: TargetSystems, filePath: string) {
const svgData = await readFile(filePath, { encoding: "utf-8" });
const root = new JSDOM(svgData).window.document.getElementsByTagName("svg")[0];
root.innerHTML += getSvgStyling(system === "macos" ? "light" : "dark");
return Buffer.from(root.outerHTML);
}
async function generateTrayIcons() {
try {
console.log("Generating tray icon pngs");
await ensureOutputFoler();
const baseIconTemplateImage = await getBaseIconImage("macos");
const baseIconImage = await getBaseIconImage("windows-or-linux");
const updateAvailableTemplateImage = await generateImageWithSvg(baseIconTemplateImage, "macos", noticeFile);
const updateAvailableImage = await generateImageWithSvg(baseIconImage, "windows-or-linux", noticeFile);
const checkingForUpdatesTemplateImage = await generateImageWithSvg(baseIconTemplateImage, "macos", spinnerFile);
const checkingForUpdatesImage = await generateImageWithSvg(baseIconImage, "windows-or-linux", spinnerFile);
await Promise.all([
// Templates are for macOS only
generateImages(baseIconTemplateImage, size, "trayIconTemplate"),
generateImages(updateAvailableTemplateImage, size, "trayIconUpdateAvailableTemplate"),
generateImages(updateAvailableTemplateImage, size, "trayIconUpdateAvailableTemplate"),
generateImages(checkingForUpdatesTemplateImage, size, "trayIconCheckingForUpdatesTemplate"),
// Non-templates are for windows and linux
generateImages(baseIconImage, size, "trayIcon"),
generateImages(updateAvailableImage, size, "trayIconUpdateAvailable"),
generateImages(checkingForUpdatesImage, size, "trayIconCheckingForUpdates"),
]);
console.log("Generated all images");
} catch (error) {
console.error(error);
}
}
generateTrayIcons();
Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

+15
View File
@@ -0,0 +1,15 @@
!macro customInit
; Make sure all old extensions are removed
RMDir /r "$INSTDIR\resources\extensions"
; Workaround for installer handing when the app directory is removed manually
${ifNot} ${FileExists} "$INSTDIR"
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\{${UNINSTALL_APP_KEY}}"
${EndIf}
; Workaround for the old-format uninstall registry key (some people report it causes hangups, too)
ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_APP_KEY}" "QuietUninstallString"
StrCmp $0 "" proceed 0
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_APP_KEY}"
proceed:
!macroend
+27
View File
@@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
const { notarize } = require("electron-notarize");
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== "darwin") {
return;
}
if (!process.env.APPLEID || !process.env.APPLEIDPASS) {
return;
}
const appName = context.packager.appInfo.productFilename;
return await notarize({
appBundleId: process.env.APPBUNDLEID || "io.kontena.lens-app",
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLEID,
appleIdPassword: process.env.APPLEIDPASS,
ascProvider:process.env.ASCPROVIDER,
});
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+6
View File
@@ -0,0 +1,6 @@
{
"extends": "../tsconfig.json",
"include": [
"./**/*",
]
}
@@ -0,0 +1,2 @@
/*! For license information please see node-fetch.bundle.js.LICENSE.txt */
(()=>{var e={414:(e,t,r)=>{if(!globalThis.DOMException)try{const{MessageChannel:e}=r(267),t=(new e).port1,o=new ArrayBuffer;t.postMessage(o,[o,o])}catch(e){"DOMException"===e.constructor.name&&(globalThis.DOMException=e.constructor)}e.exports=globalThis.DOMException},660:function(e,t){var r;r=function(e){"use strict";const t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol:e=>`Symbol(${e})`;function r(){}const o="undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:void 0;function n(e){return"object"==typeof e&&null!==e||"function"==typeof e}const i=r,s=Promise,a=Promise.prototype.then,l=Promise.resolve.bind(s),u=Promise.reject.bind(s);function c(e){return new s(e)}function d(e){return l(e)}function f(e){return u(e)}function h(e,t,r){return a.call(e,t,r)}function p(e,t,r){h(h(e,t,r),void 0,i)}function b(e,t){p(e,t)}function y(e,t){p(e,void 0,t)}function m(e,t,r){return h(e,t,r)}function _(e){h(e,void 0,i)}const g=(()=>{const e=o&&o.queueMicrotask;if("function"==typeof e)return e;const t=d(void 0);return e=>h(t,e)})();function w(e,t,r){if("function"!=typeof e)throw new TypeError("Argument is not a function");return Function.prototype.apply.call(e,t,r)}function v(e,t,r){try{return d(w(e,t,r))}catch(e){return f(e)}}class S{constructor(){this._cursor=0,this._size=0,this._front={_elements:[],_next:void 0},this._back=this._front,this._cursor=0,this._size=0}get length(){return this._size}push(e){const t=this._back;let r=t;16383===t._elements.length&&(r={_elements:[],_next:void 0}),t._elements.push(e),r!==t&&(this._back=r,t._next=r),++this._size}shift(){const e=this._front;let t=e;const r=this._cursor;let o=r+1;const n=e._elements,i=n[r];return 16384===o&&(t=e._next,o=0),--this._size,this._cursor=o,e!==t&&(this._front=t),n[r]=void 0,i}forEach(e){let t=this._cursor,r=this._front,o=r._elements;for(;!(t===o.length&&void 0===r._next||t===o.length&&(r=r._next,o=r._elements,t=0,0===o.length));)e(o[t]),++t}peek(){const e=this._front,t=this._cursor;return e._elements[t]}}function T(e,t){e._ownerReadableStream=t,t._reader=e,"readable"===t._state?A(e):"closed"===t._state?function(e){A(e),k(e)}(e):C(e,t._storedError)}function R(e,t){return wr(e._ownerReadableStream,t)}function E(e){"readable"===e._ownerReadableStream._state?q(e,new TypeError("Reader was released and can no longer be used to monitor the stream's closedness")):function(e,t){C(e,new TypeError("Reader was released and can no longer be used to monitor the stream's closedness"))}(e),e._ownerReadableStream._reader=void 0,e._ownerReadableStream=void 0}function P(e){return new TypeError("Cannot "+e+" a stream using a released reader")}function A(e){e._closedPromise=c(((t,r)=>{e._closedPromise_resolve=t,e._closedPromise_reject=r}))}function C(e,t){A(e),q(e,t)}function q(e,t){void 0!==e._closedPromise_reject&&(_(e._closedPromise),e._closedPromise_reject(t),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0)}function k(e){void 0!==e._closedPromise_resolve&&(e._closedPromise_resolve(void 0),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0)}const O=t("[[AbortSteps]]"),B=t("[[ErrorSteps]]"),L=t("[[CancelSteps]]"),j=t("[[PullSteps]]"),z=Number.isFinite||function(e){return"number"==typeof e&&isFinite(e)},W=Math.trunc||function(e){return e<0?Math.ceil(e):Math.floor(e)};function $(e,t){if(void 0!==e&&"object"!=typeof(r=e)&&"function"!=typeof r)throw new TypeError(`${t} is not an object.`);var r}function F(e,t){if("function"!=typeof e)throw new TypeError(`${t} is not a function.`)}function D(e,t){if(!function(e){return"object"==typeof e&&null!==e||"function"==typeof e}(e))throw new TypeError(`${t} is not an object.`)}function M(e,t,r){if(void 0===e)throw new TypeError(`Parameter ${t} is required in '${r}'.`)}function x(e,t,r){if(void 0===e)throw new TypeError(`${t} is required in '${r}'.`)}function I(e){return Number(e)}function U(e){return 0===e?0:e}function H(e,t){const r=Number.MAX_SAFE_INTEGER;let o=Number(e);if(o=U(o),!z(o))throw new TypeError(`${t} is not a finite number`);if(o=function(e){return U(W(e))}(o),o<0||o>r)throw new TypeError(`${t} is outside the accepted range of 0 to ${r}, inclusive`);return z(o)&&0!==o?o:0}function N(e,t){if(!_r(e))throw new TypeError(`${t} is not a ReadableStream.`)}function V(e){return new J(e)}function Y(e,t){e._reader._readRequests.push(t)}function Q(e,t,r){const o=e._reader._readRequests.shift();r?o._closeSteps():o._chunkSteps(t)}function Z(e){return e._reader._readRequests.length}function G(e){const t=e._reader;return void 0!==t&&!!K(t)}class J{constructor(e){if(M(e,1,"ReadableStreamDefaultReader"),N(e,"First parameter"),gr(e))throw new TypeError("This stream has already been locked for exclusive reading by another reader");T(this,e),this._readRequests=new S}get closed(){return K(this)?this._closedPromise:f(ee("closed"))}cancel(e){return K(this)?void 0===this._ownerReadableStream?f(P("cancel")):R(this,e):f(ee("cancel"))}read(){if(!K(this))return f(eLine truncated
@@ -0,0 +1 @@
/*! node-domexception. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
@@ -0,0 +1,67 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/*
Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE
namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
cluster and vice versa.
*/
import type { ElectronApplication, Page } from "playwright";
import * as utils from "../helpers/utils";
describe("preferences page tests", () => {
let window: Page;
let cleanup: undefined | (() => Promise<void>);
beforeEach(async () => {
let app: ElectronApplication;
({ window, cleanup, app } = await utils.start());
await utils.clickWelcomeButton(window);
await app.evaluate(async ({ app }) => {
await app.applicationMenu
?.getMenuItemById(process.platform === "darwin" ? "mac" : "file")
?.submenu
?.getMenuItemById("navigate-to-preferences")
?.click();
});
}, 10*60*1000);
afterEach(async () => {
await cleanup?.();
}, 10*60*1000);
it('shows "preferences" and can navigate through the tabs', async () => {
const pages = [
{
id: "app",
header: "Application",
},
{
id: "proxy",
header: "Proxy",
},
{
id: "kubernetes",
header: "Kubernetes",
},
];
for (const { id, header } of pages) {
await window.click(`[data-preference-tab-link-test=${id}]`);
await window.waitForSelector(`[data-preference-page-title-test] >> text=${header}`);
}
}, 10*60*1000);
// Skipping, but will turn it on again in the follow up PR
it.skip("ensures helm repos", async () => {
await window.click("[data-testid=kubernetes-tab]");
await window.waitForSelector("[data-testid=repository-name]");
await window.click("#HelmRepoSelect");
await window.waitForSelector("div.Select__option");
}, 10*60*1000);
});
@@ -0,0 +1,378 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/*
Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE
namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
cluster and vice versa.
*/
import * as utils from "../helpers/utils";
import { minikubeReady } from "../helpers/minikube";
import type { Frame, Page } from "playwright";
import { groupBy, toPairs } from "lodash/fp";
import { pipeline } from "@ogre-tools/fp";
import { describeIf } from "../../../core/src/test-utils/skippers";
const TEST_NAMESPACE = "integration-tests";
describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
let window: Page;
let cleanup: undefined | (() => Promise<void>);
let frame: Frame;
beforeEach(async () => {
({ window, cleanup } = await utils.start());
await utils.clickWelcomeButton(window);
frame = await utils.lauchMinikubeClusterFromCatalog(window);
}, 10 * 60 * 1000);
afterEach(async () => {
await cleanup?.();
}, 10 * 60 * 1000);
it("shows cluster context menu in sidebar", async () => {
await frame.click(`[data-testid="sidebar-cluster-dropdown"]`);
await frame.waitForSelector(`.Menu >> text="Add to Hotbar"`);
await frame.waitForSelector(`.Menu >> text="Settings"`);
await frame.waitForSelector(`.Menu >> text="Disconnect"`);
await frame.waitForSelector(`.Menu >> text="Remove"`);
});
// FIXME: failed locally since metrics might already exist, cc @aleksfront
it.skip("opens cluster settings by clicking link in no-metrics area", async () => {
await frame.locator("text=Open cluster settings >> nth=0").click();
await window.waitForSelector(`[data-testid="metrics-header"]`);
});
it(
"should navigate around common cluster pages",
async () => {
const scenariosByParent = pipeline(
scenarios,
groupBy("parentSidebarItemTestId"),
toPairs,
);
for (const [parentSidebarItemTestId, scenarios] of scenariosByParent) {
if (parentSidebarItemTestId !== "null") {
await frame.click(`[data-testid="${parentSidebarItemTestId}"]`);
}
for (const scenario of scenarios) {
await frame.click(`[data-testid="${scenario.sidebarItemTestId}"]`);
await frame.waitForSelector(
scenario.expectedSelector,
selectorTimeout,
);
}
}
},
10 * 60 * 1000,
);
it(
"should show the default namespaces",
async () => {
await navigateToNamespaces(frame);
await frame.waitForSelector("div.TableCell >> text='default'");
await frame.waitForSelector("div.TableCell >> text='kube-system'");
},
10 * 60 * 1000,
);
it(
`should create the ${TEST_NAMESPACE} and a pod in the namespace and then remove that pod via the context menu`,
async () => {
await navigateToNamespaces(frame);
await frame.click("button.add-button");
await frame.waitForSelector(
"div.AddNamespaceDialog >> text='Create Namespace'",
);
const namespaceNameInput = await frame.waitForSelector(
".AddNamespaceDialog input",
);
await namespaceNameInput.type(TEST_NAMESPACE);
await namespaceNameInput.press("Enter");
await frame.waitForSelector(`div.TableCell >> text=${TEST_NAMESPACE}`);
await navigateToPods(frame);
const namespacesSelector = await frame.waitForSelector(
".NamespaceSelect",
);
await namespacesSelector.click();
await namespacesSelector.type(TEST_NAMESPACE);
await namespacesSelector.press("Enter");
await namespacesSelector.click();
await frame.click(".Icon.new-dock-tab");
try {
await frame.click("li.MenuItem.create-resource-tab", {
// NOTE: the following shouldn't be required, but is because without it a TypeError is thrown
// see: https://github.com/microsoft/playwright/issues/8229
position: {
y: 0,
x: 0,
},
});
} catch (error) {
console.log(error);
await frame.waitForTimeout(100_000);
}
const testPodName = "nginx-create-pod-test";
const monacoEditor = await frame.waitForSelector(`.Dock.isOpen [data-test-id="monaco-editor"]`);
await monacoEditor.click();
await monacoEditor.type("apiVersion: v1", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type("kind: Pod", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type("metadata:", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type(` name: ${testPodName}`, { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type(`namespace: ${TEST_NAMESPACE}`, { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.press("Backspace", { delay: 10 });
await monacoEditor.type("spec:", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type(" containers:", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type(`- name: ${testPodName}`, { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type(" image: nginx:alpine", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await frame.click(".Dock .Button >> text='Create'");
await frame.waitForSelector(`.TableCell >> text=${testPodName}`);
await frame.click(".TableRow .TableCell.menu");
await frame.click(".MenuItem >> text=Delete");
await frame.click("button >> text=Remove");
await frame.waitForSelector(`.TableCell >> text=${testPodName}`, { state: "detached" });
},
10 * 60 * 1000,
);
});
const selectorTimeout = { timeout: 30000 };
const scenarios = [
{
expectedSelector: "div[data-testid='cluster-overview-page'] div.label",
parentSidebarItemTestId: null,
sidebarItemTestId: "sidebar-item-link-for-cluster-overview",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: null,
sidebarItemTestId: "sidebar-item-link-for-nodes",
},
{
expectedSelector: 'h5 >> text="Overview"',
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-overview",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-pods",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-deployments",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-daemon-sets",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-stateful-sets",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-replica-sets",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-jobs",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-cron-jobs",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-config-maps",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-secrets",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-resource-quotas",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-limit-ranges",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-horizontal-pod-auto-scalers",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-pod-disruption-budgets",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-network",
sidebarItemTestId: "sidebar-item-link-for-services",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-network",
sidebarItemTestId: "sidebar-item-link-for-endpoints",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-network",
sidebarItemTestId: "sidebar-item-link-for-ingresses",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-network",
sidebarItemTestId: "sidebar-item-link-for-network-policies",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-storage",
sidebarItemTestId: "sidebar-item-link-for-persistent-volume-claims",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-storage",
sidebarItemTestId: "sidebar-item-link-for-persistent-volumes",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-storage",
sidebarItemTestId: "sidebar-item-link-for-storage-classes",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: null,
sidebarItemTestId: "sidebar-item-link-for-namespaces",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: null,
sidebarItemTestId: "sidebar-item-link-for-events",
},
{
expectedSelector: "div.HelmCharts input",
parentSidebarItemTestId: "sidebar-item-link-for-helm",
sidebarItemTestId: "sidebar-item-link-for-charts",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-helm",
sidebarItemTestId: "sidebar-item-link-for-releases",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
sidebarItemTestId: "sidebar-item-link-for-service-accounts",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
sidebarItemTestId: "sidebar-item-link-for-cluster-roles",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
sidebarItemTestId: "sidebar-item-link-for-roles",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
sidebarItemTestId: "sidebar-item-link-for-cluster-role-bindings",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
sidebarItemTestId: "sidebar-item-link-for-role-bindings",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: null,
sidebarItemTestId: "sidebar-item-link-for-custom-resources",
},
];
const navigateToPods = async (frame: Frame) => {
await frame.click(`[data-testid="sidebar-item-link-for-workloads"]`);
await frame.click(`[data-testid="sidebar-item-link-for-pods"]`);
};
const navigateToNamespaces = async (frame: Frame) => {
await frame.click(`[data-testid="sidebar-item-link-for-namespaces"]`);
};
@@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ElectronApplication, Page } from "playwright";
import * as utils from "../helpers/utils";
describe("Lens command palette", () => {
let window: Page;
let cleanup: undefined | (() => Promise<void>);
let app: ElectronApplication;
beforeEach(async () => {
({ window, cleanup, app } = await utils.start());
await utils.clickWelcomeButton(window);
}, 10*60*1000);
afterEach(async () => {
await cleanup?.();
}, 10*60*1000);
describe("menu", () => {
it("opens command dialog from menu", async () => {
await app.evaluate(async ({ app }) => {
await app.applicationMenu
?.getMenuItemById("view")
?.submenu?.getMenuItemById("open-command-palette")
?.click();
});
await window.waitForSelector(".Select__option >> text=Hotbar: Switch");
}, 10*60*1000);
});
});
@@ -0,0 +1,42 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { spawnSync } from "child_process";
export function minikubeReady(testNamespace: string): boolean {
// determine if minikube is running
{
const { status } = spawnSync("minikube status", { shell: true });
if (status !== 0) {
console.warn("minikube not running");
return false;
}
}
// Remove TEST_NAMESPACE if it already exists
{
const { status } = spawnSync(`minikube kubectl -- get namespace ${testNamespace}`, { shell: true });
if (status === 0) {
console.warn(`Removing existing ${testNamespace} namespace`);
const { status, stdout, stderr } = spawnSync(
`minikube kubectl -- delete namespace ${testNamespace}`,
{ shell: true },
);
if (status !== 0) {
console.warn(`Error removing ${testNamespace} namespace: ${stderr.toString()}`);
return false;
}
console.log(stdout.toString());
}
}
return true;
}
@@ -0,0 +1,138 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { createHash } from "crypto";
import { mkdirp, remove } from "fs-extra";
import * as os from "os";
import * as path from "path";
import * as uuid from "uuid";
import type { ElectronApplication, Frame, Page } from "playwright";
import { _electron as electron } from "playwright";
import { noop } from "lodash";
import { disposer } from "../../../core/src/common/utils";
export const appPaths: Partial<Record<NodeJS.Platform, string>> = {
"win32": "./dist/win-unpacked/OpenLens.exe",
"linux": "./dist/linux-unpacked/open-lens",
"darwin": `./dist/mac${ process.arch === "arm64" ? "-arm64" : "" }/OpenLens.app/Contents/MacOS/OpenLens`,
};
async function getMainWindow(app: ElectronApplication, timeout = 50_000): Promise<Page> {
return new Promise((resolve, reject) => {
const cleanup = disposer();
let stdoutBuf = "";
const onWindow = (page: Page) => {
console.log(`Page opened: ${page.url()}`);
if (page.url().startsWith("https://lens.app")) {
cleanup();
console.log(stdoutBuf);
resolve(page);
}
};
app.on("window", onWindow);
cleanup.push(() => app.off("window", onWindow));
const onClose = () => {
cleanup();
reject(new Error("App has unnexpectedly closed"));
};
app.on("close", onClose);
cleanup.push(() => app.off("close", onClose));
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const stdout = app.process().stdout!;
const onData = (chunk: any) => stdoutBuf += chunk.toString();
stdout.on("data", onData);
cleanup.push(() => stdout.off("data", onData));
const timeoutId = setTimeout(() => {
cleanup();
console.log(stdoutBuf);
reject(new Error(`Lens did not open the main window within ${timeout}ms`));
}, timeout);
cleanup.push(() => clearTimeout(timeoutId));
});
}
async function attemptStart() {
const CICD = path.join(os.tmpdir(), "lens-integration-testing", uuid.v4());
// Make sure that the directory is clear
await remove(CICD).catch(noop);
await mkdirp(CICD);
const app = await electron.launch({
args: ["--integration-testing"], // this argument turns off the blocking of quit
executablePath: appPaths[process.platform],
bypassCSP: true,
env: {
CICD,
...process.env,
},
timeout: 100_000,
});
try {
const window = await getMainWindow(app);
return {
app,
window,
cleanup: async () => {
await app.close();
await remove(CICD).catch(noop);
},
};
} catch (error) {
await app.close();
await remove(CICD).catch(noop);
throw error;
}
}
export async function start() {
// this is an attempted workaround for an issue with playwright not always getting the main window when using Electron 14.2.4 (observed on windows)
for (let i = 0; ; i++) {
try {
return await attemptStart();
} catch (error) {
if (i === 4) {
throw error;
}
}
}
}
export async function clickWelcomeButton(window: Page) {
await window.click("[data-testid=welcome-menu-container] li a");
}
function minikubeEntityId() {
return createHash("md5").update(`${path.join(os.homedir(), ".kube", "config")}:minikube`).digest("hex");
}
/**
* From the catalog, click the minikube entity and wait for it to connect, returning its frame
*/
export async function lauchMinikubeClusterFromCatalog(window: Page): Promise<Frame> {
await window.click("div.TableCell >> text='minikube'");
const minikubeFrame = await window.waitForSelector(`#cluster-frame-${minikubeEntityId()}`);
const frame = await minikubeFrame.contentFrame();
if (!frame) {
throw new Error("No iframe for minikube found");
}
await frame.waitForSelector("[data-testid=cluster-sidebar]");
return frame;
}
@@ -0,0 +1,6 @@
{
"extends": "../tsconfig.json",
"include": [
"./**/*",
]
}
+273
View File
@@ -0,0 +1,273 @@
{
"name": "open-lens",
"private": true,
"productName": "OpenLens",
"description": "OpenLens - Open Source IDE for Kubernetes",
"homepage": "https://github.com/lensapp/lens",
"version": "6.4.0-alpha.2",
"repository": {
"type": "git",
"url": "git+https://github.com/lensapp/lens.git"
},
"keywords": [],
"bugs": {
"url": "https://github.com/lensapp/lens/issues"
},
"main": "static/build/main.js",
"copyright": "© 2023 OpenLens Authors",
"license": "MIT",
"author": "OpenLens Authors <info@k8slens.dev>",
"scripts": {
"clean": "rm -rf binaries/ dist/ static/build",
"build": "npm run compile",
"postbuild": "npm run build:tray-icons && npm run download:binaries",
"build:app": "electron-builder --publish onTag",
"prebuild:app": "run-script-os",
"prebuild:app:default": "exit 0",
"prebuild:app:win32": "rm -rf node_modules/win-ca/pem",
"build:dir": "npm run compile && electron-builder --dir",
"compile": "env NODE_ENV=production webpack --config webpack/webpack.ts --progress",
"postcompile": "npm run build:tray-icons && npm run download:binaries",
"predev": "rm -rf static/build/ && npm run build:tray-icons && npm run download:binaries",
"dev": "concurrently -i -k \"yarn run dev-run -C\" yarn:dev:*",
"dev-run": "nodemon --watch ./static/build/main.js --exec \"electron --remote-debugging-port=9223 --inspect .\"",
"dev:main": "env NODE_ENV=development webpack --config webpack/main.ts --progress --watch",
"dev:renderer": "env NODE_ENV=development ts-node ./webpack/dev-server.ts",
"test:integration": "jest -xyz --runInBand --detectOpenHandles --forceExit --modulePaths=[\"<rootDir>/integration/\"];",
"build:tray-icons": "ts-node build/generate-tray-icons.ts",
"download:binaries": "ts-node build/download_binaries.ts"
},
"config": {
"k8sProxyVersion": "0.3.0",
"bundledKubectlVersion": "1.23.3",
"bundledHelmVersion": "3.7.2",
"sentryDsn": "",
"contentSecurityPolicy": "script-src 'unsafe-eval' 'self'; frame-src https://*.lens.app:*/; img-src * data:",
"welcomeRoute": "/welcome"
},
"engines": {
"node": ">=16 <17"
},
"jest": {
"collectCoverage": false,
"verbose": true,
"transform": {
"^.+\\.(t|j)sx?$": [
"@swc/jest"
]
}
},
"nx": {
"targets": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
"{workspaceRoot}/binaries/",
"{workspaceRoot}/build/webpack/",
"{workspaceRoot}/static/build/"
]
},
"dev": {
"dependsOn": [
"^prepare:dev"
],
"outputs": [
"{workspaceRoot}/binaries/",
"{workspaceRoot}/static/build/"
]
},
"build:app": {
"dependsOn": [
"build"
],
"outputs": [
"{workspaceRoot}/dist/"
]
},
"test:integration": {
"dependsOn": [
"build:app"
]
}
}
},
"build": {
"generateUpdatesFilesForAllChannels": true,
"files": [
"static/**/*",
"!node_modules/@k8slens/open-lens/node_modules/**/*",
"!node_modules/@k8slens/open-lens/src"
],
"afterSign": "build/notarize.js",
"extraResources": [
{
"from": "templates/",
"to": "./templates/",
"filter": "**/*.yaml"
},
"LICENSE"
],
"linux": {
"category": "Network",
"artifactName": "${productName}-${version}.${arch}.${ext}",
"target": [
"deb",
"rpm",
"AppImage"
],
"extraResources": [
{
"from": "binaries/client/linux/${arch}/kubectl",
"to": "./${arch}/kubectl"
},
{
"from": "binaries/client/linux/${arch}/lens-k8s-proxy",
"to": "./${arch}/lens-k8s-proxy"
},
{
"from": "binaries/client/linux/${arch}/helm",
"to": "./${arch}/helm"
}
]
},
"rpm": {
"fpm": [
"--rpm-rpmbuild-define=%define _build_id_links none"
]
},
"mac": {
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"extraResources": [
{
"from": "binaries/client/darwin/${arch}/kubectl",
"to": "./${arch}/kubectl"
},
{
"from": "binaries/client/darwin/${arch}/lens-k8s-proxy",
"to": "./${arch}/lens-k8s-proxy"
},
{
"from": "binaries/client/darwin/${arch}/helm",
"to": "./${arch}/helm"
}
]
},
"win": {
"target": [
"nsis"
],
"extraResources": [
{
"from": "binaries/client/windows/${arch}/kubectl.exe",
"to": "./${arch}/kubectl.exe"
},
{
"from": "binaries/client/windows/${arch}/lens-k8s-proxy.exe",
"to": "./${arch}/lens-k8s-proxy.exe"
},
{
"from": "binaries/client/windows/${arch}/helm.exe",
"to": "./${arch}/helm.exe"
}
]
},
"nsis": {
"include": "build/installer.nsh",
"oneClick": false,
"allowElevation": true,
"createStartMenuShortcut": true,
"allowToChangeInstallationDirectory": true
},
"protocols": {
"name": "Lens Protocol Handler",
"schemes": [
"lens"
],
"role": "Viewer"
}
},
"dependencies": {
"@k8slens/open-lens": "^6.4.0-alpha.2",
"@ogre-tools/fp": "^12.0.1",
"@ogre-tools/injectable": "^12.0.1",
"@ogre-tools/injectable-extension-for-auto-registration": "^12.0.1",
"@ogre-tools/injectable-extension-for-mobx": "^12.0.1",
"@ogre-tools/injectable-react": "^12.0.1",
"mobx": "^6.7.0"
},
"devDependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@swc/core": "^1.3.26",
"@swc/jest": "^0.2.24",
"@types/byline": "^4.2.33",
"@types/chart.js": "^2.9.36",
"@types/cli-progress": "^3.11.0",
"@types/color": "^3.0.3",
"@types/crypto-js": "^3.1.47",
"@types/gunzip-maybe": "^1.4.0",
"@types/lodash": "^4.14.191",
"@types/node": "^16.18.6",
"@types/node-fetch": "^2.6.2",
"@types/proper-lockfile": "^4.1.2",
"@types/react-dom": "^17.0.16",
"@types/react-router-dom": "^5.3.3",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@types/request-promise-native": "^1.0.18",
"@types/tar": "^6.1.3",
"@types/tar-stream": "^2.2.2",
"@types/tcp-port-used": "^1.0.1",
"@types/url-parse": "^1.4.8",
"@types/uuid": "^8.3.4",
"@types/webpack": "^5.28.0",
"@types/webpack-dev-server": "^4.7.2",
"@types/webpack-env": "^1.18.0",
"@types/webpack-node-externals": "2.5.3",
"abort-controller": "^3.0.0",
"autoprefixer": "^10.4.13",
"circular-dependency-plugin": "^5.2.2",
"cli-progress": "^3.11.2",
"concurrently": "^7.6.0",
"css-loader": "^6.7.2",
"electron": "^19.1.9",
"electron-builder": "^23.6.0",
"electron-notarize": "^0.3.0",
"esbuild-loader": "^2.20.0",
"fork-ts-checker-webpack-plugin": "^7.2.14",
"gunzip-maybe": "^1.4.2",
"html-webpack-plugin": "^5.5.0",
"jest": "^28.1.3",
"jest-environment-jsdom": "^28.1.3",
"jsdom": "^20.0.3",
"jsonfile": "^6.1.0",
"mini-css-extract-plugin": "^2.7.1",
"monaco-editor": "^0.29.1",
"monaco-editor-webpack-plugin": "^5.0.0",
"node-loader": "^2.0.0",
"nodemon": "^2.0.20",
"playwright": "^1.29.2",
"react-refresh": "^0.14.0",
"react-refresh-typescript": "^2.0.7",
"react-select": "^5.7.0",
"run-script-os": "^1.1.6",
"sharp": "^0.31.2",
"style-loader": "^3.3.1",
"tailwindcss": "^3.2.4",
"ts-loader": "^9.4.2",
"ts-node": "^10.9.1",
"type-fest": "^2.14.0",
"typed-emitter": "^1.4.0",
"typescript": "^4.9.4",
"typescript-plugin-css-modules": "^4.1.1",
"webpack": "^5.75.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.11.1",
"webpack-node-externals": "^3.0.0",
"xterm-addon-fit": "^0.5.0"
}
}
@@ -0,0 +1,17 @@
import { getInjectable } from "@ogre-tools/injectable";
import { applicationInformationToken, ApplicationInformation } from "@k8slens/open-lens/common";
import packageJson from "../../package.json";
const applicationInformationInjectable = getInjectable({
id: "application-information",
injectionToken: applicationInformationToken,
instantiate: () => {
const { version, config, productName, build, copyright, description, name } = packageJson;
return { version, config, productName, build, copyright, description, name } as ApplicationInformation;
},
causesSideEffects: true,
});
export default applicationInformationInjectable;
+38
View File
@@ -0,0 +1,38 @@
import { createContainer } from "@ogre-tools/injectable";
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
import { runInAction } from "mobx";
import { createApp, extensionApi } from "@k8slens/open-lens/main";
const di = createContainer("main");
const app = createApp({
di,
mode: process.env.NODE_ENV || "development"
});
runInAction(() => {
try {
autoRegister({
di,
requireContexts: [
require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
],
});
} catch (err) {
console.error(err);
process.exit(1);
}
});
app.start().catch((error) => {
console.error(error);
process.exit(1);
})
const { Mobx, LensExtensions, Pty } = extensionApi;
export {
Mobx,
LensExtensions,
Pty
};
+43
View File
@@ -0,0 +1,43 @@
import "@k8slens/open-lens/styles";
import { createContainer } from "@ogre-tools/injectable";
import { runInAction } from "mobx";
import { createApp, extensionApi } from "@k8slens/open-lens/renderer";
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
const di = createContainer("renderer");
const app = createApp({
di,
mode: process.env.NODE_ENV || "development"
});
runInAction(() => {
autoRegister({
di,
requireContexts: [
require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
],
});
});
app.start();
const {
React,
ReactDOM,
ReactRouter,
ReactRouterDom,
Mobx,
MobxReact,
LensExtensions
} = extensionApi;
export {
React,
ReactDOM,
ReactRouter,
ReactRouterDom,
Mobx,
MobxReact,
LensExtensions
};
+164
View File
@@ -0,0 +1,164 @@
<html>
<head>
<style>
html, body{
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
padding: 0;
}
body {
background: #1e2124;
color: #6c757d;
font-family: Arial, Helvetica, sans-serif;
}
.aligner-center-center{
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.aligner-content{
position: relative;
width: 100%;
padding: 80px;
}
.icon {
position: relative;
font-size: 36px;
line-height: 54px;
margin-bottom: 10px;
text-align: center;
}
.icon svg{
position: relative;
display: inline-block;
width: 54px;
height: 54px;
top: 14px;
}
.icon .icon-text{
color: #fff;
text-align: center;
display: inline;
}
svg{
position: relative;
width: 100%;
}
.loading{
padding-top: 10px;
position: relative;
}
.bar{
position: relative;;
height: 2px;
background: #262b2f;
}
.progress{
position: relative;
height: 2px;
background: #3d90ce;
box-shadow: 0 0 20px #3d90ce;
animation-name: loader-animation;
animation-duration: 1.6s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
@keyframes loader-animation{
0% {
left: 0;
width: 0px;
}
50% {
left: 0;
width: 100%;
}
100% {
left: 100%;
width: 0px;
}
}
.glow-wrapper{
position: relative;
width: 100%;
height: 2px;
top: -2px;
}
.glow{
height: 100%;
display: flex;
transform: translateZ(0);
}
.glow div{
flex: 1;
background: #3d90ce;
animation: glow 0.8s infinite alternate ease;
box-shadow: 0 0 20px #3d90ce;
}
.glow div.b1{ animation-delay: -0.72s; }
.glow div.b2{ animation-delay: -0.64s; }
.glow div.b3{ animation-delay: -0.56s; }
.glow div.b4{ animation-delay: -0.48s; }
.glow div.b5{ animation-delay: -0.40s; }
.glow div.b6{ animation-delay: -0.32s; }
.glow div.b7{ animation-delay: -0.24s; }
.glow div.b8{ animation-delay: -0.16s; }
.glow div.b9{ animation-delay: -0.08s; }
@keyframes glow{
100% {
background: transparent;
flex: 10;
box-shadow: 0 0 0 transparent;
}
}
.text{
height: 20px;
line-height: 20px;
text-align: center;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="aligner-center-center">
<div class="aligner-content">
<div class="icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#3f90ce"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M14.25 2.26l-.08-.04-.01.02C13.46 2.09 12.74 2 12 2 6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10c0-4.75-3.31-8.72-7.75-9.74zM19.41 9h-7.99l2.71-4.7c2.4.66 4.35 2.42 5.28 4.7zM13.1 4.08L10.27 9l-1.15 2L6.4 6.3C7.84 4.88 9.82 4 12 4c.37 0 .74.03 1.1.08zM5.7 7.09L8.54 12l1.15 2H4.26C4.1 13.36 4 12.69 4 12c0-1.85.64-3.55 1.7-4.91zM4.59 15h7.98l-2.71 4.7c-2.4-.67-4.34-2.42-5.27-4.7zm6.31 4.91L14.89 13l2.72 4.7C16.16 19.12 14.18 20 12 20c-.38 0-.74-.04-1.1-.09zm7.4-3l-4-6.91h5.43c.17.64.27 1.31.27 2 0 1.85-.64 3.55-1.7 4.91z"/></svg>
<div class="icon-text">OpenLens</div>
</div>
<div class="loading">
<div class="bar">
<div class="progress"></div>
</div>
<div class="glow-wrapper">
<div class="glow">
<div class="b1"></div>
<div class="b2"></div>
<div class="b3"></div>
<div class="b4"></div>
<div class="b5"></div>
<div class="b6"></div>
<div class="b7"></div>
<div class="b8"></div>
<div class="b9"></div>
</div>
</div>
<div class="text">
Loading...
</div>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,206 @@
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: read-all-clusterrole
rules:
- nonResourceURLs:
- /metrics
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- bindings
- componentstatuses
- configmaps
- endpoints
- events
- limitranges
- namespaces
- namespaces/finalize
- namespaces/status
- nodes
- nodes/proxy
- nodes/status
- persistentvolumeclaims
- persistentvolumeclaims/status
- persistentvolumes
- persistentvolumes/status
- pods
- pods/attach
- pods/binding
- pods/eviction
- pods/exec
- pods/log
- pods/proxy
- pods/status
- podtemplates
- replicationcontrollers
- replicationcontrollers/scale
- replicationcontrollers/status
- resourcequotas
- resourcequotas/status
- serviceaccounts
- services
- services/proxy
- services/status
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- controllerrevisions
- daemonsets
- daemonsets/status
- deployments
- deployments/scale
- deployments/status
- replicasets
- replicasets/scale
- replicasets/status
- statefulsets
- statefulsets/scale
- statefulsets/status
verbs:
- list
- get
- watch
- apiGroups:
- batch
resources:
- jobs
- jobs/status
verbs:
- get
- list
- watch
- apiGroups:
- autoscaling
resources:
- horizontalpodautoscalers
- horizontalpodautoscalers/status
verbs:
- get
- list
- watch
- apiGroups:
- storage.k8s.io
resources:
- csidrivers
- csinodes
- storageclasses
- volumeattachments
- volumeattachments/status
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- networkpolicies
verbs:
- get
- list
- watch
- apiGroups:
- scheduling.k8s.io
resources:
- priorityclasses
verbs:
- get
- list
- watch
- apiGroups:
- node.k8s.io
resources:
- runtimeclasses
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses
- ingresses/status
verbs:
- get
- list
- watch
- apiGroups:
- events.k8s.io
resources:
- events
verbs:
- get
- list
- watch
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
- customresourcedefinitions/status
verbs:
- get
- list
- watch
- apiGroups:
- apiregistration.k8s.io
resources:
- apiservices
- apiservices/status
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- get
- list
- watch
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- watch
- apiGroups:
- policy
resources:
- poddisruptionbudgets
- poddisruptionbudgets/status
- podsecuritypolicies
verbs:
- get
- list
- watch
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterrolebindings
- clusterroles
- rolebindings
- roles
verbs:
- get
- list
- watch
- apiGroups:
- scheduling.k8s.io
resources:
- priorityclass
verbs:
- get
- list
- watch
@@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: developer-read-all
subjects:
- kind: ServiceAccount
name: developer
namespace: default
roleRef:
kind: ClusterRole
name: read-all-clusterrole
apiGroup: rbac.authorization.k8s.io
@@ -0,0 +1,17 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: game-demo
data:
# property-like keys; each key maps to a simple value
player_initial_lives: "3"
ui_properties_file_name: "user-interface.properties"
# file-like keys
game.properties: |
enemy.types=aliens,monsters
player.maximum-lives=5
user-interface.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
@@ -0,0 +1,19 @@
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
@@ -0,0 +1,44 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
tolerations:
# this toleration is to have the daemonset runnable on master nodes
# remove it if your masters can't run pods
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
@@ -0,0 +1,21 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
@@ -0,0 +1,17 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
@@ -0,0 +1,13 @@
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
@@ -0,0 +1,34 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
@@ -0,0 +1,18 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0003
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: /tmp
server: 172.17.0.2
@@ -0,0 +1,17 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 8Gi
storageClassName: slow
selector:
matchLabels:
release: "stable"
matchExpressions:
- {key: environment, operator: In, values: [dev]}
@@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
role: myrole
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
protocol: TCP
@@ -0,0 +1,8 @@
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: overprovisioning
description: Priority class used by overprovisioning.
preemptionPolicy: PreemptLowerPriority
globalDefault: false
value: -1
@@ -0,0 +1,21 @@
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
# modify replicas according to your case
replicas: 3
selector:
matchLabels:
tier: frontend
template:
metadata:
labels:
tier: frontend
spec:
containers:
- name: php-redis
image: gcr.io/google_samples/gb-frontend:v3
@@ -0,0 +1,19 @@
apiVersion: v1
kind: ReplicationController
metadata:
name: nginx
spec:
replicas: 3
selector:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
@@ -0,0 +1,9 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["pods"]
verbs: ["get", "watch", "list"]
@@ -0,0 +1,17 @@
apiVersion: rbac.authorization.k8s.io/v1
# This role binding allows "jane" to read pods in the "default" namespace.
# You need to already have a Role named "pod-reader" in that namespace.
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
# You can specify more than one "subject"
- kind: User
name: jane # "name" is case sensitive
apiGroup: rbac.authorization.k8s.io
roleRef:
# "roleRef" specifies the binding to a Role / ClusterRole
kind: Role #this must be Role or ClusterRole
name: pod-reader # this must match the name of the Role or ClusterRole you wish to bind to
apiGroup: rbac.authorization.k8s.io
@@ -0,0 +1,12 @@
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: spot
handler: runc
scheduling:
nodeSelector:
eks.amazonaws.com/capacityType: "SPOT"
tolerations:
- key: eks.amazonaws.com/capacityType
value: SPOT
effect: NoSchedule
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
username: admin
password: t0p-Secret
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
@@ -0,0 +1,34 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx # has to match .spec.template.metadata.labels
serviceName: "nginx"
replicas: 3 # by default is 1
template:
metadata:
labels:
app: nginx # has to match .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: k8s.gcr.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi
+32
View File
@@ -0,0 +1,32 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [
"node_modules/*",
]
},
"plugins": [
{
"name": "typescript-plugin-css-modules"
}
]
},
"include": [
"src/**/*",
"../core/types/*.d.ts"
],
"exclude": [
"out",
"dist",
"coverage",
"binaries",
"static"
],
"references": [
{
"path": "../core"
}
]
}
+45
View File
@@ -0,0 +1,45 @@
/**
* 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 renderer from "./renderer";
import { buildDir, webpackDevServerPort } from "./vars";
/**
* API docs:
* @url https://webpack.js.org/configuration/dev-server/
* @url https://github.com/chimurai/http-proxy-middleware
*/
const compiler = Webpack(renderer);
const server = new WebpackDevServer({
setupExitSignals: true,
headers: {
"Access-Control-Allow-Origin": "*",
},
allowedHosts: ".lens.app",
host: "localhost",
port: webpackDevServerPort,
static: buildDir, // aka `devServer.contentBase` in webpack@4
hot: "only", // use HMR only without errors
liveReload: false,
devMiddleware: {
writeToDisk: true,
index: "index.html",
publicPath: "/build",
},
proxy: {
"^/$": "/build/",
},
client: {
overlay: false, // don't show warnings and errors on top of rendered app view
logging: "error",
},
}, compiler);
console.info(`[WEBPACK-DEV-SERVER]: created with options`, server.options);
server.start();
+59
View File
@@ -0,0 +1,59 @@
import path from "path";
import type webpack from "webpack";
import { DefinePlugin } from "webpack";
import nodeExternals from "webpack-node-externals";
import { iconsAndImagesWebpackRules } from "./renderer";
import { buildDir, isDevelopment, mainDir } from "./vars";
import { platform } from "process";
const main: webpack.Configuration = ({
name: "lens-app-main",
context: __dirname,
target: "electron-main",
mode: isDevelopment ? "development" : "production",
devtool: isDevelopment ? "cheap-module-source-map" : "source-map",
cache: isDevelopment ? { type: "filesystem" } : false,
entry: {
main: path.resolve(mainDir, "index.ts"),
},
output: {
libraryTarget: "global",
path: buildDir,
},
optimization: {
minimize: false,
},
resolve: {
extensions: [".json", ".js", ".ts"],
},
externals: [
nodeExternals(),
],
module: {
parser: {
javascript: {
commonjsMagicComments: true,
},
},
rules: [
{
test: /\.node$/,
use: "node-loader",
},
{
test: /\.ts$/,
loader: "ts-loader",
options: {},
},
...iconsAndImagesWebpackRules(),
],
},
plugins: [
new DefinePlugin({
CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`,
CONTEXT_MATCHER_FOR_FEATURES: `/\\/(renderer|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`,
}),
],
});
export default main;
+186
View File
@@ -0,0 +1,186 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import path from "path";
import type 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 CircularDependencyPlugin from "circular-dependency-plugin";
import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
import type { WebpackPluginInstance } from "webpack";
import { DefinePlugin } from "webpack";
import { assetsFolderName, isDevelopment, rendererDir, buildDir, htmlTemplate, publicPath, sassCommonVars } from "./vars";
import { platform } from "process";
const renderer: webpack.Configuration = {
target: "electron-renderer",
name: "lens-app-renderer",
mode: isDevelopment ? "development" : "production",
// https://webpack.js.org/configuration/devtool/ (see description of each option)
devtool: isDevelopment ? "cheap-module-source-map" : "source-map",
cache: isDevelopment ? { type: "filesystem" } : false,
entry: {
lens: path.resolve(rendererDir, "index.ts"),
},
output: {
libraryTarget: "global",
globalObject: "this",
publicPath,
path: buildDir,
filename: "[name].js",
chunkFilename: "chunks/[name].js",
assetModuleFilename: `${assetsFolderName}/[name][ext][query]`,
},
watchOptions: {
ignored: /node_modules/, // https://webpack.js.org/configuration/watch/
},
ignoreWarnings: [
/Critical dependency: the request of a dependency is an expression/,
/require.extensions is not supported by webpack./, // handlebars
/\[ReactRefreshPlugin] .*?HMR.*? is not enabled/, // enabled in webpack.dev-server
],
resolve: {
extensions: [
".js", ".jsx", ".json",
".ts", ".tsx",
],
},
externals: [
"node-fetch",
"npm",
"win-ca"
],
optimization: {
minimize: false,
},
module: {
parser: {
javascript: {
commonjsMagicComments: true,
},
},
rules: [
{
test: /\.node$/,
use: "node-loader",
},
{
test: /\.tsx?$/,
loader: "ts-loader",
options: {},
},
cssModulesWebpackRule(),
...iconsAndImagesWebpackRules(),
...fontsLoaderWebpackRules(),
],
},
plugins: [
new DefinePlugin({
CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`,
CONTEXT_MATCHER_FOR_FEATURES: `/\\/(renderer|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`,
}),
new ForkTsCheckerPlugin(),
// see also: https://github.com/Microsoft/monaco-editor-webpack-plugin#options
new MonacoWebpackPlugin({
// publicPath: "/",
// filename: "[name].worker.js",
languages: ["json", "yaml"],
globalAPI: isDevelopment,
}),
new HtmlWebpackPlugin({
filename: "index.html",
template: htmlTemplate,
inject: true,
hash: true,
templateParameters: {
assetPath: `${publicPath}${assetsFolderName}`,
},
}),
new CircularDependencyPlugin({
cwd: __dirname,
exclude: /node_modules/,
failOnError: true,
}) as unknown as WebpackPluginInstance,
new MiniCssExtractPlugin({
filename: "[name].css",
}),
...(
isDevelopment
? [new ReactRefreshWebpackPlugin()]
: []
),
],
};
/**
* Import icons and image files.
* Read more about asset types: https://webpack.js.org/guides/asset-modules/
*/
export function iconsAndImagesWebpackRules(): webpack.RuleSetRule[] {
return [
{
test: /\.svg$/,
type: "asset/source", // exports the source code of the asset, so we get XML
},
{
test: /\.(jpg|png|ico)$/,
type: "asset/resource", // path to file, e.g. "/static/build/assets/*"
},
];
}
/**
* Import custom fonts as URL.
*/
export function fontsLoaderWebpackRules(): webpack.RuleSetRule[] {
return [
{
test: /\.(ttf|eot|woff2?)$/,
type: "asset/resource",
},
];
}
export interface CssModulesWebpackRuleOptions {
styleLoader?: string;
}
/**
* Import CSS or SASS styles with modules support (*.module.scss)
*/
export function cssModulesWebpackRule({ styleLoader }: CssModulesWebpackRuleOptions = {}): webpack.RuleSetRule {
styleLoader ??= isDevelopment
? "style-loader"
: MiniCssExtractPlugin.loader;
return {
test: /\.s?css$/,
use: [
styleLoader,
{
loader: "css-loader",
options: {
sourceMap: isDevelopment,
modules: {
auto: /\.module\./i, // https://github.com/webpack-contrib/css-loader#auto
mode: "local", // :local(.selector) by default
localIdentName: "[name]__[local]--[hash:base64:5]",
},
},
},
],
};
}
export default renderer;
+19
View File
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import assert from "assert";
import path from "path";
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 assetsFolderName = "assets";
export const rendererDir = path.join(process.cwd(), "src", "renderer");
export const htmlTemplate = path.resolve(__dirname, "..", "node_modules", "@k8slens", "open-lens", "src/renderer", "template.html");
export const publicPath = "/build/";
export const sassCommonVars = path.resolve(__dirname, "..", "node_modules", "@k8slens", "open-lens", "src", "renderer", "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");
+4
View File
@@ -0,0 +1,4 @@
import main from "./main";
import renderer from "./renderer";
export default [main, renderer];
File diff suppressed because it is too large. Load diff