refactor/fix integration tests
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
1
.github/workflows/bump-master-version.yaml
vendored
@ -20,7 +20,6 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
yarn install --frozen-lockfile
|
yarn install --frozen-lockfile
|
||||||
yarn lerna bootstrap
|
|
||||||
- name: Bump version to first alpha of next minor version
|
- name: Bump version to first alpha of next minor version
|
||||||
id: bump
|
id: bump
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
8
.github/workflows/main.yml
vendored
@ -35,11 +35,11 @@ jobs:
|
|||||||
- name: Generate Extensions API Reference using typedocs
|
- name: Generate Extensions API Reference using typedocs
|
||||||
run: |
|
run: |
|
||||||
yarn install
|
yarn install
|
||||||
yarn typedocs-extensions-api
|
yarn run build:docs
|
||||||
|
|
||||||
- name: Verify that the markdown is valid
|
- name: Verify that the markdown is valid
|
||||||
run: |
|
run: |
|
||||||
yarn run verify-docs
|
yarn run mkdocs:verify
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Deploy docs
|
name: Deploy docs
|
||||||
@ -77,8 +77,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate Extensions API Reference using typedocs
|
- name: Generate Extensions API Reference using typedocs
|
||||||
run: |
|
run: |
|
||||||
yarn install
|
yarn install --frozen-lockfile
|
||||||
yarn typedocs-extensions-api
|
yarn build:docs
|
||||||
|
|
||||||
- name: mkdocs deploy master
|
- name: mkdocs deploy master
|
||||||
if: contains(github.ref, 'refs/heads/master')
|
if: contains(github.ref, 'refs/heads/master')
|
||||||
|
|||||||
1
.github/workflows/publish-master-npm.yml
vendored
@ -28,7 +28,6 @@ jobs:
|
|||||||
- name: Generate NPM packages
|
- name: Generate NPM packages
|
||||||
run: |
|
run: |
|
||||||
yarn install --frozen-lockfile
|
yarn install --frozen-lockfile
|
||||||
yarn lerna bootstrap
|
|
||||||
yarn run build
|
yarn run build
|
||||||
|
|
||||||
- name: Publish NPM package
|
- name: Publish NPM package
|
||||||
|
|||||||
1
.github/workflows/publish-release-npm.yml
vendored
@ -33,7 +33,6 @@ jobs:
|
|||||||
- name: Generate NPM packages
|
- name: Generate NPM packages
|
||||||
run: |
|
run: |
|
||||||
yarn install --frozen-lockfile
|
yarn install --frozen-lockfile
|
||||||
yarn lerna bootstrap
|
|
||||||
yarn run build
|
yarn run build
|
||||||
|
|
||||||
- name: Publish NPM packages
|
- name: Publish NPM packages
|
||||||
|
|||||||
6
.github/workflows/test.yml
vendored
@ -51,7 +51,7 @@ jobs:
|
|||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
retry_on: error
|
retry_on: error
|
||||||
command: yarn install --frozen-lockfile && yarn lerna bootstrap
|
command: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- run: make test
|
- run: make test
|
||||||
name: Run tests
|
name: Run tests
|
||||||
@ -72,14 +72,14 @@ jobs:
|
|||||||
name: Run Linux integration tests
|
name: Run Linux integration tests
|
||||||
if: ${{ runner.os == 'Linux' && matrix.type == 'smoke' }}
|
if: ${{ runner.os == 'Linux' && matrix.type == 'smoke' }}
|
||||||
|
|
||||||
- run: make integration
|
- run: yarn run test:integration
|
||||||
name: Run macOS integration tests
|
name: Run macOS integration tests
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
ELECTRON_BUILDER_EXTRA_ARGS: "--x64 --arm64"
|
ELECTRON_BUILDER_EXTRA_ARGS: "--x64 --arm64"
|
||||||
if: ${{ runner.os == 'macOS' && matrix.type == 'smoke' }}
|
if: ${{ runner.os == 'macOS' && matrix.type == 'smoke' }}
|
||||||
|
|
||||||
- run: make integration
|
- run: yarn run test:integration
|
||||||
name: Run Windows integration tests
|
name: Run Windows integration tests
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
|
|||||||
2
.yarnrc
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
--install.check-files true
|
||||||
|
--install.network-timeout 100000
|
||||||
20
Makefile
@ -5,15 +5,9 @@ CMD_ARGS = $(filter-out $@,$(MAKECMDGOALS))
|
|||||||
|
|
||||||
ELECTRON_BUILDER_EXTRA_ARGS ?=
|
ELECTRON_BUILDER_EXTRA_ARGS ?=
|
||||||
|
|
||||||
ifeq ($(OS),Windows_NT)
|
.PHONY: bootstrap
|
||||||
DETECTED_OS := Windows
|
bootstrap:
|
||||||
else
|
yarn install
|
||||||
DETECTED_OS := $(shell uname)
|
|
||||||
endif
|
|
||||||
|
|
||||||
node_modules: yarn.lock
|
|
||||||
yarn install --check-files --frozen-lockfile --network-timeout=100000
|
|
||||||
yarn lerna bootstrap
|
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: node_modules
|
lint: node_modules
|
||||||
@ -29,13 +23,7 @@ integration: build
|
|||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build:
|
build:
|
||||||
yarn run build
|
yarn lerna run build:app
|
||||||
ifeq "$(DETECTED_OS)" "Windows"
|
|
||||||
# https://github.com/ukoloff/win-ca#clear-pem-folder-on-publish
|
|
||||||
rm -rf packages/core/node_modules/win-ca/pem
|
|
||||||
endif
|
|
||||||
yarn lerna run build:app --publish onTag $(ELECTRON_BUILDER_EXTRA_ARGS)
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
32
nx.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"tasksRunnerOptions": {
|
||||||
|
"default": {
|
||||||
|
"runner": "nx/tasks-runners/default",
|
||||||
|
"options": {
|
||||||
|
"cacheableOperations": [
|
||||||
|
"build"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targetDefaults": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"{workspaceRoot}/dist"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"build:app": {
|
||||||
|
"dependsOn": [
|
||||||
|
"build"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test:integration": {
|
||||||
|
"dependsOn": [
|
||||||
|
"build:app"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@
|
|||||||
"adr:update-readme": "adr update",
|
"adr:update-readme": "adr update",
|
||||||
"adr:list": "adr list",
|
"adr:list": "adr list",
|
||||||
"build": "lerna run --stream build",
|
"build": "lerna run --stream build",
|
||||||
|
"build:app": "lerna run --stream build:app",
|
||||||
"build:docs": "lerna run --stream build:docs",
|
"build:docs": "lerna run --stream build:docs",
|
||||||
"clean": "lerna run clean --stream",
|
"clean": "lerna run clean --stream",
|
||||||
"clean:node_modules": "lerna clean -y && rm -rf node_modules",
|
"clean:node_modules": "lerna clean -y && rm -rf node_modules",
|
||||||
@ -17,7 +18,8 @@
|
|||||||
"test:integration": "lerna run --stream test:integration",
|
"test:integration": "lerna run --stream test:integration",
|
||||||
"bump-version": "lerna version --no-git-tag-version --no-push",
|
"bump-version": "lerna version --no-git-tag-version --no-push",
|
||||||
"precreate-release-pr": "lerna run build --no-progress --scope @k8slens/release-tool",
|
"precreate-release-pr": "lerna run build --no-progress --scope @k8slens/release-tool",
|
||||||
"create-release-pr": "node ./packages/release-tool/dist/index.mjs"
|
"create-release-pr": "node ./packages/release-tool/dist/index.mjs",
|
||||||
|
"postinstall": "lerna bootstrap"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"adr": "^1.4.3",
|
"adr": "^1.4.3",
|
||||||
|
|||||||
@ -49,18 +49,9 @@
|
|||||||
"build": "env NODE_ENV=production yarn run webpack --config webpack/library-bundle.ts",
|
"build": "env NODE_ENV=production yarn run webpack --config webpack/library-bundle.ts",
|
||||||
"clean": "rm -rf dist webpack/build/ static/build",
|
"clean": "rm -rf dist webpack/build/ static/build",
|
||||||
"compile:node-fetch": "yarn run webpack --config webpack/node-fetch.ts",
|
"compile:node-fetch": "yarn run webpack --config webpack/node-fetch.ts",
|
||||||
|
"dev": "env NODE_ENV=development yarn run webpack --config webpack/library-bundle.ts --watch",
|
||||||
"prepare": "yarn run compile:node-fetch",
|
"prepare": "yarn run compile:node-fetch",
|
||||||
"build:linux": "yarn run compile && electron-builder --linux --dir",
|
"test:unit": "func() { jest ${1} --testPathIgnorePatterns integration; }; func",
|
||||||
"build:mac": "yarn run compile && electron-builder --mac --dir",
|
|
||||||
"build:win": "yarn run compile && electron-builder --win --dir",
|
|
||||||
"integration": "jest --runInBand --detectOpenHandles --forceExit integration",
|
|
||||||
"test:unit": "func() { jest ${1} --watch --testPathIgnorePatterns integration; }; func",
|
|
||||||
"test:integration": "func() { jest ${1:-xyz} --watch --runInBand --detectOpenHandles --forceExit --modulePaths=[\"<rootDir>/integration/\"]; }; func",
|
|
||||||
"dist": "yarn run compile && electron-builder --publish onTag",
|
|
||||||
"dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null",
|
|
||||||
"download:binaries": "yarn run ts-node build/download_binaries.ts",
|
|
||||||
"build:tray-icons": "yarn run ts-node build/generate-tray-icons.ts",
|
|
||||||
"build:theme-vars": "yarn run ts-node build/build_theme_vars.ts",
|
|
||||||
"lint": "PROD=true yarn run eslint --ext js,ts,tsx --max-warnings=0 .",
|
"lint": "PROD=true yarn run eslint --ext js,ts,tsx --max-warnings=0 .",
|
||||||
"lint:fix": "yarn run lint --fix"
|
"lint:fix": "yarn run lint --fix"
|
||||||
},
|
},
|
||||||
|
|||||||
280
packages/open-lens/build/download_binaries.ts
Normal file
@ -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));
|
||||||
12
packages/open-lens/build/entitlements.mac.plist
Normal file
@ -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>
|
||||||
139
packages/open-lens/build/generate-tray-icons.ts
Normal file
@ -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, "../src/renderer/components/icon/logo-lens.svg");
|
||||||
|
const noticeFile = process.env.NOTICE_SVG_PATH || path.resolve(__dirname, "../src/renderer/components/icon/notice.svg");
|
||||||
|
const spinnerFile = process.env.SPINNER_SVG_PATH || path.resolve(__dirname, "../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();
|
||||||
BIN
packages/open-lens/build/icon.ico
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
packages/open-lens/build/icon.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
packages/open-lens/build/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
packages/open-lens/build/icons/512x512@2x.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
15
packages/open-lens/build/installer.nsh
Normal 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
packages/open-lens/build/notarize.js
Normal 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,
|
||||||
|
});
|
||||||
|
};
|
||||||
BIN
packages/open-lens/build/tray/trayIcon.png
Normal file
|
After Width: | Height: | Size: 392 B |
BIN
packages/open-lens/build/tray/trayIcon@2x.png
Normal file
|
After Width: | Height: | Size: 724 B |
BIN
packages/open-lens/build/tray/trayIcon@3x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
packages/open-lens/build/tray/trayIcon@4x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
packages/open-lens/build/tray/trayIconCheckingForUpdates.png
Normal file
|
After Width: | Height: | Size: 504 B |
BIN
packages/open-lens/build/tray/trayIconCheckingForUpdates@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
packages/open-lens/build/tray/trayIconCheckingForUpdates@3x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/open-lens/build/tray/trayIconCheckingForUpdates@4x.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 993 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
BIN
packages/open-lens/build/tray/trayIconTemplate.png
Normal file
|
After Width: | Height: | Size: 397 B |
BIN
packages/open-lens/build/tray/trayIconTemplate@2x.png
Normal file
|
After Width: | Height: | Size: 717 B |
BIN
packages/open-lens/build/tray/trayIconTemplate@3x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
packages/open-lens/build/tray/trayIconTemplate@4x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
packages/open-lens/build/tray/trayIconUpdateAvailable.png
Normal file
|
After Width: | Height: | Size: 518 B |
BIN
packages/open-lens/build/tray/trayIconUpdateAvailable@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
packages/open-lens/build/tray/trayIconUpdateAvailable@3x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/open-lens/build/tray/trayIconUpdateAvailable@4x.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 466 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
2
packages/open-lens/build/webpack/node-fetch.bundle.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
/*! node-domexception. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||||
@ -10,12 +10,12 @@ import * as uuid from "uuid";
|
|||||||
import type { ElectronApplication, Frame, Page } from "playwright";
|
import type { ElectronApplication, Frame, Page } from "playwright";
|
||||||
import { _electron as electron } from "playwright";
|
import { _electron as electron } from "playwright";
|
||||||
import { noop } from "lodash";
|
import { noop } from "lodash";
|
||||||
import { disposer } from "../../src/common/utils";
|
import { disposer } from "../../../core/src/common/utils";
|
||||||
|
|
||||||
export const appPaths: Partial<Record<NodeJS.Platform, string>> = {
|
export const appPaths: Partial<Record<NodeJS.Platform, string>> = {
|
||||||
"win32": "./dist/win-unpacked/OpenLens.exe",
|
"win32": "./dist/win-unpacked/OpenLens.exe",
|
||||||
"linux": "./dist/linux-unpacked/open-lens",
|
"linux": "./dist/linux-unpacked/open-lens",
|
||||||
"darwin": "./dist/mac/OpenLens.app/Contents/MacOS/OpenLens",
|
"darwin": `./dist/mac${ process.arch === "arm64" ? "-arm64" : "" }/OpenLens.app/Contents/MacOS/OpenLens`,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getMainWindow(app: ElectronApplication, timeout = 50_000): Promise<Page> {
|
async function getMainWindow(app: ElectronApplication, timeout = 50_000): Promise<Page> {
|
||||||
6
packages/open-lens/integration/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"./**/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -20,7 +20,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf binaries/ dist/ static/build",
|
"clean": "rm -rf binaries/ dist/ static/build",
|
||||||
"build": "npm run compile",
|
"build": "npm run compile",
|
||||||
"build:app": "electron-builder --publish onTag",
|
"build:app": "electron-builder --publish onTag $ELECTRON_BUILDER_EXTRA_ARGS",
|
||||||
|
"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",
|
"build:dir": "npm run compile && electron-builder --dir",
|
||||||
"compile": "env NODE_ENV=production webpack --config webpack/webpack.ts --progress",
|
"compile": "env NODE_ENV=production webpack --config webpack/webpack.ts --progress",
|
||||||
"postcompile": "npm run build:tray-icons && npm run download:binaries",
|
"postcompile": "npm run build:tray-icons && npm run download:binaries",
|
||||||
@ -29,8 +32,9 @@
|
|||||||
"dev-run": "nodemon --watch ./static/build/main.js --exec \"electron --remote-debugging-port=9223 --inspect .\"",
|
"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:main": "env NODE_ENV=development webpack --config webpack/main.ts --progress --watch",
|
||||||
"dev:renderer": "env NODE_ENV=development ts-node ./webpack/dev-server.ts",
|
"dev:renderer": "env NODE_ENV=development ts-node ./webpack/dev-server.ts",
|
||||||
"build:tray-icons": "ts-node ../core/build/generate-tray-icons.ts",
|
"test:integration": "func() { jest ${1:-xyz} --runInBand --detectOpenHandles --forceExit --modulePaths=[\"<rootDir>/integration/\"]; }; func",
|
||||||
"download:binaries": "ts-node ../core/build/download_binaries.ts"
|
"build:tray-icons": "ts-node build/generate-tray-icons.ts",
|
||||||
|
"download:binaries": "ts-node build/download_binaries.ts"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"k8sProxyVersion": "0.3.0",
|
"k8sProxyVersion": "0.3.0",
|
||||||
@ -50,26 +54,21 @@
|
|||||||
"^.+\\.(t|j)sx?$": [
|
"^.+\\.(t|j)sx?$": [
|
||||||
"@swc/jest"
|
"@swc/jest"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"testEnvironment": "jsdom",
|
},
|
||||||
"resolver": "<rootDir>/src/jest-28-resolver.js",
|
"nx": {
|
||||||
"moduleNameMapper": {
|
"targets": {
|
||||||
"\\.(css|scss)$": "identity-obj-proxy",
|
"build": {
|
||||||
"\\.(svg|png|jpg|eot|woff2?|ttf)$": "<rootDir>/__mocks__/assetMock.ts"
|
"dependsOn": [
|
||||||
},
|
"^build"
|
||||||
"modulePathIgnorePatterns": [
|
],
|
||||||
"<rootDir>/dist",
|
"outputs": [
|
||||||
"<rootDir>/packages"
|
"{workspaceRoot}/dist/",
|
||||||
],
|
"{workspaceRoot}/binaries/",
|
||||||
"setupFiles": [
|
"{workspaceRoot}/static/build/"
|
||||||
"<rootDir>/src/jest.setup.ts",
|
]
|
||||||
"jest-canvas-mock"
|
}
|
||||||
],
|
}
|
||||||
"globalSetup": "<rootDir>/src/jest.timezone.ts",
|
|
||||||
"setupFilesAfterEnv": [
|
|
||||||
"<rootDir>/src/jest-after-env.setup.ts"
|
|
||||||
],
|
|
||||||
"runtime": "@side/jest-runtime"
|
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"generateUpdatesFilesForAllChannels": true,
|
"generateUpdatesFilesForAllChannels": true,
|
||||||
@ -78,7 +77,7 @@
|
|||||||
"!node_modules/@k8slens/open-lens/node_modules/**/*",
|
"!node_modules/@k8slens/open-lens/node_modules/**/*",
|
||||||
"!node_modules/@k8slens/open-lens/src"
|
"!node_modules/@k8slens/open-lens/src"
|
||||||
],
|
],
|
||||||
"afterSign": "../core/build/notarize.js",
|
"afterSign": "build/notarize.js",
|
||||||
"extraResources": [
|
"extraResources": [
|
||||||
{
|
{
|
||||||
"from": "templates/",
|
"from": "templates/",
|
||||||
@ -118,8 +117,8 @@
|
|||||||
"mac": {
|
"mac": {
|
||||||
"hardenedRuntime": true,
|
"hardenedRuntime": true,
|
||||||
"gatekeeperAssess": false,
|
"gatekeeperAssess": false,
|
||||||
"entitlements": "../core/build/entitlements.mac.plist",
|
"entitlements": "build/entitlements.mac.plist",
|
||||||
"entitlementsInherit": "../core/build/entitlements.mac.plist",
|
"entitlementsInherit": "core/build/entitlements.mac.plist",
|
||||||
"extraResources": [
|
"extraResources": [
|
||||||
{
|
{
|
||||||
"from": "binaries/client/darwin/${arch}/kubectl",
|
"from": "binaries/client/darwin/${arch}/kubectl",
|
||||||
@ -155,7 +154,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"include": "../core/build/installer.nsh",
|
"include": "build/installer.nsh",
|
||||||
"oneClick": false,
|
"oneClick": false,
|
||||||
"allowElevation": true,
|
"allowElevation": true,
|
||||||
"createStartMenuShortcut": true,
|
"createStartMenuShortcut": true,
|
||||||
@ -180,6 +179,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
||||||
|
"@swc/core": "^1.3.26",
|
||||||
|
"@swc/jest": "^0.2.24",
|
||||||
"@types/byline": "^4.2.33",
|
"@types/byline": "^4.2.33",
|
||||||
"@types/chart.js": "^2.9.36",
|
"@types/chart.js": "^2.9.36",
|
||||||
"@types/color": "^3.0.3",
|
"@types/color": "^3.0.3",
|
||||||
@ -212,6 +213,8 @@
|
|||||||
"fork-ts-checker-webpack-plugin": "^7.2.14",
|
"fork-ts-checker-webpack-plugin": "^7.2.14",
|
||||||
"gunzip-maybe": "^1.4.2",
|
"gunzip-maybe": "^1.4.2",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"jest": "^28.1.3",
|
||||||
|
"jest-environment-jsdom": "^28.1.3",
|
||||||
"jsdom": "^20.0.3",
|
"jsdom": "^20.0.3",
|
||||||
"jsonfile": "^6.1.0",
|
"jsonfile": "^6.1.0",
|
||||||
"mini-css-extract-plugin": "^2.7.1",
|
"mini-css-extract-plugin": "^2.7.1",
|
||||||
@ -219,9 +222,11 @@
|
|||||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||||
"node-loader": "^2.0.0",
|
"node-loader": "^2.0.0",
|
||||||
"nodemon": "^2.0.20",
|
"nodemon": "^2.0.20",
|
||||||
|
"playwright": "^1.29.2",
|
||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
"react-refresh-typescript": "^2.0.7",
|
"react-refresh-typescript": "^2.0.7",
|
||||||
"react-select": "^5.7.0",
|
"react-select": "^5.7.0",
|
||||||
|
"run-script-os": "^1.1.6",
|
||||||
"sharp": "^0.31.2",
|
"sharp": "^0.31.2",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
"tailwindcss": "^3.2.4",
|
"tailwindcss": "^3.2.4",
|
||||||
|
|||||||