diff --git a/README.md b/README.md index 5e97035d0f..0c4a63c174 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,7 @@ See [Development](https://docs.k8slens.dev/latest/contributing/development/) pag ## Contributing See [Contributing](https://docs.k8slens.dev/latest/contributing/) page. + +## License + +See [License](LICENSE). diff --git a/build/download_binaries.ts b/build/download_binaries.ts index ff26a8fb2b..4b8d38904c 100644 --- a/build/download_binaries.ts +++ b/build/download_binaries.ts @@ -194,7 +194,7 @@ async function main() { }, multiBar), ]; - if (normalizedPlatform === "darwin") { + if (normalizedPlatform !== "windows") { downloaders.push( new LensK8sProxyDownloader({ version: packageInfo.config.k8sProxyVersion, diff --git a/extensions/kube-object-event-status/package.json b/extensions/kube-object-event-status/package.json index a453097288..53db719a83 100644 --- a/extensions/kube-object-event-status/package.json +++ b/extensions/kube-object-event-status/package.json @@ -16,7 +16,6 @@ "dist/**/*" ], "devDependencies": { - "@k8slens/extensions": "file:../../src/extensions/npm/extensions", - "npm": "^8.5.3" + "@k8slens/extensions": "file:../../src/extensions/npm/extensions" } } diff --git a/extensions/metrics-cluster-feature/package.json b/extensions/metrics-cluster-feature/package.json index 708d644b28..41da8f3f86 100644 --- a/extensions/metrics-cluster-feature/package.json +++ b/extensions/metrics-cluster-feature/package.json @@ -19,7 +19,6 @@ ], "devDependencies": { "@k8slens/extensions": "file:../../src/extensions/npm/extensions", - "npm": "^8.5.3", "semver": "^7.3.2" } } diff --git a/extensions/node-menu/package.json b/extensions/node-menu/package.json index 4ec9fb5688..7705b4f606 100644 --- a/extensions/node-menu/package.json +++ b/extensions/node-menu/package.json @@ -17,7 +17,6 @@ ], "dependencies": {}, "devDependencies": { - "@k8slens/extensions": "file:../../src/extensions/npm/extensions", - "npm": "^8.5.3" + "@k8slens/extensions": "file:../../src/extensions/npm/extensions" } } diff --git a/extensions/pod-menu/package.json b/extensions/pod-menu/package.json index c55c9bdd75..79e4278b32 100644 --- a/extensions/pod-menu/package.json +++ b/extensions/pod-menu/package.json @@ -17,7 +17,6 @@ ], "dependencies": {}, "devDependencies": { - "@k8slens/extensions": "file:../../src/extensions/npm/extensions", - "npm": "^8.5.3" + "@k8slens/extensions": "file:../../src/extensions/npm/extensions" } } diff --git a/integration/__tests__/app-preferences.tests.ts b/integration/__tests__/app-preferences.tests.ts index ed3cc71099..2054a5342d 100644 --- a/integration/__tests__/app-preferences.tests.ts +++ b/integration/__tests__/app-preferences.tests.ts @@ -11,7 +11,6 @@ */ import type { ElectronApplication, Page } from "playwright"; import * as utils from "../helpers/utils"; -import { isWindows } from "../../src/common/vars"; describe("preferences page tests", () => { let window: Page, cleanup: () => Promise; @@ -34,8 +33,7 @@ describe("preferences page tests", () => { await cleanup(); }, 10*60*1000); - // skip on windows due to suspected playwright issue with Electron 14 - utils.itIf(!isWindows)('shows "preferences" and can navigate through the tabs', async () => { + it('shows "preferences" and can navigate through the tabs', async () => { const pages = [ { id: "application", diff --git a/integration/__tests__/command-palette.tests.ts b/integration/__tests__/command-palette.tests.ts index 7073d1d4cc..8356379b60 100644 --- a/integration/__tests__/command-palette.tests.ts +++ b/integration/__tests__/command-palette.tests.ts @@ -5,7 +5,6 @@ import type { ElectronApplication, Page } from "playwright"; import * as utils from "../helpers/utils"; -import { isWindows } from "../../src/common/vars"; describe("Lens command palette", () => { let window: Page, cleanup: () => Promise, app: ElectronApplication; @@ -20,8 +19,7 @@ describe("Lens command palette", () => { }, 10*60*1000); describe("menu", () => { - // skip on windows due to suspected playwright issue with Electron 14 - utils.itIf(!isWindows)("opens command dialog from menu", async () => { + it("opens command dialog from menu", async () => { await app.evaluate(async ({ app }) => { await app.applicationMenu ?.getMenuItemById("view") diff --git a/package.json b/package.json index f9c34db678..48260cdfff 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "6.0.0", + "version": "6.1.0", "main": "static/build/main.js", "copyright": "© 2022 OpenLens Authors", "license": "MIT", @@ -56,7 +56,8 @@ "bundledKubectlVersion": "1.23.3", "bundledHelmVersion": "3.7.2", "sentryDsn": "", - "contentSecurityPolicy": "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:" + "contentSecurityPolicy": "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:", + "welcomeRoute": "/welcome" }, "engines": { "node": ">=16 <17" @@ -216,14 +217,14 @@ "@astronautlabs/jsonpath": "^1.1.0", "@hapi/call": "^9.0.0", "@hapi/subtext": "^7.0.4", - "@kubernetes/client-node": "^0.17.0", + "@kubernetes/client-node": "^0.17.1", "@material-ui/styles": "^4.11.5", - "@ogre-tools/fp": "9.0.3", - "@ogre-tools/injectable": "9.0.3", - "@ogre-tools/injectable-extension-for-auto-registration": "9.0.3", - "@ogre-tools/injectable-extension-for-mobx": "9.0.3", - "@ogre-tools/injectable-react": "9.0.3", - "@sentry/electron": "^3.0.7", + "@ogre-tools/fp": "10.1.0", + "@ogre-tools/injectable": "10.1.0", + "@ogre-tools/injectable-extension-for-auto-registration": "10.1.0", + "@ogre-tools/injectable-extension-for-mobx": "10.1.0", + "@ogre-tools/injectable-react": "10.1.0", + "@sentry/electron": "^3.0.8", "@sentry/integrations": "^6.19.3", "@side/jest-runtime": "^1.0.1", "@types/circular-dependency-plugin": "5.0.5", @@ -251,11 +252,11 @@ "jsdom": "^16.7.0", "lodash": "^4.17.15", "mac-ca": "^1.0.6", - "marked": "^4.0.19", + "marked": "^4.1.0", "md5-file": "^5.0.0", - "mobx": "^6.6.1", + "mobx": "^6.6.2", "mobx-observable-history": "^2.0.3", - "mobx-react": "^7.5.2", + "mobx-react": "^7.5.3", "mobx-utils": "^6.0.4", "mock-fs": "^5.1.4", "moment": "^2.29.4", @@ -264,7 +265,7 @@ "monaco-editor-webpack-plugin": "^5.0.0", "node-fetch": "^2.6.7", "node-pty": "0.10.1", - "npm": "^6.14.17", + "npm": "^8.19.2", "p-limit": "^3.1.0", "path-to-regexp": "^6.2.0", "proper-lockfile": "^4.1.2", @@ -272,12 +273,12 @@ "react-dom": "^17.0.2", "react-material-ui-carousel": "^2.3.11", "react-router": "^5.2.0", - "react-virtualized-auto-sizer": "^1.0.6", + "react-virtualized-auto-sizer": "^1.0.7", "readable-stream": "^3.6.0", "request": "^2.88.2", "request-promise-native": "^1.0.9", "rfc6902": "^4.0.2", - "selfsigned": "^2.0.1", + "selfsigned": "^2.1.1", "semver": "^7.3.7", "shell-env": "^3.0.1", "spdy": "^4.0.2", @@ -288,7 +289,7 @@ "url-parse": "^1.5.10", "uuid": "^8.3.2", "win-ca": "^3.5.0", - "winston": "^3.8.1", + "winston": "^3.8.2", "winston-console-format": "^1.0.8", "winston-transport-browserconsole": "^1.0.5", "ws": "^8.8.1", @@ -302,7 +303,7 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@sentry/types": "^6.19.7", "@swc/cli": "^0.1.57", - "@swc/core": "^1.2.242", + "@swc/core": "^1.3.1", "@swc/jest": "^0.2.22", "@testing-library/dom": "^7.31.2", "@testing-library/jest-dom": "^5.16.5", @@ -327,12 +328,12 @@ "@types/jest": "^28.1.6", "@types/js-yaml": "^4.0.5", "@types/jsdom": "^16.2.14", - "@types/lodash": "^4.14.184", - "@types/marked": "^4.0.6", + "@types/lodash": "^4.14.185", + "@types/marked": "^4.0.7", "@types/md5-file": "^4.0.2", "@types/mini-css-extract-plugin": "^2.4.0", "@types/mock-fs": "^4.13.1", - "@types/node": "^16.11.55", + "@types/node": "^16.11.59", "@types/node-fetch": "^2.6.2", "@types/npm": "^2.0.32", "@types/proper-lockfile": "^4.1.2", @@ -362,28 +363,28 @@ "@types/webpack-dev-server": "^4.7.2", "@types/webpack-env": "^1.18.0", "@types/webpack-node-externals": "^2.5.3", - "@typescript-eslint/eslint-plugin": "^5.35.1", - "@typescript-eslint/parser": "^5.35.1", - "adr": "^1.4.1", + "@typescript-eslint/eslint-plugin": "^5.37.0", + "@typescript-eslint/parser": "^5.37.0", + "adr": "^1.4.2", "ansi_up": "^5.1.0", "chart.js": "^2.9.4", "circular-dependency-plugin": "^5.2.2", "cli-progress": "^3.11.2", "color": "^3.2.1", "command-line-args": "^5.2.1", - "concurrently": "^7.3.0", + "concurrently": "^7.4.0", "css-loader": "^6.7.1", "deepdash": "^5.3.9", "dompurify": "^2.4.0", - "electron": "^19.0.13", + "electron": "^19.0.17", "electron-builder": "^23.3.3", "electron-notarize": "^0.3.0", - "esbuild": "^0.15.6", - "esbuild-loader": "^2.19.0", - "eslint": "^8.23.0", + "esbuild": "^0.15.7", + "esbuild-loader": "^2.20.0", + "eslint": "^8.23.1", "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-react": "7.30.1", + "eslint-plugin-react": "7.31.8", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-unused-imports": "^2.0.0", "flex.box": "^3.4.4", @@ -397,18 +398,19 @@ "jest-canvas-mock": "^2.3.1", "jest-environment-jsdom": "^28.1.3", "jest-fetch-mock": "^3.0.3", - "jest-mock-extended": "^2.0.7", + "jest-mock-extended": "^2.0.9", "make-plural": "^6.2.2", "mini-css-extract-plugin": "^2.6.1", "mock-http": "^1.1.0", "node-gyp": "^8.3.0", "node-loader": "^2.0.0", "nodemon": "^2.0.19", - "playwright": "^1.25.1", + "playwright": "^1.25.2", "postcss": "^8.4.16", "postcss-loader": "^6.2.1", + "query-string": "^7.1.1", "randomcolor": "^0.6.2", - "react-beautiful-dnd": "^13.1.0", + "react-beautiful-dnd": "^13.1.1", "react-refresh": "^0.14.0", "react-refresh-typescript": "^2.0.7", "react-router-dom": "^5.3.3", @@ -416,9 +418,9 @@ "react-select-event": "^5.5.1", "react-table": "^7.8.0", "react-window": "^1.8.7", - "sass": "^1.54.6", + "sass": "^1.54.9", "sass-loader": "^12.6.0", - "sharp": "^0.30.7", + "sharp": "^0.31.0", "style-loader": "^3.3.1", "tailwindcss": "^3.1.8", "tar-stream": "^2.2.0", @@ -426,13 +428,13 @@ "ts-node": "^10.9.1", "type-fest": "^2.14.0", "typed-emitter": "^1.4.0", - "typedoc": "0.23.11", + "typedoc": "0.23.14", "typedoc-plugin-markdown": "^3.13.1", - "typescript": "^4.7.4", + "typescript": "^4.8.3", "typescript-plugin-css-modules": "^3.4.0", "webpack": "^5.74.0", "webpack-cli": "^4.9.2", - "webpack-dev-server": "^4.10.1", + "webpack-dev-server": "^4.11.0", "webpack-node-externals": "^3.0.0", "xterm": "^4.19.0", "xterm-addon-fit": "^0.5.0" diff --git a/scripts/create-release-pr.ts b/scripts/create-release-pr.ts index da0d5d4f26..be4a13d426 100755 --- a/scripts/create-release-pr.ts +++ b/scripts/create-release-pr.ts @@ -8,7 +8,7 @@ import fse from "fs-extra"; import { basename } from "path"; import { createInterface } from "readline"; import semver from "semver"; -import { inspect, promisify } from "util"; +import { promisify } from "util"; const { SemVer, @@ -27,6 +27,10 @@ const options = commandLineArgs([ { name: "preid", }, + { + name: "check-commits", + type: Boolean, + }, ]); const validReleaseValues = [ @@ -79,10 +83,22 @@ if (basename(process.cwd()) === "scripts") { console.error(errorMessages.wrongCwd); } - -const currentVersion = new SemVer((await fse.readJson("./package.json")).version); +const packageJson = await fse.readJson("./package.json"); +const currentVersion = new SemVer(packageJson.version); console.log(`current version: ${currentVersion.format()}`); + +const newVersion = currentVersion.inc(options.type, options.preid); +const newVersionMilestone = `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`; +const prBranch = `release/v${newVersion.format()}`; + +await fse.writeJson("./package.json", { ...packageJson, version: newVersion.format() }, { spaces: 2 }); +await exec(`git checkout -b ${prBranch}`); +await exec("git add package.json"); +await exec(`git commit -sm "Release ${newVersion.format()}"`); + +console.log(`new version: ${newVersion.format()}`); + console.log("fetching tags..."); await exec("git fetch --tags --force"); @@ -93,25 +109,6 @@ const [previousReleasedVersion] = actualTags .sort((l, r) => semverRcompare(l, r)) .filter(version => semverLte(version, currentVersion)); -const npmVersionArgs = [ - "npm", - "version", - options.type, -]; - -if (options.preid) { - npmVersionArgs.push(`--preid=${options.preid}`); -} - -npmVersionArgs.push("--git-tag-version false"); - -await exec(npmVersionArgs.join(" ")); - -const newVersion = new SemVer((await fse.readJson("./package.json")).version); -const newVersionMilestone = `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`; - -console.log(`new version: ${newVersion.format()}`); - const getMergedPrsArgs = [ "gh", "pr", @@ -146,6 +143,10 @@ interface GithubPrData { title: string; } +interface ExtendedGithubPrData extends Omit { + mergedAt: Date; +} + console.log("retreiving last 500 PRs to create release PR body..."); const mergedPrs = JSON.parse((await exec(getMergedPrsArgs.join(" "), { encoding: "utf-8" })).stdout) as GithubPrData[]; const milestoneRelevantPrs = mergedPrs.filter(pr => pr.milestone?.title === newVersionMilestone); @@ -159,7 +160,7 @@ const relaventPrs = relaventPrsQuery .filter(query => query.stdout) .map(query => query.pr) .filter(pr => pr.labels.every(label => label.name !== "skip-changelog")) - .map(pr => ({ ...pr, mergedAt: new Date(pr.mergedAt) })) + .map(pr => ({ ...pr, mergedAt: new Date(pr.mergedAt) } as ExtendedGithubPrData)) .sort((left, right) => { const leftAge = left.mergedAt.valueOf(); const rightAge = right.mergedAt.valueOf(); @@ -175,75 +176,55 @@ const relaventPrs = relaventPrsQuery return -1; }); -console.log(inspect(relaventPrs, false, null, true)); - const enhancementPrLabelName = "enhancement"; const bugfixPrLabelName = "bug"; -const enhancementPrs = relaventPrs.filter(pr => pr.labels.some(label => label.name === enhancementPrLabelName)); -const bugfixPrs = relaventPrs.filter(pr => pr.labels.some(label => label.name === bugfixPrLabelName)); -const maintenencePrs = relaventPrs.filter(pr => pr.labels.every(label => label.name !== bugfixPrLabelName && label.name !== enhancementPrLabelName)); +const isEnhancementPr = (pr: ExtendedGithubPrData) => pr.labels.some(label => label.name === enhancementPrLabelName); +const isBugfixPr = (pr: ExtendedGithubPrData) => pr.labels.some(label => label.name === bugfixPrLabelName); -console.log("Found:"); -console.log(`${enhancementPrs.length} enhancement PRs`); -console.log(`${bugfixPrs.length} bug fix PRs`); -console.log(`${maintenencePrs.length} maintenence PRs`); +const prLines = { + enhancement: [] as string[], + bugfix: [] as string[], + maintenence: [] as string[], +}; -const prBodyLines = [ - `## Changes since ${previousReleasedVersion}`, - "", -]; - -function getPrEntry(pr) { +function getPrEntry(pr: ExtendedGithubPrData) { return `- ${pr.title} (**[#${pr.number}](https://github.com/lensapp/lens/pull/${pr.number})**) https://github.com/${pr.author.login}`; } -if (enhancementPrs.length > 0) { - prBodyLines.push( - "## 🚀 Features", - "", - ...enhancementPrs.map(getPrEntry), - "", - ); -} - -if (bugfixPrs.length > 0) { - prBodyLines.push( - "## 🐛 Bug Fixes", - "", - ...bugfixPrs.map(getPrEntry), - "", - ); -} - -if (maintenencePrs.length > 0) { - prBodyLines.push( - "## 🧰 Maintenance", - "", - ...maintenencePrs.map(getPrEntry), - "", - ); -} - -const prBody = prBodyLines.join("\n"); +const rl = createInterface(process.stdin); const prBase = newVersion.patch === 0 ? "master" : `release/v${newVersion.major}.${newVersion.minor}`; -const createPrArgs = [ - "pr", - "create", - "--base", prBase, - "--title", `release ${newVersion.format()}`, - "--label", "skip-changelog", - "--body-file", "-", -]; -const rl = createInterface(process.stdin); +function askQuestion(question: string): Promise { + return new Promise(resolve => { + function _askQuestion() { + console.log(question); -if (prBase !== "master") { - console.log("Cherry-picking commits to current branch"); + rl.once("line", (answer) => { + const cleaned = answer.trim().toLowerCase(); - for (const pr of relaventPrs) { + if (cleaned === "y") { + resolve(true); + } else if (cleaned === "n") { + resolve(false); + } else { + _askQuestion(); + } + }); + } + + _askQuestion(); + }); +} + +async function handleRelaventPr(pr: ExtendedGithubPrData) { + if (options["check-commits"] && !(await askQuestion(`Would you like to use #${pr.number}: ${pr.title}? - Y/N`))) { + return; + } + + if (prBase !== "master") { try { const promise = exec(`git cherry-pick ${pr.mergeCommit.oid}`); @@ -255,11 +236,71 @@ if (prBase !== "master") { await promise; } catch { console.error(`Failed to cherry-pick ${pr.mergeCommit.oid}, please resolve conflicts and then press enter here:`); - await new Promise(resolve => rl.on("line", () => resolve())); + await new Promise(resolve => rl.once("line", () => resolve())); } } + + if (isEnhancementPr(pr)) { + prLines.enhancement.push(getPrEntry(pr)); + } else if (isBugfixPr(pr)) { + prLines.bugfix.push(getPrEntry(pr)); + } else { + prLines.maintenence.push(getPrEntry(pr)); + } } +for (const pr of relaventPrs) { + await handleRelaventPr(pr); +} + +rl.close(); + +const prBodyLines = [ + `## Changes since ${previousReleasedVersion}`, + "", + ...( + prLines.enhancement.length > 0 + ? [ + "## 🚀 Features", + "", + ...prLines.enhancement, + "", + ] + : [] + ), + ...( + prLines.bugfix.length > 0 + ? [ + "## 🐛 Bug Fixes", + "", + ...prLines.bugfix, + "", + ] + : [] + ), + ...( + prLines.maintenence.length > 0 + ? [ + "## 🧰 Maintenance", + "", + ...prLines.maintenence, + "", + ] + : [] + ), +]; +const prBody = prBodyLines.join("\n"); +const createPrArgs = [ + "pr", + "create", + "--base", prBase, + "--title", `Release ${newVersion.format()}`, + "--label", "skip-changelog", + "--body-file", "-", +]; + +await exec(`git push --set-upstream origin ${prBranch}`); + const createPrProcess = execFile("gh", createPrArgs); createPrProcess.child.stdout?.pipe(process.stdout); diff --git a/src/common/__tests__/cluster-store.test.ts b/src/common/__tests__/cluster-store.test.ts index b74659aea0..2253c7662f 100644 --- a/src/common/__tests__/cluster-store.test.ts +++ b/src/common/__tests__/cluster-store.test.ts @@ -18,13 +18,13 @@ import { createClusterInjectionToken } from "../cluster/create-cluster-injection import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; -import appVersionInjectable from "../vars/app-version.injectable"; import assert from "assert"; import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable"; import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable"; import kubectlDownloadingNormalizedArchInjectable from "../../main/kubectl/normalized-arch.injectable"; import normalizedPlatformInjectable from "../vars/normalized-platform.injectable"; import fsInjectable from "../fs/fs.injectable"; +import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable"; console = new Console(stdout, stderr); @@ -372,7 +372,7 @@ users: mockFs(mockOpts); - mainDi.override(appVersionInjectable, () => "3.6.0"); + mainDi.override(storeMigrationVersionInjectable, () => "3.6.0"); createCluster = mainDi.inject(createClusterInjectionToken); diff --git a/src/common/__tests__/hotbar-store.test.ts b/src/common/__tests__/hotbar-store.test.ts index d847ac76dc..0e1b3e27a2 100644 --- a/src/common/__tests__/hotbar-store.test.ts +++ b/src/common/__tests__/hotbar-store.test.ts @@ -8,7 +8,6 @@ import mockFs from "mock-fs"; import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; -import appVersionInjectable from "../vars/app-version.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; import hotbarStoreInjectable from "../hotbars/store.injectable"; import type { HotbarStore } from "../hotbars/store"; @@ -19,6 +18,7 @@ import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog- import loggerInjectable from "../logger.injectable"; import type { Logger } from "../logger"; import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable"; function getMockCatalogEntity(data: Partial & CatalogEntityKindData): CatalogEntity { return { @@ -348,7 +348,7 @@ describe("HotbarStore", () => { mockFs(configurationToBeMigrated); - di.override(appVersionInjectable, () => "5.0.0-beta.10"); + di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10"); hotbarStore = di.inject(hotbarStoreInjectable); diff --git a/src/common/__tests__/user-store.test.ts b/src/common/__tests__/user-store.test.ts index 23740c5457..a90e03c75f 100644 --- a/src/common/__tests__/user-store.test.ts +++ b/src/common/__tests__/user-store.test.ts @@ -23,8 +23,6 @@ jest.mock("electron", () => ({ import type { UserStore } from "../user-store"; import { Console } from "console"; -import { SemVer } from "semver"; -import electron from "electron"; import { stdout, stderr } from "process"; import userStoreInjectable from "../user-store/user-store.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; @@ -34,7 +32,9 @@ import { defaultThemeId } from "../vars"; import writeFileInjectable from "../fs/write-file.injectable"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; -import appVersionInjectable from "../vars/app-version.injectable"; +import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable"; +import releaseChannelInjectable from "../vars/release-channel.injectable"; +import defaultUpdateChannelInjectable from "../application-update/selected-update-channel/default-update-channel.injectable"; console = new Console(stdout, stderr); @@ -42,7 +42,7 @@ describe("user store tests", () => { let userStore: UserStore; let di: DiContainer; - beforeEach(() => { + beforeEach(async () => { di = getDiForUnitTesting({ doGeneralOverrides: true }); mockFs(); @@ -52,6 +52,12 @@ describe("user store tests", () => { di.permitSideEffects(getConfigurationFileModelInjectable); di.permitSideEffects(userStoreInjectable); + di.override(releaseChannelInjectable, () => ({ + get: () => "latest" as const, + init: async () => {}, + })); + await di.inject(defaultUpdateChannelInjectable).init(); + di.unoverride(userStoreInjectable); }); @@ -64,6 +70,7 @@ describe("user store tests", () => { mockFs({ "some-directory-for-user-data": { "config.json": "{}", "kube_config": "{}" }}); userStore = di.inject(userStoreInjectable); + userStore.load(); }); it("allows setting and retrieving lastSeenAppVersion", () => { @@ -86,13 +93,6 @@ describe("user store tests", () => { userStore.resetTheme(); expect(userStore.colorTheme).toBe(defaultThemeId); }); - - it("correctly calculates if the last seen version is an old release", () => { - expect(userStore.isNewVersion).toBe(true); - - userStore.lastSeenAppVersion = (new SemVer(electron.app.getVersion())).inc("major").format(); - expect(userStore.isNewVersion).toBe(false); - }); }); describe("migrations", () => { @@ -125,9 +125,10 @@ describe("user store tests", () => { }, }); - di.override(appVersionInjectable, () => "10.0.0"); + di.override(storeMigrationVersionInjectable, () => "10.0.0"); userStore = di.inject(userStoreInjectable); + userStore.load(); }); it("sets last seen app version to 0.0.0", () => { diff --git a/src/common/app-event-bus/app-event-bus.global-override-for-injectable.ts b/src/common/app-event-bus/app-event-bus.global-override-for-injectable.ts new file mode 100644 index 0000000000..2cdd21c919 --- /dev/null +++ b/src/common/app-event-bus/app-event-bus.global-override-for-injectable.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getGlobalOverride } from "../test-utils/get-global-override"; +import emitEventInjectable from "./emit-event.injectable"; + +export default getGlobalOverride(emitEventInjectable, () => () => {}); diff --git a/src/common/app-event-bus/app-event-bus.injectable.ts b/src/common/app-event-bus/app-event-bus.injectable.ts index 31ed3dd3a1..d707ec9fc3 100644 --- a/src/common/app-event-bus/app-event-bus.injectable.ts +++ b/src/common/app-event-bus/app-event-bus.injectable.ts @@ -9,6 +9,7 @@ const appEventBusInjectable = getInjectable({ id: "app-event-bus", instantiate: () => appEventBus, causesSideEffects: true, + decorable: false, }); export default appEventBusInjectable; diff --git a/src/common/app-event-bus/emit-event.injectable.ts b/src/common/app-event-bus/emit-event.injectable.ts index 47bf2ca691..d5aaafe37b 100644 --- a/src/common/app-event-bus/emit-event.injectable.ts +++ b/src/common/app-event-bus/emit-event.injectable.ts @@ -8,6 +8,7 @@ import appEventBusInjectable from "./app-event-bus.injectable"; const emitEventInjectable = getInjectable({ id: "emit-event", instantiate: (di) => di.inject(appEventBusInjectable).emit, + decorable: false, }); export default emitEventInjectable; diff --git a/src/common/app-paths/app-paths.test.ts b/src/common/app-paths/app-paths.test.ts index b5ec33059c..ff4bd88988 100644 --- a/src/common/app-paths/app-paths.test.ts +++ b/src/common/app-paths/app-paths.test.ts @@ -7,7 +7,6 @@ import { appPathsInjectionToken } from "./app-path-injection-token"; import getElectronAppPathInjectable from "../../main/app-paths/get-electron-app-path/get-electron-app-path.injectable"; import type { PathName } from "./app-path-names"; import setElectronAppPathInjectable from "../../main/app-paths/set-electron-app-path/set-electron-app-path.injectable"; -import appNameInjectable from "../../main/app-paths/app-name/app-name.injectable"; import directoryForIntegrationTestingInjectable from "../../main/app-paths/directory-for-integration-testing/directory-for-integration-testing.injectable"; import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; @@ -53,8 +52,6 @@ describe("app-paths", () => { defaultAppPathsStub[key] = path; }, ); - - mainDi.override(appNameInjectable, () => "some-app-name"); }); }); @@ -88,7 +85,7 @@ describe("app-paths", () => { recent: "some-recent", temp: "some-temp", videos: "some-videos", - userData: "some-app-data/some-app-name", + userData: "some-app-data/some-product-name", }); }); @@ -111,7 +108,7 @@ describe("app-paths", () => { recent: "some-recent", temp: "some-temp", videos: "some-videos", - userData: "some-app-data/some-app-name", + userData: "some-app-data/some-product-name", }); }); }); @@ -137,7 +134,7 @@ describe("app-paths", () => { expect({ appData, userData }).toEqual({ appData: "some-integration-testing-app-data", - userData: `some-integration-testing-app-data/some-app-name`, + userData: `some-integration-testing-app-data/some-product-name`, }); }); @@ -146,7 +143,7 @@ describe("app-paths", () => { expect({ appData, userData }).toEqual({ appData: "some-integration-testing-app-data", - userData: "some-integration-testing-app-data/some-app-name", + userData: "some-integration-testing-app-data/some-product-name", }); }); }); diff --git a/src/common/application-update/selected-update-channel/default-update-channel.injectable.ts b/src/common/application-update/selected-update-channel/default-update-channel.injectable.ts index 90dc22457a..9c00efa204 100644 --- a/src/common/application-update/selected-update-channel/default-update-channel.injectable.ts +++ b/src/common/application-update/selected-update-channel/default-update-channel.injectable.ts @@ -2,24 +2,13 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getInjectable } from "@ogre-tools/injectable"; -import appSemanticVersionInjectable from "../../vars/app-semantic-version.injectable"; -import type { UpdateChannelId } from "../update-channels"; +import { createInitializableState } from "../../initializable-state/create"; +import releaseChannelInjectable from "../../vars/release-channel.injectable"; import { updateChannels } from "../update-channels"; -const defaultUpdateChannelInjectable = getInjectable({ +const defaultUpdateChannelInjectable = createInitializableState({ id: "default-update-channel", - - instantiate: (di) => { - const appSemanticVersion = di.inject(appSemanticVersionInjectable); - const currentReleaseChannel = appSemanticVersion.prerelease[0]?.toString(); - - if (currentReleaseChannel in updateChannels) { - return updateChannels[currentReleaseChannel as UpdateChannelId]; - } - - return updateChannels.latest; - }, + init: (di) => updateChannels[di.inject(releaseChannelInjectable).get()], }); export default defaultUpdateChannelInjectable; diff --git a/src/common/application-update/selected-update-channel/selected-update-channel.injectable.ts b/src/common/application-update/selected-update-channel/selected-update-channel.injectable.ts index ceb47aee5e..8ca31e00aa 100644 --- a/src/common/application-update/selected-update-channel/selected-update-channel.injectable.ts +++ b/src/common/application-update/selected-update-channel/selected-update-channel.injectable.ts @@ -5,13 +5,13 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { IComputedValue } from "mobx"; import { action, computed, observable } from "mobx"; -import type { UpdateChannel, UpdateChannelId } from "../update-channels"; +import type { UpdateChannel, ReleaseChannel } from "../update-channels"; import { updateChannels } from "../update-channels"; import defaultUpdateChannelInjectable from "./default-update-channel.injectable"; export interface SelectedUpdateChannel { value: IComputedValue; - setValue: (channelId?: UpdateChannelId) => void; + setValue: (channelId?: ReleaseChannel) => void; } const selectedUpdateChannelInjectable = getInjectable({ @@ -19,16 +19,16 @@ const selectedUpdateChannelInjectable = getInjectable({ instantiate: (di): SelectedUpdateChannel => { const defaultUpdateChannel = di.inject(defaultUpdateChannelInjectable); - const state = observable.box(defaultUpdateChannel); + const state = observable.box(); return { - value: computed(() => state.get()), + value: computed(() => state.get() ?? defaultUpdateChannel.get()), setValue: action((channelId) => { const targetUpdateChannel = channelId && updateChannels[channelId] ? updateChannels[channelId] - : defaultUpdateChannel; + : defaultUpdateChannel.get(); state.set(targetUpdateChannel); }), diff --git a/src/common/application-update/update-channels.ts b/src/common/application-update/update-channels.ts index dff1e5879e..1a38974946 100644 --- a/src/common/application-update/update-channels.ts +++ b/src/common/application-update/update-channels.ts @@ -4,7 +4,7 @@ */ -export type UpdateChannelId = "alpha" | "beta" | "latest"; +export type ReleaseChannel = "alpha" | "beta" | "latest"; const latestChannel: UpdateChannel = { id: "latest", @@ -24,14 +24,14 @@ const alphaChannel: UpdateChannel = { moreStableUpdateChannel: betaChannel, }; -export const updateChannels: Record = { +export const updateChannels = { latest: latestChannel, beta: betaChannel, alpha: alphaChannel, }; export interface UpdateChannel { - readonly id: UpdateChannelId; + readonly id: ReleaseChannel; readonly label: string; readonly moreStableUpdateChannel: UpdateChannel | null; } diff --git a/src/common/base-store.ts b/src/common/base-store.ts index 1bc2357458..92383b328d 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -19,7 +19,7 @@ import { kebabCase } from "lodash"; import { getLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import directoryForUserDataInjectable from "./app-paths/directory-for-user-data/directory-for-user-data.injectable"; import getConfigurationFileModelInjectable from "./get-configuration-file-model/get-configuration-file-model.injectable"; -import appVersionInjectable from "./vars/app-version.injectable"; +import storeMigrationVersionInjectable from "./vars/store-migration-version.injectable"; export interface BaseStoreParams extends ConfOptions { syncOptions?: { @@ -31,7 +31,7 @@ export interface BaseStoreParams extends ConfOptions { /** * Note: T should only contain base JSON serializable types. */ -export abstract class BaseStore extends Singleton { +export abstract class BaseStore extends Singleton { protected storeConfig?: Config; protected syncDisposers: Disposer[] = []; @@ -59,10 +59,10 @@ export abstract class BaseStore extends Singleton { const getConfigurationFileModel = di.inject(getConfigurationFileModelInjectable); this.storeConfig = getConfigurationFileModel({ - ...this.params, projectName: "lens", - projectVersion: di.inject(appVersionInjectable), + projectVersion: di.inject(storeMigrationVersionInjectable), cwd: this.cwd(), + ...this.params, }); const res: any = this.fromStore(this.storeConfig.store); diff --git a/src/common/catalog-entities/__tests__/kubernetes-cluster.test.ts b/src/common/catalog-entities/__tests__/kubernetes-cluster.test.ts index d681713a80..b2814f9785 100644 --- a/src/common/catalog-entities/__tests__/kubernetes-cluster.test.ts +++ b/src/common/catalog-entities/__tests__/kubernetes-cluster.test.ts @@ -12,7 +12,7 @@ describe("kubernetesClusterCategory", () => { let kubernetesClusterCategory: KubernetesClusterCategory; beforeEach(() => { - const di = getDiForUnitTesting(); + const di = getDiForUnitTesting({ doGeneralOverrides: true }); kubernetesClusterCategory = di.inject(kubernetesClusterCategoryInjectable); }); diff --git a/src/common/catalog-entities/web-link.ts b/src/common/catalog-entities/web-link.ts index 35dc86be57..dade63af16 100644 --- a/src/common/catalog-entities/web-link.ts +++ b/src/common/catalog-entities/web-link.ts @@ -3,9 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog"; import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity"; -import { productName } from "../vars"; +import productNameInjectable from "../vars/product-name.injectable"; import { WeblinkStore } from "../weblink-store"; export type WebLinkStatusPhase = "available" | "unavailable"; @@ -30,6 +31,9 @@ export class WebLink extends CatalogEntity () => {}); diff --git a/src/common/error-reporting/initialize-sentry-reporting.injectable.ts b/src/common/error-reporting/initialize-sentry-reporting.injectable.ts new file mode 100644 index 0000000000..778f959739 --- /dev/null +++ b/src/common/error-reporting/initialize-sentry-reporting.injectable.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { ElectronMainOptions } from "@sentry/electron/main"; +import type { BrowserOptions } from "@sentry/electron/renderer"; +import isProductionInjectable from "../vars/is-production.injectable"; +import sentryDataSourceNameInjectable from "../vars/sentry-dsn-url.injectable"; +import { Dedupe, Offline } from "@sentry/integrations"; +import { inspect } from "util"; +import userStoreInjectable from "../user-store/user-store.injectable"; + +export type InitializeSentryReportingWith = (initSentry: (opts: BrowserOptions | ElectronMainOptions) => void) => void; + +const mapProcessName = (type: "browser" | "renderer" | "worker") => type === "browser" ? "main" : type; + +const initializeSentryReportingWithInjectable = getInjectable({ + id: "initialize-sentry-reporting-with", + instantiate: (di): InitializeSentryReportingWith => { + const sentryDataSourceName = di.inject(sentryDataSourceNameInjectable); + const isProduction = di.inject(isProductionInjectable); + const userStore = di.inject(userStoreInjectable); + + if (!sentryDataSourceName) { + return () => {}; + } + + return (initSentry) => initSentry({ + beforeSend: (event) => { + if (userStore.allowErrorReporting) { + return event; + } + + /** + * Directly write to stdout so that no other integrations capture this and create an infinite loop + */ + process.stdout.write(`🔒 [SENTRY-BEFORE-SEND-HOOK]: Sentry event is caught but not sent to server.`); + process.stdout.write("🔒 [SENTRY-BEFORE-SEND-HOOK]: === START OF SENTRY EVENT ==="); + process.stdout.write(inspect(event, false, null, true)); + process.stdout.write("🔒 [SENTRY-BEFORE-SEND-HOOK]: === END OF SENTRY EVENT ==="); + + // if return null, the event won't be sent + // ref https://github.com/getsentry/sentry-javascript/issues/2039 + return null; + }, + dsn: sentryDataSourceName, + integrations: [ + new Dedupe(), + new Offline(), + ], + initialScope: { + tags: { + "process": mapProcessName(process.type), + }, + }, + environment: isProduction ? "production" : "development", + }); + }, + causesSideEffects: true, +}); + +export default initializeSentryReportingWithInjectable; diff --git a/src/common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable.ts b/src/common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable.ts index 77fae74011..3d9aebddf5 100644 --- a/src/common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable.ts @@ -16,8 +16,7 @@ const navigateToHelmReleasesInjectable = getInjectable({ const navigateToRoute = di.inject(navigateToRouteInjectionToken); const route = di.inject(helmReleasesRouteInjectable); - return (parameters) => - navigateToRoute(route, { parameters }); + return (parameters) => navigateToRoute(route, { parameters }); }, }); diff --git a/src/common/front-end-routing/routes/welcome/default-welcome-route.injectable.ts b/src/common/front-end-routing/routes/welcome/default-welcome-route.injectable.ts new file mode 100644 index 0000000000..d8db889033 --- /dev/null +++ b/src/common/front-end-routing/routes/welcome/default-welcome-route.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import welcomeRouteConfigInjectable from "./welcome-route-config.injectable"; +import { frontEndRouteInjectionToken } from "../../front-end-route-injection-token"; + +const defaultWelcomeRouteInjectable = getInjectable({ + id: "default-welcome-route", + + instantiate: (di) => { + const welcomeRoute = di.inject(welcomeRouteConfigInjectable); + + return { + path: "/welcome", + clusterFrame: false, + isEnabled: computed(() => welcomeRoute === "/welcome"), + }; + }, + + injectionToken: frontEndRouteInjectionToken, +}); + +export default defaultWelcomeRouteInjectable; diff --git a/src/common/front-end-routing/routes/welcome/welcome-route-config.injectable.ts b/src/common/front-end-routing/routes/welcome/welcome-route-config.injectable.ts new file mode 100644 index 0000000000..4e16df5bb4 --- /dev/null +++ b/src/common/front-end-routing/routes/welcome/welcome-route-config.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationInformationInjectable from "../../../vars/application-information.injectable"; + +const welcomeRouteConfigInjectable = getInjectable({ + id: "welcome-route-config", + + instantiate: (di) => di.inject(applicationInformationInjectable).config.welcomeRoute, +}); + +export default welcomeRouteConfigInjectable; diff --git a/src/common/front-end-routing/routes/welcome/welcome-route.injectable.ts b/src/common/front-end-routing/routes/welcome/welcome-route.injectable.ts index 75d722ab46..839a7446c1 100644 --- a/src/common/front-end-routing/routes/welcome/welcome-route.injectable.ts +++ b/src/common/front-end-routing/routes/welcome/welcome-route.injectable.ts @@ -4,16 +4,21 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; +import welcomeRouteConfigInjectable from "./welcome-route-config.injectable"; import { frontEndRouteInjectionToken } from "../../front-end-route-injection-token"; const welcomeRouteInjectable = getInjectable({ id: "welcome-route", - instantiate: () => ({ - path: "/welcome", - clusterFrame: false, - isEnabled: computed(() => true), - }), + instantiate: (di) => { + const welcomeRoute = di.inject(welcomeRouteConfigInjectable); + + return { + path: welcomeRoute, + clusterFrame: false, + isEnabled: computed(() => true), + }; + }, injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts b/src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts index 4b6776bdaa..acd1d4401d 100644 --- a/src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts +++ b/src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts @@ -5,7 +5,7 @@ import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting"; import { routeSpecificComponentInjectionToken } from "../../renderer/routes/route-specific-component-injection-token"; import { frontEndRouteInjectionToken } from "./front-end-route-injection-token"; -import { filter, map, matches } from "lodash/fp"; +import { filter, map } from "lodash/fp"; import clusterStoreInjectable from "../cluster-store/cluster-store.injectable"; import type { ClusterStore } from "../cluster-store/cluster-store"; import { pipeline } from "@ogre-tools/fp"; @@ -27,9 +27,11 @@ describe("verify-that-all-routes-have-component", () => { routes, map( - (route) => ({ - path: route.path, - routeComponent: routeComponents.find(matches({ route })), + (currentRoute) => ({ + path: currentRoute.path, + routeComponent: routeComponents.find(({ route }) => ( + route.path === currentRoute.path + && route.clusterFrame === currentRoute.clusterFrame)), }), ), diff --git a/src/common/fs/exec-file.injectable.ts b/src/common/fs/exec-file.injectable.ts index 510fa025e4..15d0ad48dc 100644 --- a/src/common/fs/exec-file.injectable.ts +++ b/src/common/fs/exec-file.injectable.ts @@ -3,20 +3,23 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; +import type { ExecFileOptions } from "child_process"; import { execFile } from "child_process"; import { promisify } from "util"; -export type ExecFile = (filePath: string, args: string[]) => Promise; +export type ExecFile = (filePath: string, args: string[], options: ExecFileOptions) => Promise; const execFileInjectable = getInjectable({ id: "exec-file", - instantiate: (): ExecFile => async (filePath, args) => { + instantiate: (): ExecFile => { const asyncExecFile = promisify(execFile); - const result = await asyncExecFile(filePath, args); + return async (filePath, args, options) => { + const result = await asyncExecFile(filePath, args, options); - return result.stdout; + return result.stdout; + }; }, causesSideEffects: true, diff --git a/src/common/get-configuration-file-model/get-configuration-file-model.injectable.ts b/src/common/get-configuration-file-model/get-configuration-file-model.injectable.ts index a1027c22c6..dc54e96de1 100644 --- a/src/common/get-configuration-file-model/get-configuration-file-model.injectable.ts +++ b/src/common/get-configuration-file-model/get-configuration-file-model.injectable.ts @@ -8,7 +8,7 @@ import type { BaseStoreParams } from "../base-store"; const getConfigurationFileModelInjectable = getInjectable({ id: "get-configuration-file-model", - instantiate: () => (content: BaseStoreParams) => new Config(content), + instantiate: () => (content: BaseStoreParams) => new Config(content), causesSideEffects: true, }); diff --git a/src/common/initializable-state/create.test.ts b/src/common/initializable-state/create.test.ts new file mode 100644 index 0000000000..38980b0a41 --- /dev/null +++ b/src/common/initializable-state/create.test.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { AsyncFnMock } from "@async-fn/jest"; +import asyncFn from "@async-fn/jest"; +import type { DiContainer, Injectable } from "@ogre-tools/injectable"; +import { runInAction } from "mobx"; +import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; +import type { InitializableState } from "./create"; +import { createInitializableState } from "./create"; + +describe("InitializableState tests", () => { + let di: DiContainer; + + beforeEach(() => { + di = getDiForUnitTesting({ doGeneralOverrides: true }); + }); + + describe("when created", () => { + let stateInjectable: Injectable, unknown, void>; + let initMock: AsyncFnMock<() => number>; + + beforeEach(() => { + initMock = asyncFn(); + stateInjectable = createInitializableState({ + id: "my-state", + init: initMock, + }); + + runInAction(() => { + di.register(stateInjectable); + }); + }); + + describe("when injected", () => { + let state: InitializableState; + + beforeEach(() => { + state = di.inject(stateInjectable); + }); + + it("when get is called, throw", () => { + expect(() => state.get()).toThrowError("InitializableState(my-state) has not been initialized yet"); + }); + + describe("when init is called", () => { + beforeEach(() => { + state.init(); + }); + + it("should call provided initialization function", () => { + expect(initMock).toBeCalled(); + }); + + it("when get is called, throw", () => { + expect(() => state.get()).toThrowError("InitializableState(my-state) has not finished initializing"); + }); + + describe("when initialization resolves", () => { + beforeEach(async () => { + await initMock.resolve(42); + }); + + it("when get is called, returns value", () => { + expect(state.get()).toBe(42); + }); + + it("when init is called again, throws", async () => { + await expect(() => state.init()).rejects.toThrow("Cannot initialize InitializableState(my-state) more than once"); + }); + }); + }); + }); + }); +}); diff --git a/src/common/initializable-state/create.ts b/src/common/initializable-state/create.ts new file mode 100644 index 0000000000..829de57d94 --- /dev/null +++ b/src/common/initializable-state/create.ts @@ -0,0 +1,62 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { DiContainerForInjection, Injectable, InjectionToken } from "@ogre-tools/injectable"; +import { getInjectable } from "@ogre-tools/injectable"; + +export interface CreateInitializableStateArgs { + id: string; + init: (di: DiContainerForInjection) => Promise | T; + injectionToken?: InjectionToken, void>; +} + +export interface InitializableState { + get: () => T; + init: () => Promise; +} + +type InitializableStateValue = + | { set: false } + | { set: true; value: T } ; + +export function createInitializableState(args: CreateInitializableStateArgs): Injectable, unknown, void> { + const { id, init, injectionToken } = args; + + return getInjectable({ + id, + instantiate: (di) => { + let box: InitializableStateValue = { + set: false, + }; + let initCalled = false; + + return { + init: async () => { + if (initCalled) { + throw new Error(`Cannot initialize InitializableState(${id}) more than once`); + } + + initCalled = true; + box = { + set: true, + value: await init(di), + }; + }, + get: () => { + if (!initCalled) { + throw new Error(`InitializableState(${id}) has not been initialized yet`); + } + + if (box.set === false) { + throw new Error(`InitializableState(${id}) has not finished initializing`); + } + + return box.value; + }, + }; + }, + injectionToken, + }); +} diff --git a/src/common/k8s-api/__tests__/pods.test.ts b/src/common/k8s-api/__tests__/pods.test.ts index 826c2b4617..3ed979face 100644 --- a/src/common/k8s-api/__tests__/pods.test.ts +++ b/src/common/k8s-api/__tests__/pods.test.ts @@ -4,7 +4,7 @@ */ import assert from "assert"; -import type { PodContainer, PodContainerStatus } from "../endpoints"; +import type { Container, PodContainerStatus } from "../endpoints"; import { Pod } from "../endpoints"; interface GetDummyPodOptions { @@ -22,8 +22,8 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod { initRunning = 0, } = rawOpts; - const containers: PodContainer[] = []; - const initContainers: PodContainer[] = []; + const containers: Container[] = []; + const initContainers: Container[] = []; const containerStatuses: PodContainerStatus[] = []; const initContainerStatuses: PodContainerStatus[] = []; const pod = new Pod({ @@ -58,7 +58,7 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod { containers.push({ image: "dummy", - imagePullPolicy: "dummy", + imagePullPolicy: "Always", name, }); containerStatuses.push({ @@ -80,7 +80,7 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod { containers.push({ image: "dummy", - imagePullPolicy: "dummy", + imagePullPolicy: "Always", name, }); containerStatuses.push({ @@ -105,7 +105,7 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod { initContainers.push({ image: "dummy", - imagePullPolicy: "dummy", + imagePullPolicy: "Always", name, }); initContainerStatuses.push({ @@ -127,7 +127,7 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod { initContainers.push({ image: "dummy", - imagePullPolicy: "dummy", + imagePullPolicy: "Always", name, }); initContainerStatuses.push({ @@ -169,7 +169,7 @@ describe("Pods", () => { function getNamedContainer(name: string) { return { image: "dummy", - imagePullPolicy: "dummy", + imagePullPolicy: "Always", name, }; } diff --git a/src/common/k8s-api/endpoints/index.ts b/src/common/k8s-api/endpoints/index.ts index 19a7cac378..0e2e677521 100644 --- a/src/common/k8s-api/endpoints/index.ts +++ b/src/common/k8s-api/endpoints/index.ts @@ -41,3 +41,4 @@ export * from "./service-account.api"; export * from "./stateful-set.api"; export * from "./storage-class.api"; export * from "./legacy-globals"; +export * from "./types"; diff --git a/src/common/k8s-api/endpoints/job.api.ts b/src/common/k8s-api/endpoints/job.api.ts index adc7705af8..84e166ecc4 100644 --- a/src/common/k8s-api/endpoints/job.api.ts +++ b/src/common/k8s-api/endpoints/job.api.ts @@ -6,7 +6,8 @@ import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; import { metricsApi } from "./metrics.api"; -import type { PodContainer, PodMetricData, PodSpec } from "./pod.api"; +import type { PodMetricData, PodSpec } from "./pod.api"; +import type { Container } from "./types/container"; import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; import { KubeObject } from "../kube-object"; @@ -23,7 +24,7 @@ export interface JobSpec { }; spec: PodSpec; }; - containers?: PodContainer[]; + containers?: Container[]; restartPolicy?: string; terminationGracePeriodSeconds?: number; dnsPolicy?: string; diff --git a/src/common/k8s-api/endpoints/metrics.api.ts b/src/common/k8s-api/endpoints/metrics.api.ts index 7b0028b061..b5469e8a24 100644 --- a/src/common/k8s-api/endpoints/metrics.api.ts +++ b/src/common/k8s-api/endpoints/metrics.api.ts @@ -7,7 +7,6 @@ import moment from "moment"; import { apiBase } from "../index"; -import type { IMetricsQuery } from "../../../main/routes/metrics/metrics-query"; export interface MetricData { status: string; @@ -55,28 +54,33 @@ export interface IResourceMetrics { networkTransmit: T; } +async function getMetrics(query: string, reqParams?: IMetricsReqParams): Promise; +async function getMetrics(query: string[], reqParams?: IMetricsReqParams): Promise; +async function getMetrics(query: Record>>, reqParams?: IMetricsReqParams): Promise>; + +async function getMetrics(query: string | string[] | Partial>>>, reqParams: IMetricsReqParams = {}): Promise>> { + const { range = 3600, step = 60, namespace } = reqParams; + let { start, end } = reqParams; + + if (!start && !end) { + const timeNow = Date.now() / 1000; + const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes + + start = now - range; + end = now; + } + + return apiBase.post("/metrics", { + data: query, + query: { + start, end, step, + "kubernetes_namespace": namespace, + }, + }); +} + export const metricsApi = { - async getMetrics(query: T, reqParams: IMetricsReqParams = {}): Promise { - const { range = 3600, step = 60, namespace } = reqParams; - let { start, end } = reqParams; - - if (!start && !end) { - const timeNow = Date.now() / 1000; - const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes - - start = now - range; - end = now; - } - - return apiBase.post("/metrics", { - data: query, - query: { - start, end, step, - "kubernetes_namespace": namespace, - }, - }); - }, - + getMetrics, async getMetricProviders(): Promise { return apiBase.get("/metrics/providers"); }, diff --git a/src/common/k8s-api/endpoints/pod.api.ts b/src/common/k8s-api/endpoints/pod.api.ts index 4ac49489c9..5822c0c5ef 100644 --- a/src/common/k8s-api/endpoints/pod.api.ts +++ b/src/common/k8s-api/endpoints/pod.api.ts @@ -8,11 +8,15 @@ import { metricsApi } from "./metrics.api"; import type { DerivedKubeApiOptions, IgnoredKubeApiOptions, ResourceDescriptor } from "../kube-api"; import { KubeApi } from "../kube-api"; import type { RequireExactlyOne } from "type-fest"; -import type { KubeObjectMetadata, LocalObjectReference, Affinity, Toleration, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; +import type { KubeObjectMetadata, LocalObjectReference, Affinity, Toleration, NamespaceScopedMetadata } from "../kube-object"; import type { SecretReference } from "./secret.api"; import type { PersistentVolumeClaimSpec } from "./persistent-volume-claim.api"; import { KubeObject } from "../kube-object"; import { isDefined } from "../../utils"; +import type { PodSecurityContext } from "./types/pod-security-context"; +import type { Probe } from "./types/probe"; +import type { Container } from "./types/container"; +import type { ObjectFieldSelector, ResourceFieldSelector } from "./types"; export class PodApi extends KubeApi { constructor(opts: DerivedKubeApiOptions & IgnoredKubeApiOptions = {}) { @@ -83,93 +87,6 @@ export enum PodStatusPhase { EVICTED = "Evicted", } -export interface ContainerPort { - containerPort: number; - hostIP?: string; - hostPort?: number; - name?: string; - protocol?: "UDP" | "TCP" | "SCTP"; -} - -export interface VolumeMount { - name: string; - readOnly?: boolean; - mountPath: string; - mountPropagation?: string; - subPath?: string; - subPathExpr?: string; -} - -export interface PodContainer extends Partial> { - name: string; - image: string; - command?: string[]; - args?: string[]; - ports?: ContainerPort[]; - resources?: { - limits?: { - cpu: string; - memory: string; - }; - requests?: { - cpu: string; - memory: string; - }; - }; - terminationMessagePath?: string; - terminationMessagePolicy?: string; - env?: { - name: string; - value?: string; - valueFrom?: { - fieldRef?: { - apiVersion: string; - fieldPath: string; - }; - secretKeyRef?: { - key: string; - name: string; - }; - configMapKeyRef?: { - key: string; - name: string; - }; - }; - }[]; - envFrom?: { - configMapRef?: LocalObjectReference; - secretRef?: LocalObjectReference; - }[]; - volumeMounts?: VolumeMount[]; - imagePullPolicy?: string; -} - -export type PodContainerProbe = "livenessProbe" | "readinessProbe" | "startupProbe"; - -interface IContainerProbe { - httpGet?: { - path?: string; - - /** - * either a port number or an IANA_SVC_NAME string referring to a port defined in the container - */ - port: number | string; - scheme: string; - host?: string; - }; - exec?: { - command: string[]; - }; - tcpSocket?: { - port: number; - }; - initialDelaySeconds?: number; - timeoutSeconds?: number; - periodSeconds?: number; - successThreshold?: number; - failureThreshold?: number; -} - export interface ContainerStateRunning { startedAt: string; } @@ -459,17 +376,6 @@ export interface ConfigMapProjection { optional?: boolean; } -export interface ObjectFieldSelector { - fieldPath: string; - apiVersion?: string; -} - -export interface ResourceFieldSelector { - resource: string; - containerName?: string; - divisor?: string; -} - export interface DownwardAPIVolumeFile { path: string; fieldRef?: ObjectFieldSelector; @@ -674,43 +580,11 @@ export interface HostAlias { hostnames: string[]; } -export interface SELinuxOptions { - level?: string; - role?: string; - type?: string; - user?: string; -} - -export interface SeccompProfile { - localhostProfile?: string; - type: string; -} - export interface Sysctl { name: string; value: string; } -export interface WindowsSecurityContextOptions { - labelSelector?: LabelSelector; - maxSkew: number; - topologyKey: string; - whenUnsatisfiable: string; -} - -export interface PodSecurityContext { - fsGroup?: number; - fsGroupChangePolicy?: string; - runAsGroup?: number; - runAsNonRoot?: boolean; - runAsUser?: number; - seLinuxOptions?: SELinuxOptions; - seccompProfile?: SeccompProfile; - supplementalGroups?: number[]; - sysctls?: Sysctl; - windowsOptions?: WindowsSecurityContextOptions; -} - export interface TopologySpreadConstraint { } @@ -719,7 +593,7 @@ export interface PodSpec { activeDeadlineSeconds?: number; affinity?: Affinity; automountServiceAccountToken?: boolean; - containers?: PodContainer[]; + containers?: Container[]; dnsPolicy?: string; enableServiceLinks?: boolean; ephemeralContainers?: unknown[]; @@ -729,7 +603,7 @@ export interface PodSpec { hostNetwork?: boolean; hostPID?: boolean; imagePullSecrets?: LocalObjectReference[]; - initContainers?: PodContainer[]; + initContainers?: Container[]; nodeName?: string; nodeSelector?: Partial>; overhead?: Partial>; @@ -931,44 +805,45 @@ export class Pod extends KubeObject< return this.getStatusPhase() !== "Running"; } - getLivenessProbe(container: PodContainer) { - return this.getProbe(container, "livenessProbe"); + getLivenessProbe(container: Container) { + return this.getProbe(container, container.livenessProbe); } - getReadinessProbe(container: PodContainer) { - return this.getProbe(container, "readinessProbe"); + getReadinessProbe(container: Container) { + return this.getProbe(container, container.readinessProbe); } - getStartupProbe(container: PodContainer) { - return this.getProbe(container, "startupProbe"); + getStartupProbe(container: Container) { + return this.getProbe(container, container.startupProbe); } - private getProbe(container: PodContainer, field: PodContainerProbe): string[] { - const probe: string[] = []; - const probeData = container[field]; + private getProbe(container: Container, probe: Probe | undefined): string[] { + const probeItems: string[] = []; - if (!probeData) { - return probe; + if (!probe) { + return probeItems; } const { - httpGet, exec, tcpSocket, + httpGet, + exec, + tcpSocket, initialDelaySeconds = 0, timeoutSeconds = 0, periodSeconds = 0, successThreshold = 0, failureThreshold = 0, - } = probeData; + } = probe; // HTTP Request if (httpGet) { - const { path = "", port, host = "", scheme } = httpGet; + const { path = "", port, host = "", scheme = "HTTP" } = httpGet; const resolvedPort = typeof port === "number" ? port // Try and find the port number associated witht the name or fallback to the name itself : container.ports?.find(containerPort => containerPort.name === port)?.containerPort || port; - probe.push( + probeItems.push( "http-get", `${scheme.toLowerCase()}://${host}:${resolvedPort}${path}`, ); @@ -976,15 +851,15 @@ export class Pod extends KubeObject< // Command if (exec?.command) { - probe.push(`exec [${exec.command.join(" ")}]`); + probeItems.push(`exec [${exec.command.join(" ")}]`); } // TCP Probe if (tcpSocket?.port) { - probe.push(`tcp-socket :${tcpSocket.port}`); + probeItems.push(`tcp-socket :${tcpSocket.port}`); } - probe.push( + probeItems.push( `delay=${initialDelaySeconds}s`, `timeout=${timeoutSeconds}s`, `period=${periodSeconds}s`, @@ -992,7 +867,7 @@ export class Pod extends KubeObject< `#failure=${failureThreshold}`, ); - return probe; + return probeItems; } getNodeName(): string | undefined { diff --git a/src/common/k8s-api/endpoints/types/capabilities.ts b/src/common/k8s-api/endpoints/types/capabilities.ts new file mode 100644 index 0000000000..baea13c688 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/capabilities.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +/** + * Adds and removes POSIX capabilities from running containers. + */ +export interface Capabilities { + /** + * Added capabilities + */ + add?: string[]; + + /** + * Removed capabilities + */ + drop?: string[]; +} diff --git a/src/common/k8s-api/endpoints/types/container-port.ts b/src/common/k8s-api/endpoints/types/container-port.ts new file mode 100644 index 0000000000..9ccb635e98 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/container-port.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +export interface ContainerPort { + containerPort: number; + hostIP?: string; + hostPort?: number; + name?: string; + protocol?: "UDP" | "TCP" | "SCTP"; +} diff --git a/src/common/k8s-api/endpoints/types/container.ts b/src/common/k8s-api/endpoints/types/container.ts new file mode 100644 index 0000000000..bccf45eb64 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/container.ts @@ -0,0 +1,176 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { Lifecycle } from "./lifecycle"; +import type { ResourceRequirements } from "./resource-requirements"; +import type { SecurityContext } from "./security-context"; +import type { Probe } from "./probe"; +import type { VolumeDevice } from "./volume-device"; +import type { VolumeMount } from "./volume-mount"; +import type { ContainerPort } from "./container-port"; +import type { EnvFromSource } from "./env-from-source"; +import type { EnvVar } from "./env-var"; + +/** + * A single application container that you want to run within a pod. + */ +export interface Container { + /** + * Arguments to the entrypoint. The docker image's CMD is used if this is not provided. Variable + * references `$(VAR_NAME)` are expanded using the container's environment. + * + * If a variable cannot be resolved, the reference in the input string will be unchanged. + * Double `$$` are reduced to a single `$`, which allows for escaping the `$(VAR_NAME)` syntax: + * i.e. `"$$(VAR_NAME)"` will produce the string literal `"$(VAR_NAME)`". + * + * Escaped references will never be expanded, regardless of whether the variable exists or not. + * Cannot be updated. + * + * More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + */ + args?: string[]; + + /** + * Entrypoint array. Not executed within a shell. The docker image's ENTRYPOINT is used if this + * is not provided. Variable references `$(VAR_NAME)` are expanded using the container's + * environment. + * + * If a variable cannot be resolved, the reference in the input string will be unchanged. + * Double `$$` are reduced to a single `$`, which allows for escaping the `$(VAR_NAME)` syntax: + * i.e. `"$$(VAR_NAME)"` will produce the string literal `"$(VAR_NAME)`". + * + * Escaped references will never be expanded, regardless of whether the variable exists or not. + * Cannot be updated. + * + * More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + */ + command?: string[]; + + /** + * List of environment variables to set in the container. Cannot be updated. + */ + env?: EnvVar[]; + + /** + * List of sources to populate environment variables in the container. The keys defined within a + * source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the + * container is starting. + * + * When a key exists in multiple sources, the value associated with the last source will take + * precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be + * updated. + */ + envFrom?: EnvFromSource[]; + + /** + * Docker image name. + * + * More info: https://kubernetes.io/docs/concepts/containers/images + */ + image?: string; + + /** + * Image pull policy. Defaults to `"Always"` if :latest tag is specified, or `"IfNotPresent"` + * otherwise. Cannot be updated. + * + * More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + */ + imagePullPolicy?: "Always" | "Never" | "IfNotPresent"; + + lifecycle?: Lifecycle; + livenessProbe?: Probe; + + /** + * Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique + * name. Cannot be updated. + */ + name: string; + + /** + * List of ports to expose from the container. Exposing a port here gives the system additional + * information about the network connections a container uses, but is primarily informational. + * Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is + * listening on the default `"0.0.0.0"` address inside a container will be accessible from the + * network. Cannot be updated. + */ + ports?: ContainerPort[]; + + readinessProbe?: Probe; + resources?: ResourceRequirements; + securityContext?: SecurityContext; + startupProbe?: Probe; + + /** + * Whether this container should allocate a buffer for stdin in the container runtime. If this is + * not set, reads from stdin in the container will always result in EOF. + * + * @default false + */ + stdin?: boolean; + + /** + * Whether the container runtime should close the stdin channel after it has been opened by a + * single attach. When stdin is true the stdin stream will remain open across multiple attach + * sessions. + * + * If stdinOnce is set to true, stdin is opened on container start, is empty until the first + * client attaches to stdin, and then remains open and accepts data until the client disconnects, + * at which time stdin is closed and remains closed until the container is restarted. + * + * If this flag is false, a container processes that reads from stdin will never receive an EOF. + * + * @default false + */ + stdinOnce?: boolean; + + /** + * Path at which the file to which the container's termination message will be written + * is mounted into the container's filesystem. Message written is intended to be brief final + * status, such as an assertion failure message. + * + * Will be truncated by the node if greater than 4096 bytes. + * The total message length across all containers will be limited to 12kb. Cannot be updated. + * + * @default "/dev/termination-log" + */ + terminationMessagePath?: string; + + /** + * Indicate how the termination message should be populated. + * + * - `File`: will use the contents of {@link terminationMessagePath} to populate the container + * status message on both success and failure. + * + * - `FallbackToLogsOnError`: will use the last chunk of container log output if the + * termination message file is empty and the container exited with an error. + * + * The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Cannot be updated. + * + * @default "File" + */ + terminationMessagePolicy?: "File" | "FallbackToLogsOnError"; + + /** + * Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. + * + * @default false + */ + tty?: boolean; + + /** + * volumeDevices is the list of block devices to be used by the container. + */ + volumeDevices?: VolumeDevice[]; + + /** + * Pod volumes to mount into the container's filesystem. Cannot be updated. + */ + volumeMounts?: VolumeMount[]; + + /** + * Container's working directory. If not specified, the container runtime's default will be used, + * which might be configured in the container image. Cannot be updated. + */ + workingDir?: string; +} diff --git a/src/common/k8s-api/endpoints/types/env-from-source.ts b/src/common/k8s-api/endpoints/types/env-from-source.ts new file mode 100644 index 0000000000..fa87b2fac8 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/env-from-source.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { EnvSource } from "./env-source"; + +export interface EnvFromSource { + configMapRef?: EnvSource; + /** + * An identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + */ + prefix?: string; + secretRef?: EnvSource; +} diff --git a/src/common/k8s-api/endpoints/types/env-source.ts b/src/common/k8s-api/endpoints/types/env-source.ts new file mode 100644 index 0000000000..2a16ee2ada --- /dev/null +++ b/src/common/k8s-api/endpoints/types/env-source.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { LocalObjectReference } from "../../kube-object"; + +export interface EnvSource extends LocalObjectReference { + /** + * Whether the object must be defined + */ + optional?: boolean; +} diff --git a/src/common/k8s-api/endpoints/types/env-var-key-selector.ts b/src/common/k8s-api/endpoints/types/env-var-key-selector.ts new file mode 100644 index 0000000000..3aca6d79bd --- /dev/null +++ b/src/common/k8s-api/endpoints/types/env-var-key-selector.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +export interface EnvVarKeySelector { + key: string; + name?: string; + optional?: boolean; +} diff --git a/src/common/k8s-api/endpoints/types/env-var-source.ts b/src/common/k8s-api/endpoints/types/env-var-source.ts new file mode 100644 index 0000000000..c47c839d18 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/env-var-source.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { EnvVarKeySelector } from "./env-var-key-selector"; +import type { ObjectFieldSelector } from "./object-field-selector"; +import type { ResourceFieldSelector } from "./resource-field-selector"; + +export interface EnvVarSource { + configMapKeyRef?: EnvVarKeySelector; + fieldRef?: ObjectFieldSelector; + resourceFieldRef?: ResourceFieldSelector; + secretKeyRef?: EnvVarKeySelector; +} diff --git a/src/common/k8s-api/endpoints/types/env-var.ts b/src/common/k8s-api/endpoints/types/env-var.ts new file mode 100644 index 0000000000..4a60b69129 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/env-var.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { EnvVarSource } from "./env-var-source"; + +export interface EnvVar { + name: string; + value?: string; + valueFrom?: EnvVarSource; +} diff --git a/src/common/k8s-api/endpoints/types/exec-action.ts b/src/common/k8s-api/endpoints/types/exec-action.ts new file mode 100644 index 0000000000..abfd1947cb --- /dev/null +++ b/src/common/k8s-api/endpoints/types/exec-action.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +/** + * ExecAction describes a "run in container" action. + */ +export interface ExecAction { + /** + * Command is the command line to execute inside the container, the working directory for the + * command is root ('\\') in the container's filesystem. The command is simply exec'd, it is not + * run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, + * you need to explicitly call out to that shell. + * + * Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + */ + command?: string[]; +} diff --git a/src/common/k8s-api/endpoints/types/handler.ts b/src/common/k8s-api/endpoints/types/handler.ts new file mode 100644 index 0000000000..d30e6cd181 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/handler.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { ExecAction } from "./exec-action"; +import type { HttpGetAction } from "./http-get-action"; +import type { TcpSocketAction } from "./tcp-socket-action"; + +/** + * Handler defines a specific action that should be taken. + */ +export interface Handler { + exec?: ExecAction; + httpGet?: HttpGetAction; + tcpSocket?: TcpSocketAction; +} diff --git a/src/common/k8s-api/endpoints/types/http-get-action.ts b/src/common/k8s-api/endpoints/types/http-get-action.ts new file mode 100644 index 0000000000..1077ea13e1 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/http-get-action.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { HttpHeader } from "./http-header"; + +/** + * An action based on HTTP Get requests. + */ +export interface HttpGetAction { + /** + * Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead. + */ + host?: string; + + /** + * Custom headers to set in the request. HTTP allows repeated headers. + */ + httpHeaders?: HttpHeader[]; + + /** + * Path to access on the HTTP server. + */ + path?: string; + + /** + * The PORT to request from. + */ + port: string | number; + + /** + * Scheme to use for connecting to the host. + * + * @default "HTTP" + */ + scheme?: string; +} diff --git a/src/common/k8s-api/endpoints/types/http-header.ts b/src/common/k8s-api/endpoints/types/http-header.ts new file mode 100644 index 0000000000..d70b42afdb --- /dev/null +++ b/src/common/k8s-api/endpoints/types/http-header.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +/** + * A custom header to be used in HTTP probes and get actions + */ +export interface HttpHeader { + /** + * Field name + */ + name: string; + + /** + * The value of the field + */ + value: string; +} diff --git a/src/common/k8s-api/endpoints/types/index.ts b/src/common/k8s-api/endpoints/types/index.ts new file mode 100644 index 0000000000..6fb52e7403 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/index.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +export * from "./aggregation-rule"; +export * from "./capabilities"; +export * from "./container"; +export * from "./container-port"; +export * from "./env-from-source"; +export * from "./env-source"; +export * from "./env-var-key-selector"; +export * from "./env-var-source"; +export * from "./env-var"; +export * from "./exec-action"; +export * from "./handler"; +export * from "./http-get-action"; +export * from "./http-header"; +export * from "./job-template-spec"; +export * from "./lifecycle"; +export * from "./object-field-selector"; +export * from "./persistent-volume-claim-template-spec"; +export * from "./pod-security-context"; +export * from "./pod-template-spec"; +export * from "./policy-rule"; +export * from "./probe"; +export * from "./resource-field-selector"; +export * from "./resource-requirements"; +export * from "./role-ref"; +export * from "./se-linux-options"; +export * from "./seccomp-profile"; +export * from "./subject"; +export * from "./tcp-socket-action"; +export * from "./volume-device"; +export * from "./volume-mount"; +export * from "./windows-security-context-options"; diff --git a/src/common/k8s-api/endpoints/types/lifecycle.ts b/src/common/k8s-api/endpoints/types/lifecycle.ts new file mode 100644 index 0000000000..2e459958c4 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/lifecycle.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { Handler } from "./handler"; + +/** + * Lifecycle describes actions that the management system should take in response to container + * lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container + * blocks until the action is complete, unless the container process fails, in which case the + * handler is aborted. + */ +export interface Lifecycle { + postStart?: Handler; + preStop?: Handler; +} diff --git a/src/main/routes/metrics/metrics-query.ts b/src/common/k8s-api/endpoints/types/object-field-selector.ts similarity index 63% rename from src/main/routes/metrics/metrics-query.ts rename to src/common/k8s-api/endpoints/types/object-field-selector.ts index d6d9532b3b..8593456ff1 100644 --- a/src/main/routes/metrics/metrics-query.ts +++ b/src/common/k8s-api/endpoints/types/object-field-selector.ts @@ -2,6 +2,8 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -export type IMetricsQuery = string | string[] | { - [metricName: string]: string; -}; + +export interface ObjectFieldSelector { + apiVersion?: string; + fieldPath: string; +} diff --git a/src/common/k8s-api/endpoints/types/pod-security-context.ts b/src/common/k8s-api/endpoints/types/pod-security-context.ts new file mode 100644 index 0000000000..983ae86262 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/pod-security-context.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { SeLinuxOptions } from "./se-linux-options"; +import type { SeccompProfile } from "./seccomp-profile"; +import type { WindowsSecurityContextOptions } from "./windows-security-context-options"; +import type { Sysctl } from "../pod.api"; + + +export interface PodSecurityContext { + fsGroup?: number; + fsGroupChangePolicy?: string; + runAsGroup?: number; + runAsNonRoot?: boolean; + runAsUser?: number; + seLinuxOptions?: SeLinuxOptions; + seccompProfile?: SeccompProfile; + supplementalGroups?: number[]; + sysctls?: Sysctl; + windowsOptions?: WindowsSecurityContextOptions; +} diff --git a/src/common/k8s-api/endpoints/types/probe.ts b/src/common/k8s-api/endpoints/types/probe.ts new file mode 100644 index 0000000000..4251be5e2c --- /dev/null +++ b/src/common/k8s-api/endpoints/types/probe.ts @@ -0,0 +1,80 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { ExecAction } from "./exec-action"; +import type { HttpGetAction } from "./http-get-action"; +import type { TcpSocketAction } from "./tcp-socket-action"; + +/** + * Describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic. + */ +export interface Probe { + exec?: ExecAction; + + /** + * Minimum consecutive failures for the probe to be considered failed after having succeeded. + * + * @default 3 + * @minimum 1 + */ + failureThreshold?: number; + + httpGet?: HttpGetAction; + + /** + * Duration after the container has started before liveness probes are initiated. + * + * More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + */ + initialDelaySeconds?: number; + + /** + * How often to perform the probe. + * + * @default 10 + * @minimum 1 + */ + periodSeconds?: number; + + /** + * Minimum consecutive successes for the probe to be considered successful after having failed. + * + * Must be 1 for liveness and startup. + * + * @default 1 + * @minimum 1 + */ + successThreshold?: number; + + tcpSocket?: TcpSocketAction; + + /** + * Duration the pod needs to terminate gracefully upon probe failure. + * + * The grace period is the duration in seconds after the processes running in the pod are sent a + * termination signal and the time when the processes are forcibly halted with a kill signal. + * + * Set this value longer than the expected cleanup time for your process. + * + * If this value is not set, the pod's terminationGracePeriodSeconds will be used. Otherwise, + * this value overrides the value provided by the pod spec. Value must be non-negative integer. + * The value zero indicates stop immediately via the kill signal (no opportunity to shut down). + * + * This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + * + * @minimum 1 + */ + terminationGracePeriodSeconds?: number; + + /** + * Duration after which the probe times out. + * + * More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + * + * @default 1 + * @minimum 1 + */ + timeoutSeconds?: number; +} diff --git a/src/common/k8s-api/endpoints/types/resource-field-selector.ts b/src/common/k8s-api/endpoints/types/resource-field-selector.ts new file mode 100644 index 0000000000..9727506183 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/resource-field-selector.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +export interface ResourceFieldSelector { + containerName?: string; + divisor?: string; + resource: string; +} diff --git a/src/common/k8s-api/endpoints/types/se-linux-options.ts b/src/common/k8s-api/endpoints/types/se-linux-options.ts new file mode 100644 index 0000000000..9e3c629192 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/se-linux-options.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +/** + * SELinuxOptions are the labels to be applied to the container + */ +export interface SeLinuxOptions { + /** + * The SELinux `level` label that applies to the container. + */ + level?: string; + + /** + * The SELinux `role` label that applies to the container. + */ + role?: string; + + /** + * The SELinux `type` label that applies to the container. + */ + type?: string; + + /** + * The SELinux `user` label that applies to the container. + */ + user?: string; +} diff --git a/src/common/k8s-api/endpoints/types/seccomp-profile.ts b/src/common/k8s-api/endpoints/types/seccomp-profile.ts new file mode 100644 index 0000000000..20eddbf94d --- /dev/null +++ b/src/common/k8s-api/endpoints/types/seccomp-profile.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +/** + * Defines a pod's or a container's seccomp profile settings. Only one profile source may be set. + */ +export interface SeccompProfile { + /** + * Indicates a profile defined in a file on the node should be used. The profile must be + * preconfigured on the node to work. Must be a descending path, relative to the kubelet's + * configured seccomp profile location. Must only be set if type is "Localhost". + */ + localhostProfile?: string; + + /** + * Indicates which kind of seccomp profile will be applied. + * + * Options: + * + * | Value | Description | + * |--|--| + * | `Localhost` | A profile defined in a file on the node should be used. | + * | `RuntimeDefault` | The container runtime default profile should be used. | + * | `Unconfined` | No profile should be applied. | + */ + type: "Localhost" | "RuntimeDefault" | "Unconfined"; +} diff --git a/src/common/k8s-api/endpoints/types/security-context.ts b/src/common/k8s-api/endpoints/types/security-context.ts new file mode 100644 index 0000000000..ce5cf60e40 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/security-context.ts @@ -0,0 +1,55 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { Capabilities } from "./capabilities"; +import type { SeLinuxOptions } from "./se-linux-options"; +import type { SeccompProfile } from "./seccomp-profile"; +import type { WindowsSecurityContextOptions } from "./windows-security-context-options"; + +/** + * SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence. + */ +export interface SecurityContext { + /** + * AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN + */ + allowPrivilegeEscalation?: boolean; + + capabilities?: Capabilities; + + /** + * Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. + */ + privileged?: boolean; + + /** + * procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. + */ + procMount?: string; + + /** + * Whether this container has a read-only root filesystem. Default is false. + */ + readOnlyRootFilesystem?: boolean; + + /** + * The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + */ + runAsGroup?: number; + + /** + * Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + */ + runAsNonRoot?: boolean; + + /** + * The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + */ + runAsUser?: number; + + seLinuxOptions?: SeLinuxOptions; + seccompProfile?: SeccompProfile; + windowsOptions?: WindowsSecurityContextOptions; +} diff --git a/src/common/k8s-api/endpoints/types/tcp-socket-action.ts b/src/common/k8s-api/endpoints/types/tcp-socket-action.ts new file mode 100644 index 0000000000..02f1805823 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/tcp-socket-action.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +/** + * An action based on opening a socket + */ +export interface TcpSocketAction { + /** + * Host name to connect to, defaults to the pod IP. + */ + host?: string; + + /** + * Port to connect to + */ + port: number | string; +} diff --git a/src/common/k8s-api/endpoints/types/volume-device.ts b/src/common/k8s-api/endpoints/types/volume-device.ts new file mode 100644 index 0000000000..1dacaafe21 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/volume-device.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +/** + * A mapping of a raw block device within a container. + */ +export interface VolumeDevice { + /** + * The path inside of the container that the device will be mapped to. + */ + devicePath: string; + + /** + * Must match the name of a persistentVolumeClaim in the pod + */ + name: string; +} diff --git a/src/common/k8s-api/endpoints/types/volume-mount.ts b/src/common/k8s-api/endpoints/types/volume-mount.ts new file mode 100644 index 0000000000..0066d8896d --- /dev/null +++ b/src/common/k8s-api/endpoints/types/volume-mount.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +export interface VolumeMount { + name: string; + readOnly?: boolean; + mountPath: string; + mountPropagation?: string; + subPath?: string; + subPathExpr?: string; +} diff --git a/src/common/k8s-api/endpoints/types/windows-security-context-options.ts b/src/common/k8s-api/endpoints/types/windows-security-context-options.ts new file mode 100644 index 0000000000..07daf48731 --- /dev/null +++ b/src/common/k8s-api/endpoints/types/windows-security-context-options.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +/** + * Windows-specific options and credentials. + */ +export interface WindowsSecurityContextOptions { + /** + * The location of the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + * inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. + */ + gmsaCredentialSpec?: string; + + /** + * The name of the GMSA credential spec to use. + */ + gmsaCredentialSpecName?: string; + + /** + * Determines if a container should be run as a 'Host Process' container. + * + * This field is alpha-level and will only be honored by components that enable the + * WindowsHostProcessContainers feature flag. + * + * Setting this field without the feature flag will result in errors when validating the Pod. + * + * All of a Pod's containers must have the same effective HostProcess value (it is not allowed to + * have a mix of HostProcess containers and non-HostProcess containers). + * + * In addition, if HostProcess is true then HostNetwork must also be set to true. + */ + hostProcess?: boolean; + + /** + * The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + */ + runAsUserName?: string; +} diff --git a/src/common/k8s/resource-stack.ts b/src/common/k8s/resource-stack.ts index d289a375b3..96ecf2e39c 100644 --- a/src/common/k8s/resource-stack.ts +++ b/src/common/k8s/resource-stack.ts @@ -11,8 +11,9 @@ import logger from "../../main/logger"; import { app } from "electron"; import { ClusterStore } from "../cluster-store/cluster-store"; import yaml from "js-yaml"; -import { productName } from "../vars"; import { requestKubectlApplyAll, requestKubectlDeleteAll } from "../../renderer/ipc"; +import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import productNameInjectable from "../vars/product-name.injectable"; export class ResourceStack { constructor(protected cluster: KubernetesCluster, protected name: string) {} @@ -97,6 +98,8 @@ export class ResourceStack { protected async renderTemplates(folderPath: string, templateContext: any): Promise { const resources: string[] = []; + const di = getLegacyGlobalDiForExtensionApi(); + const productName = di.inject(productNameInjectable); logger.info(`[RESOURCE-STACK]: render templates from ${folderPath}`); const files = await fse.readdir(folderPath); diff --git a/src/common/protocol-handler/router.ts b/src/common/protocol-handler/router.ts index 1c0ae909eb..3204a5356c 100644 --- a/src/common/protocol-handler/router.ts +++ b/src/common/protocol-handler/router.ts @@ -113,7 +113,17 @@ export abstract class LensProtocolRouter { } // if no exact match pick the one that is the most specific - return matches.sort(([a], [b]) => compareMatches(a, b))[0] ?? null; + return matches.sort(([a], [b]) => { + if (a.path === "/") { + return 1; + } + + if (b.path === "/") { + return -1; + } + + return countBy(b.path)["/"] - countBy(a.path)["/"]; + })[0] ?? null; } /** @@ -265,21 +275,3 @@ export abstract class LensProtocolRouter { this.internalRoutes.delete(urlSchema); } } - -/** - * a comparison function for `array.sort(...)`. Sort order should be most path - * parts to least path parts. - * @param a the left side to compare - * @param b the right side to compare - */ -function compareMatches(a: match, b: match): number { - if (a.path === "/") { - return 1; - } - - if (b.path === "/") { - return -1; - } - - return countBy(b.path)["/"] - countBy(a.path)["/"]; -} diff --git a/src/common/sentry.ts b/src/common/sentry.ts deleted file mode 100644 index 282afa6f32..0000000000 --- a/src/common/sentry.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { Dedupe, Offline } from "@sentry/integrations"; -import { sentryDsn, isProduction } from "./vars"; -import { UserStore } from "./user-store"; -import { inspect } from "util"; -import type { BrowserOptions } from "@sentry/electron/renderer"; -import type { ElectronMainOptions } from "@sentry/electron/main"; - -/** - * "Translate" 'browser' to 'main' as Lens developer more familiar with the term 'main' - */ -function mapProcessName(processType: string) { - if (processType === "browser") { - return "main"; - } - - return processType; -} - -/** - * Initialize Sentry for the current process so to send errors for debugging. - */ -export function initializeSentryReporting(init: (opts: BrowserOptions | ElectronMainOptions) => void) { - const processName = mapProcessName(process.type); - - if (!sentryDsn) { - return; // do nothing if not configured to avoid uncaught error in dev mode - } - - init({ - beforeSend: (event) => { - // default to false, in case instance of UserStore is not created (yet) - const allowErrorReporting = UserStore.getInstance(false)?.allowErrorReporting ?? false; - - if (allowErrorReporting) { - return event; - } - - /** - * Directly write to stdout so that no other integrations capture this and create an infinite loop - */ - process.stdout.write(`🔒 [SENTRY-BEFORE-SEND-HOOK]: allowErrorReporting: ${allowErrorReporting}. Sentry event is caught but not sent to server.`); - process.stdout.write("🔒 [SENTRY-BEFORE-SEND-HOOK]: === START OF SENTRY EVENT ==="); - process.stdout.write(inspect(event, false, null, true)); - process.stdout.write("🔒 [SENTRY-BEFORE-SEND-HOOK]: === END OF SENTRY EVENT ==="); - - // if return null, the event won't be sent - // ref https://github.com/getsentry/sentry-javascript/issues/2039 - return null; - }, - dsn: sentryDsn, - integrations: [ - new Dedupe(), - new Offline(), - ], - initialScope: { - tags: { - "process": processName, - }, - }, - environment: isProduction ? "production" : "development", - }); -} diff --git a/src/common/user-store/file-name-migration.global-override-for-injectable.ts b/src/common/user-store/file-name-migration.global-override-for-injectable.ts new file mode 100644 index 0000000000..bb0ac054f3 --- /dev/null +++ b/src/common/user-store/file-name-migration.global-override-for-injectable.ts @@ -0,0 +1,9 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getGlobalOverride } from "../test-utils/get-global-override"; +import userStoreFileNameMigrationInjectable from "./file-name-migration.injectable"; + +export default getGlobalOverride(userStoreFileNameMigrationInjectable, () => async () => {}); diff --git a/src/common/user-store/file-name-migration.injectable.ts b/src/common/user-store/file-name-migration.injectable.ts index 8f2dcf1e23..caf94dc491 100644 --- a/src/common/user-store/file-name-migration.injectable.ts +++ b/src/common/user-store/file-name-migration.injectable.ts @@ -9,27 +9,32 @@ import directoryForUserDataInjectable from "../app-paths/directory-for-user-data import { isErrnoException } from "../utils"; import { getInjectable } from "@ogre-tools/injectable"; +export type UserStoreFileNameMigration = () => Promise; + const userStoreFileNameMigrationInjectable = getInjectable({ id: "user-store-file-name-migration", - instantiate: (di) => { + instantiate: (di): UserStoreFileNameMigration => { const userDataPath = di.inject(directoryForUserDataInjectable); const configJsonPath = path.join(userDataPath, "config.json"); const lensUserStoreJsonPath = path.join(userDataPath, "lens-user-store.json"); - try { - fse.moveSync(configJsonPath, lensUserStoreJsonPath); - } catch (error) { - if (error instanceof Error && error.message === "dest already exists.") { - fse.removeSync(configJsonPath); - } else if (isErrnoException(error) && error.code === "ENOENT" && error.path === configJsonPath) { - // (No such file or directory) - return; // file already moved - } else { - // pass other errors along - throw error; + return async () => { + try { + await fse.move(configJsonPath, lensUserStoreJsonPath); + } catch (error) { + if (error instanceof Error && error.message === "dest already exists.") { + await fse.remove(configJsonPath); + } else if (isErrnoException(error) && error.code === "ENOENT" && error.path === configJsonPath) { + // (No such file or directory) + return; // file already moved + } else { + // pass other errors along + throw error; + } } - } + }; }, + causesSideEffects: true, }); export default userStoreFileNameMigrationInjectable; diff --git a/src/common/user-store/user-store.injectable.ts b/src/common/user-store/user-store.injectable.ts index 3b4aba0b56..ffd2310886 100644 --- a/src/common/user-store/user-store.injectable.ts +++ b/src/common/user-store/user-store.injectable.ts @@ -3,8 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { ipcMain } from "electron"; -import userStoreFileNameMigrationInjectable from "./file-name-migration.injectable"; import { UserStore } from "./user-store"; import selectedUpdateChannelInjectable from "../application-update/selected-update-channel/selected-update-channel.injectable"; @@ -14,10 +12,6 @@ const userStoreInjectable = getInjectable({ instantiate: (di) => { UserStore.resetInstance(); - if (ipcMain) { - di.inject(userStoreFileNameMigrationInjectable); - } - return UserStore.createInstance({ selectedUpdateChannel: di.inject(selectedUpdateChannelInjectable), }); diff --git a/src/common/user-store/user-store.ts b/src/common/user-store/user-store.ts index b806732735..6c995996bf 100644 --- a/src/common/user-store/user-store.ts +++ b/src/common/user-store/user-store.ts @@ -4,19 +4,16 @@ */ import { app } from "electron"; -import semver from "semver"; import { action, computed, observable, reaction, makeObservable, isObservableArray, isObservableSet, isObservableMap } from "mobx"; import { BaseStore } from "../base-store"; import migrations from "../../migrations/user-store"; -import { getAppVersion } from "../utils/app-version"; import { kubeConfigDefaultPath } from "../kube-helpers"; -import { appEventBus } from "../app-event-bus/event-bus"; import { getOrInsertSet, toggle, toJS, object } from "../../renderer/utils"; import { DESCRIPTORS } from "./preferences-helpers"; import type { UserPreferencesModel, StoreType } from "./preferences-helpers"; import logger from "../../main/logger"; import type { SelectedUpdateChannel } from "../application-update/selected-update-channel/selected-update-channel.injectable"; -import type { UpdateChannelId } from "../application-update/update-channels"; +import type { ReleaseChannel } from "../application-update/update-channels"; export interface UserStoreModel { lastSeenAppVersion: string; @@ -24,7 +21,7 @@ export interface UserStoreModel { } interface Dependencies { - selectedUpdateChannel: SelectedUpdateChannel; + readonly selectedUpdateChannel: SelectedUpdateChannel; } export class UserStore extends BaseStore /* implements UserStoreFlatModel (when strict null is enabled) */ { @@ -37,7 +34,6 @@ export class UserStore extends BaseStore /* implements UserStore }); makeObservable(this); - this.load(); } @observable lastSeenAppVersion = "0.0.0"; @@ -98,10 +94,6 @@ export class UserStore extends BaseStore /* implements UserStore */ @observable syncKubeconfigEntries!: StoreType; - @computed get isNewVersion() { - return semver.gt(getAppVersion(), this.lastSeenAppVersion); - } - @computed get resolvedShell(): string | undefined { return this.shell || process.env.SHELL || process.env.PTYSHELL; } @@ -151,12 +143,6 @@ export class UserStore extends BaseStore /* implements UserStore this.colorTheme = DESCRIPTORS.colorTheme.fromStore(undefined); } - @action - saveLastSeenAppVersion() { - appEventBus.emit({ name: "app", action: "whats-new-seen" }); - this.lastSeenAppVersion = getAppVersion(); - } - @action protected fromStore({ lastSeenAppVersion, preferences }: Partial = {}) { logger.debug("UserStore.fromStore()", { lastSeenAppVersion, preferences }); @@ -180,7 +166,7 @@ export class UserStore extends BaseStore /* implements UserStore // TODO: Switch to action-based saving instead saving stores by reaction if (preferences?.updateChannel) { - this.dependencies.selectedUpdateChannel.setValue(preferences?.updateChannel as UpdateChannelId); + this.dependencies.selectedUpdateChannel.setValue(preferences?.updateChannel as ReleaseChannel); } } diff --git a/src/common/utils/app-version.ts b/src/common/utils/app-version.ts index 28882e092b..183cc3e6b2 100644 --- a/src/common/utils/app-version.ts +++ b/src/common/utils/app-version.ts @@ -4,15 +4,6 @@ */ import requestPromise from "request-promise-native"; -import packageInfo from "../../../package.json"; - -export function getAppVersion(): string { - return packageInfo.version; -} - -export function getBundledKubectlVersion(): string { - return packageInfo.config.bundledKubectlVersion; -} export async function getAppVersionFromProxyServer(proxyPort: number): Promise { const response = await requestPromise({ diff --git a/src/common/utils/channel/channel.test.ts b/src/common/utils/channel/channel.test.ts index a3d6a805e2..c5f52c0874 100644 --- a/src/common/utils/channel/channel.test.ts +++ b/src/common/utils/channel/channel.test.ts @@ -18,6 +18,7 @@ import { requestChannelListenerInjectionToken } from "./request-channel-listener import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; import { getPromiseStatus } from "../../test-utils/get-promise-status"; +import { runInAction } from "mobx"; type TestMessageChannel = MessageChannel; type TestRequestChannel = RequestChannel; @@ -47,12 +48,16 @@ describe("channel", () => { }); builder.beforeApplicationStart((mainDi) => { - mainDi.register(testMessageChannelInjectable); + runInAction(() => { + mainDi.register(testMessageChannelInjectable); + }); }); builder.beforeWindowStart((windowDi) => { - windowDi.register(testChannelListenerInTestWindowInjectable); - windowDi.register(testMessageChannelInjectable); + runInAction(() => { + windowDi.register(testChannelListenerInTestWindowInjectable); + windowDi.register(testMessageChannelInjectable); + }); }); mainDi = builder.mainDi; @@ -126,12 +131,16 @@ describe("channel", () => { }); applicationBuilder.beforeApplicationStart((mainDi) => { - mainDi.register(testChannelListenerInMainInjectable); - mainDi.register(testMessageChannelInjectable); + runInAction(() => { + mainDi.register(testChannelListenerInMainInjectable); + mainDi.register(testMessageChannelInjectable); + }); }); applicationBuilder.beforeWindowStart((windowDi) => { - windowDi.register(testMessageChannelInjectable); + runInAction(() => { + windowDi.register(testMessageChannelInjectable); + }); }); await applicationBuilder.render(); @@ -172,12 +181,16 @@ describe("channel", () => { }); applicationBuilder.beforeApplicationStart((mainDi) => { - mainDi.register(testChannelListenerInMainInjectable); - mainDi.register(testRequestChannelInjectable); + runInAction(() => { + mainDi.register(testChannelListenerInMainInjectable); + mainDi.register(testRequestChannelInjectable); + }); }); applicationBuilder.beforeWindowStart((windowDi) => { - windowDi.register(testRequestChannelInjectable); + runInAction(() => { + windowDi.register(testRequestChannelInjectable); + }); }); await applicationBuilder.render(); diff --git a/src/common/utils/readableStream.ts b/src/common/utils/readableStream.ts index 5fd49ba699..136e18c841 100644 --- a/src/common/utils/readableStream.ts +++ b/src/common/utils/readableStream.ts @@ -4,6 +4,7 @@ */ import { Readable } from "readable-stream"; +import type { ReadableStreamDefaultReadResult } from "stream/web"; import type { TypedArray } from "type-fest"; /** diff --git a/src/common/utils/singleton.ts b/src/common/utils/singleton.ts index d553de52db..d60fdb0490 100644 --- a/src/common/utils/singleton.ts +++ b/src/common/utils/singleton.ts @@ -27,7 +27,7 @@ export class Singleton { * @param args The constructor arguments for the child class * @returns An instance of the child class */ - static createInstance(this: StaticThis, ...args: R): T { + static createInstance(this: StaticThis, ...args: R): T { if (!Singleton.instances.has(this)) { if (Singleton.creating.length > 0) { throw new TypeError(`Cannot create a second singleton (${this.name}) while creating a first (${Singleton.creating})`); diff --git a/src/common/utils/sync-box/sync-box.test.ts b/src/common/utils/sync-box/sync-box.test.ts index cfb8954802..95f139a8cd 100644 --- a/src/common/utils/sync-box/sync-box.test.ts +++ b/src/common/utils/sync-box/sync-box.test.ts @@ -18,11 +18,15 @@ describe("sync-box", () => { applicationBuilder = getApplicationBuilder(); applicationBuilder.beforeApplicationStart(mainDi => { - mainDi.register(someInjectable); + runInAction(() => { + mainDi.register(someInjectable); + }); }); applicationBuilder.beforeWindowStart((windowDi) => { - windowDi.register(someInjectable); + runInAction(() => { + windowDi.register(someInjectable); + }); }); }); diff --git a/src/common/utils/with-error-logging/with-error-logging.test.ts b/src/common/utils/with-error-logging/with-error-logging.test.ts index 05526c64c9..b14b7278e9 100644 --- a/src/common/utils/with-error-logging/with-error-logging.test.ts +++ b/src/common/utils/with-error-logging/with-error-logging.test.ts @@ -19,7 +19,7 @@ describe("with-error-logging", () => { let decorated: (a: string, b: string) => number | undefined; beforeEach(() => { - const di = getDiForUnitTesting(); + const di = getDiForUnitTesting({ doGeneralOverrides: true }); loggerStub = { error: jest.fn(), @@ -119,7 +119,7 @@ describe("with-error-logging", () => { let toBeDecorated: AsyncFnMock; beforeEach(() => { - const di = getDiForUnitTesting(); + const di = getDiForUnitTesting({ doGeneralOverrides: true }); loggerStub = { error: jest.fn(), diff --git a/src/common/vars.ts b/src/common/vars.ts index be8b472a5d..a5507e1ee6 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -5,7 +5,6 @@ // App's common configuration for any process (main, renderer, build pipeline, etc.) import path from "path"; -import packageInfo from "../../package.json"; import type { ThemeId } from "../renderer/themes/store"; import { lazyInitialized } from "./utils/lazy-initialized"; @@ -25,7 +24,6 @@ export const isWindows = process.platform === "win32"; export const isLinux = process.platform === "linux"; export const isDebugging = ["true", "1", "yes", "y", "on"].includes((process.env.DEBUG ?? "").toLowerCase()); -export const isSnap = !!process.env.SNAP; /** * @deprecated Switch to using isTestEnvInjectable @@ -42,13 +40,6 @@ export const isProduction = process.env.NODE_ENV === "production"; */ export const isDevelopment = !isTestEnv && !isProduction; -export const productName = packageInfo.productName; - -/** - * @deprecated Switch to using appNameInjectable - */ -export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`; - export const publicPath = "/build/" as string; export const defaultThemeId: ThemeId = "lens-dark"; export const defaultFontSize = 12; @@ -139,6 +130,3 @@ export const lensBlogWeblinkId = "lens-blog-link"; export const kubernetesDocumentationWeblinkId = "kubernetes-documentation-link"; export const docsUrl = "https://docs.k8slens.dev/main" as string; - -export const sentryDsn = packageInfo.config?.sentryDsn ?? ""; -export const contentSecurityPolicy = packageInfo.config?.contentSecurityPolicy ?? ""; diff --git a/src/main/app-paths/app-name/app-name.injectable.ts b/src/common/vars/app-name.injectable.ts similarity index 83% rename from src/main/app-paths/app-name/app-name.injectable.ts rename to src/common/vars/app-name.injectable.ts index 0a1db468d8..4d6d87421c 100644 --- a/src/main/app-paths/app-name/app-name.injectable.ts +++ b/src/common/vars/app-name.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isDevelopmentInjectable from "../../../common/vars/is-development.injectable"; +import isDevelopmentInjectable from "./is-development.injectable"; import productNameInjectable from "./product-name.injectable"; const appNameInjectable = getInjectable({ @@ -15,8 +15,6 @@ const appNameInjectable = getInjectable({ return `${productName}${isDevelopment ? "Dev" : ""}`; }, - - causesSideEffects: true, }); export default appNameInjectable; diff --git a/src/common/vars/app-semantic-version.injectable.ts b/src/common/vars/app-semantic-version.injectable.ts deleted file mode 100644 index ae68ea828d..0000000000 --- a/src/common/vars/app-semantic-version.injectable.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { SemVer } from "semver"; -import appVersionInjectable from "./app-version.injectable"; - -const appSemanticVersionInjectable = getInjectable({ - id: "app-semantic-version", - instantiate: (di) => new SemVer(di.inject(appVersionInjectable)), -}); - -export default appSemanticVersionInjectable; diff --git a/src/common/vars/app-version.injectable.ts b/src/common/vars/app-version.injectable.ts deleted file mode 100644 index d7647f8318..0000000000 --- a/src/common/vars/app-version.injectable.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import packageJsonInjectable from "./package-json.injectable"; - -const appVersionInjectable = getInjectable({ - id: "app-version", - instantiate: (di) => di.inject(packageJsonInjectable).version, -}); - -export default appVersionInjectable; diff --git a/src/common/vars/application-copyright.injectable.ts b/src/common/vars/application-copyright.injectable.ts new file mode 100644 index 0000000000..cdac64855c --- /dev/null +++ b/src/common/vars/application-copyright.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationInformationInjectable from "./application-information.injectable"; + +const applicationCopyrightInjectable = getInjectable({ + id: "application-copyright", + instantiate: (di) => di.inject(applicationInformationInjectable).copyright, +}); + +export default applicationCopyrightInjectable; diff --git a/src/common/vars/application-description.injectable.ts b/src/common/vars/application-description.injectable.ts new file mode 100644 index 0000000000..d6c4c9f79b --- /dev/null +++ b/src/common/vars/application-description.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationInformationInjectable from "./application-information.injectable"; + +const applicationDescriptionInjectable = getInjectable({ + id: "application-description", + instantiate: (di) => di.inject(applicationInformationInjectable).description, +}); + +export default applicationDescriptionInjectable; diff --git a/src/common/vars/application-information.global-override-for-injectable.ts b/src/common/vars/application-information.global-override-for-injectable.ts new file mode 100644 index 0000000000..ac53b9f341 --- /dev/null +++ b/src/common/vars/application-information.global-override-for-injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getGlobalOverride } from "../test-utils/get-global-override"; +import applicationInformationInjectable from "./application-information.injectable"; + +export default getGlobalOverride(applicationInformationInjectable, () => ({ + productName: "some-product-name", + version: "6.0.0", + build: {}, + config: { + k8sProxyVersion: "0.2.1", + bundledKubectlVersion: "1.23.3", + bundledHelmVersion: "3.7.2", + sentryDsn: "", + contentSecurityPolicy: "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:", + welcomeRoute: "/welcome", + }, + copyright: "some-copyright-information", + description: "some-descriptive-text", +})); diff --git a/src/common/vars/application-information.injectable.ts b/src/common/vars/application-information.injectable.ts new file mode 100644 index 0000000000..559b15294b --- /dev/null +++ b/src/common/vars/application-information.injectable.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import packageJson from "../../../package.json"; + +export type ApplicationInformation = Pick & { + build: Partial & { publish?: unknown[] }; +}; + +const applicationInformationInjectable = getInjectable({ + id: "application-information", + instantiate: (): ApplicationInformation => { + const { version, config, productName, build, copyright, description } = packageJson; + + return { version, config, productName, build, copyright, description }; + }, + causesSideEffects: true, +}); + +export default applicationInformationInjectable; diff --git a/src/common/vars/build-semantic-version.injectable.ts b/src/common/vars/build-semantic-version.injectable.ts new file mode 100644 index 0000000000..a41efb0bd7 --- /dev/null +++ b/src/common/vars/build-semantic-version.injectable.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; +import { SemVer } from "semver"; +import type { InitializableState } from "../initializable-state/create"; +import { createInitializableState } from "../initializable-state/create"; +import type { RequestChannel } from "../utils/channel/request-channel-injection-token"; + +export const buildVersionInjectionToken = getInjectionToken>({ + id: "build-version-token", +}); + +export const buildVersionChannel: RequestChannel = { + id: "build-version", +}; + +const buildSemanticVersionInjectable = createInitializableState({ + id: "build-semantic-version", + init: (di) => { + const buildVersion = di.inject(buildVersionInjectionToken); + + return new SemVer(buildVersion.get()); + }, +}); + +export default buildSemanticVersionInjectable; + diff --git a/src/common/vars/bundled-kubectl-version.injectable.ts b/src/common/vars/bundled-kubectl-version.injectable.ts new file mode 100644 index 0000000000..9542a79834 --- /dev/null +++ b/src/common/vars/bundled-kubectl-version.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationInformationInjectable from "./application-information.injectable"; + +const bundledKubectlVersionInjectable = getInjectable({ + id: "bundled-kubectl-version", + instantiate: (di) => di.inject(applicationInformationInjectable).config.bundledKubectlVersion, +}); + +export default bundledKubectlVersionInjectable; diff --git a/src/common/vars/content-security-policy.injectable.ts b/src/common/vars/content-security-policy.injectable.ts new file mode 100644 index 0000000000..b6e1e0eb30 --- /dev/null +++ b/src/common/vars/content-security-policy.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationInformationInjectable from "./application-information.injectable"; + +const contentSecurityPolicyInjectable = getInjectable({ + id: "content-security-policy", + instantiate: (di) => di.inject(applicationInformationInjectable).config.contentSecurityPolicy, +}); + +export default contentSecurityPolicyInjectable; diff --git a/src/common/vars/extension-api-version.injectable.ts b/src/common/vars/extension-api-version.injectable.ts new file mode 100644 index 0000000000..4f7f4d9930 --- /dev/null +++ b/src/common/vars/extension-api-version.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { SemVer } from "semver"; +import applicationInformationInjectable from "./application-information.injectable"; + +const extensionApiVersionInjectable = getInjectable({ + id: "extension-api-version", + instantiate: (di) => { + const { major, minor, patch } = new SemVer(di.inject(applicationInformationInjectable).version); + + return `${major}.${minor}.${patch}`; + }, +}); + +export default extensionApiVersionInjectable; diff --git a/src/common/vars/is-snap-package.global-override-for-injectable.ts b/src/common/vars/is-snap-package.global-override-for-injectable.ts new file mode 100644 index 0000000000..cb3ff0a6e9 --- /dev/null +++ b/src/common/vars/is-snap-package.global-override-for-injectable.ts @@ -0,0 +1,9 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getGlobalOverride } from "../test-utils/get-global-override"; +import isSnapPackageInjectable from "./is-snap-package.injectable"; + +export default getGlobalOverride(isSnapPackageInjectable, () => false); diff --git a/src/common/vars/package-json.injectable.ts b/src/common/vars/is-snap-package.injectable.ts similarity index 55% rename from src/common/vars/package-json.injectable.ts rename to src/common/vars/is-snap-package.injectable.ts index fa132be518..a2c545870b 100644 --- a/src/common/vars/package-json.injectable.ts +++ b/src/common/vars/is-snap-package.injectable.ts @@ -3,12 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import packageJson from "../../../package.json"; -const packageJsonInjectable = getInjectable({ - id: "package-json", - instantiate: () => packageJson, +const isSnapPackageInjectable = getInjectable({ + id: "is-snap", + instantiate: () => Boolean(process.env.SNAP), causesSideEffects: true, }); -export default packageJsonInjectable; +export default isSnapPackageInjectable; diff --git a/src/main/app-paths/app-name/product-name.injectable.ts b/src/common/vars/product-name.injectable.ts similarity index 65% rename from src/main/app-paths/app-name/product-name.injectable.ts rename to src/common/vars/product-name.injectable.ts index 8c5c53bfba..910c6afa48 100644 --- a/src/main/app-paths/app-name/product-name.injectable.ts +++ b/src/common/vars/product-name.injectable.ts @@ -3,12 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import packageInfo from "../../../../package.json"; +import applicationInformationInjectable from "./application-information.injectable"; const productNameInjectable = getInjectable({ id: "product-name", - instantiate: () => packageInfo.productName, - causesSideEffects: true, + instantiate: (di) => di.inject(applicationInformationInjectable).productName, }); export default productNameInjectable; diff --git a/src/common/vars/release-channel.injectable.ts b/src/common/vars/release-channel.injectable.ts new file mode 100644 index 0000000000..d8275ff1cb --- /dev/null +++ b/src/common/vars/release-channel.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { ReleaseChannel } from "../application-update/update-channels"; +import { createInitializableState } from "../initializable-state/create"; +import buildSemanticVersionInjectable from "./build-semantic-version.injectable"; + +const releaseChannelInjectable = createInitializableState({ + id: "release-channel", + init: (di): ReleaseChannel => { + const buildSemanticVersion = di.inject(buildSemanticVersionInjectable); + const currentReleaseChannel = buildSemanticVersion.get().prerelease[0]; + + switch (currentReleaseChannel) { + case "latest": + case "beta": + case "alpha": + return currentReleaseChannel; + default: + return "latest"; + } + }, +}); + +export default releaseChannelInjectable; diff --git a/src/common/vars/sentry-dsn-url.injectable.ts b/src/common/vars/sentry-dsn-url.injectable.ts new file mode 100644 index 0000000000..e33c2fd0de --- /dev/null +++ b/src/common/vars/sentry-dsn-url.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationInformationInjectable from "./application-information.injectable"; + +const sentryDataSourceNameInjectable = getInjectable({ + id: "sentry-data-source-name", + instantiate: (di) => di.inject(applicationInformationInjectable).config.sentryDsn, +}); + +export default sentryDataSourceNameInjectable; diff --git a/src/common/vars/store-migration-version.injectable.ts b/src/common/vars/store-migration-version.injectable.ts new file mode 100644 index 0000000000..79ab1578a3 --- /dev/null +++ b/src/common/vars/store-migration-version.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import applicationInformationInjectable from "./application-information.injectable"; + +const storeMigrationVersionInjectable = getInjectable({ + id: "store-migration-version", + instantiate: (di) => di.inject(applicationInformationInjectable).version, +}); + +export default storeMigrationVersionInjectable; diff --git a/src/extensions/__tests__/is-compatible-extension.test.ts b/src/extensions/__tests__/is-compatible-extension.test.ts index d581722139..f2ca144681 100644 --- a/src/extensions/__tests__/is-compatible-extension.test.ts +++ b/src/extensions/__tests__/is-compatible-extension.test.ts @@ -3,65 +3,53 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import assert from "assert"; -import semver from "semver"; import { isCompatibleExtension } from "../extension-discovery/is-compatible-extension/is-compatible-extension"; import type { LensExtensionManifest } from "../lens-extension"; describe("Extension/App versions compatibility checks", () => { it("is compatible with exact version matching", () => { - expect(isCompatible({ extLensEngineVersion: "5.5.0", appVersion: "5.5.0" })).toBeTruthy(); + expect(isCompatible({ extLensEngineVersion: "5.5.0", extensionApiVersion: "5.5.0" })).toBeTruthy(); }); it("is compatible with upper %PATCH versions of base app", () => { - expect(isCompatible({ extLensEngineVersion: "5.5.0", appVersion: "5.5.5" })).toBeTruthy(); + expect(isCompatible({ extLensEngineVersion: "5.5.0", extensionApiVersion: "5.5.5" })).toBeTruthy(); }); it("is compatible with higher %MINOR version of base app", () => { - expect(isCompatible({ extLensEngineVersion: "5.5.0", appVersion: "5.6.0" })).toBeTruthy(); + expect(isCompatible({ extLensEngineVersion: "5.5.0", extensionApiVersion: "5.6.0" })).toBeTruthy(); }); it("is not compatible with higher %MAJOR version of base app", () => { - expect(isCompatible({ extLensEngineVersion: "5.6.0", appVersion: "6.0.0" })).toBeFalsy(); // extension for lens@5 not compatible with lens@6 - expect(isCompatible({ extLensEngineVersion: "6.0.0", appVersion: "5.6.0" })).toBeFalsy(); - }); - - it("is compatible with lensEngine with prerelease", () => { - expect(isCompatible({ - extLensEngineVersion: "^5.4.0-alpha.0", - appVersion: "5.5.0-alpha.0", - })).toBeTruthy(); + expect(isCompatible({ extLensEngineVersion: "5.6.0", extensionApiVersion: "6.0.0" })).toBeFalsy(); // extension for lens@5 not compatible with lens@6 + expect(isCompatible({ extLensEngineVersion: "6.0.0", extensionApiVersion: "5.6.0" })).toBeFalsy(); }); it("supports short version format for manifest.engines.lens", () => { - expect(isCompatible({ extLensEngineVersion: "5.5", appVersion: "5.5.1" })).toBeTruthy(); + expect(isCompatible({ extLensEngineVersion: "5.5", extensionApiVersion: "5.5.1" })).toBeTruthy(); }); it("throws for incorrect or not supported version format", () => { expect(() => isCompatible({ extLensEngineVersion: ">=2.0", - appVersion: "2.0", + extensionApiVersion: "2.0", })).toThrow(/Invalid format/i); expect(() => isCompatible({ extLensEngineVersion: "~2.0", - appVersion: "2.0", + extensionApiVersion: "2.0", })).toThrow(/Invalid format/i); expect(() => isCompatible({ extLensEngineVersion: "*", - appVersion: "1.0", + extensionApiVersion: "1.0", })).toThrow(/Invalid format/i); }); }); -function isCompatible({ extLensEngineVersion = "^1.0", appVersion = "1.0" } = {}): boolean { - const appSemVer = semver.coerce(appVersion); +function isCompatible({ extLensEngineVersion = "^1.0", extensionApiVersion = "1.0" } = {}): boolean { const extensionManifestMock = getExtensionManifestMock(extLensEngineVersion); - assert(appSemVer); - - return isCompatibleExtension({ appSemVer })(extensionManifestMock); + return isCompatibleExtension({ extensionApiVersion })(extensionManifestMock); } function getExtensionManifestMock(lensEngine = "1.0"): LensExtensionManifest { diff --git a/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts b/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts index 340a97c941..a5261d2917 100644 --- a/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts +++ b/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts @@ -9,7 +9,7 @@ import type { Injectable } from "@ogre-tools/injectable"; * @deprecated use asLegacyGlobalForExtensionApi instead, and use proper implementations instead of "modifications". */ export const asLegacyGlobalObjectForExtensionApiWithModifications = < - InjectableInstance extends InjectionTokenInstance, + InjectableInstance extends InjectionTokenInstance & object, InjectionTokenInstance, ModificationObject extends object, >( diff --git a/src/extensions/common-api/app.ts b/src/extensions/common-api/app.ts index 6be9a193d1..92ecd19ef9 100644 --- a/src/extensions/common-api/app.ts +++ b/src/extensions/common-api/app.ts @@ -3,14 +3,65 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getAppVersion } from "../../common/utils"; +import appNameInjectable from "../../common/vars/app-name.injectable"; +import isLinuxInjectable from "../../common/vars/is-linux.injectable"; +import isMacInjectable from "../../common/vars/is-mac.injectable"; +import isSnapPackageInjectable from "../../common/vars/is-snap-package.injectable"; +import isWindowsInjectable from "../../common/vars/is-windows.injectable"; import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; +import { getLegacyGlobalDiForExtensionApi } from "../as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import getEnabledExtensionsInjectable from "./get-enabled-extensions/get-enabled-extensions.injectable"; -import * as Preferences from "./user-preferences"; +import type { UserPreferenceExtensionItems } from "./user-preferences"; +import { Preferences } from "./user-preferences"; +import { slackUrl, issuesTrackerUrl } from "../../common/vars"; +import { buildVersionInjectionToken } from "../../common/vars/build-semantic-version.injectable"; -export const version = getAppVersion(); -export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars"; +export interface AppExtensionItems { + readonly Preferences: UserPreferenceExtensionItems; + readonly version: string; + readonly appName: string; + readonly slackUrl: string; + readonly issuesTrackerUrl: string; + readonly isSnap: boolean; + readonly isWindows: boolean; + readonly isMac: boolean; + readonly isLinux: boolean; + getEnabledExtensions: () => string[]; +} -export const getEnabledExtensions = asLegacyGlobalFunctionForExtensionApi(getEnabledExtensionsInjectable); +export const App: AppExtensionItems = { + Preferences, + getEnabledExtensions: asLegacyGlobalFunctionForExtensionApi(getEnabledExtensionsInjectable), + get version() { + const di = getLegacyGlobalDiForExtensionApi(); -export { Preferences }; + return di.inject(buildVersionInjectionToken).get(); + }, + get appName() { + const di = getLegacyGlobalDiForExtensionApi(); + + return di.inject(appNameInjectable); + }, + get isSnap() { + const di = getLegacyGlobalDiForExtensionApi(); + + return di.inject(isSnapPackageInjectable); + }, + get isWindows() { + const di = getLegacyGlobalDiForExtensionApi(); + + return di.inject(isWindowsInjectable); + }, + get isMac() { + const di = getLegacyGlobalDiForExtensionApi(); + + return di.inject(isMacInjectable); + }, + get isLinux() { + const di = getLegacyGlobalDiForExtensionApi(); + + return di.inject(isLinuxInjectable); + }, + slackUrl, + issuesTrackerUrl, +}; diff --git a/src/extensions/common-api/index.ts b/src/extensions/common-api/index.ts index 0d07220d1d..6e4b39b1b7 100644 --- a/src/extensions/common-api/index.ts +++ b/src/extensions/common-api/index.ts @@ -4,10 +4,10 @@ */ // APIs -import * as App from "./app"; +import { App } from "./app"; import * as EventBus from "./event-bus"; import * as Store from "./stores"; -import * as Util from "./utils"; +import { Util } from "./utils"; import * as Catalog from "./catalog"; import * as Types from "./types"; import * as Proxy from "./proxy"; diff --git a/src/extensions/common-api/k8s-api.ts b/src/extensions/common-api/k8s-api.ts index 45d8230597..7fd25b08a1 100644 --- a/src/extensions/common-api/k8s-api.ts +++ b/src/extensions/common-api/k8s-api.ts @@ -35,6 +35,7 @@ export { } from "../../common/k8s-api/kube-object"; export { + KubeJsonApi, type KubeJsonApiData, } from "../../common/k8s-api/kube-json-api"; @@ -47,7 +48,7 @@ export { } from "../../common/k8s-api/kube-object.store"; export { - type PodContainer as IPodContainer, + type Container as IPodContainer, type PodContainerStatus as IPodContainerStatus, Pod, PodApi as PodsApi, diff --git a/src/extensions/common-api/user-preferences.ts b/src/extensions/common-api/user-preferences.ts index 2c44f6f604..3a0a93793b 100644 --- a/src/extensions/common-api/user-preferences.ts +++ b/src/extensions/common-api/user-preferences.ts @@ -4,10 +4,13 @@ */ import { UserStore } from "../../common/user-store"; - -/** - * Get the configured kubectl binaries path. - */ -export function getKubectlPath(): string | undefined { - return UserStore.getInstance().kubectlBinariesPath; +export interface UserPreferenceExtensionItems { + /** + * Get the configured kubectl binaries path. + */ + getKubectlPath: () => string | undefined; } + +export const Preferences: UserPreferenceExtensionItems = { + getKubectlPath: () => UserStore.getInstance().kubectlBinariesPath, +}; diff --git a/src/extensions/common-api/utils.ts b/src/extensions/common-api/utils.ts index 3dea165238..a6dfdee447 100644 --- a/src/extensions/common-api/utils.ts +++ b/src/extensions/common-api/utils.ts @@ -4,14 +4,34 @@ */ import openLinkInBrowserInjectable from "../../common/utils/open-link-in-browser.injectable"; +import buildVersionInjectable from "../../main/vars/build-version/build-version.injectable"; import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; +import { getLegacyGlobalDiForExtensionApi } from "../as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import { Singleton } from "../../common/utils"; +import { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault"; +import type { IClassName } from "../../renderer/utils/cssNames"; +import { cssNames } from "../../renderer/utils/cssNames"; -export { Singleton, getAppVersion } from "../../common/utils"; -export { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault"; -export { cssNames } from "../../renderer/utils/cssNames"; +export interface UtilsExtensionItems { + Singleton: typeof Singleton; + prevDefault: (callback: (evt: E) => R) => (evt: E) => R; + stopPropagation: (evt: Event | React.SyntheticEvent) => void; + cssNames: (...classNames: IClassName[]) => string; + openExternal: (url: string) => Promise; + openBrowser: (url: string) => Promise; + getAppVersion: () => string; +} -/** - * @deprecated Use {@link openBrowser} instead - */ -export const openExternal = asLegacyGlobalFunctionForExtensionApi(openLinkInBrowserInjectable); -export const openBrowser = asLegacyGlobalFunctionForExtensionApi(openLinkInBrowserInjectable); +export const Util: UtilsExtensionItems = { + Singleton, + prevDefault, + stopPropagation, + cssNames, + openExternal: asLegacyGlobalFunctionForExtensionApi(openLinkInBrowserInjectable), + openBrowser: asLegacyGlobalFunctionForExtensionApi(openLinkInBrowserInjectable), + getAppVersion: () => { + const di = getLegacyGlobalDiForExtensionApi(); + + return di.inject(buildVersionInjectable).get(); + }, +}; diff --git a/src/extensions/extension-discovery/extension-discovery.injectable.ts b/src/extensions/extension-discovery/extension-discovery.injectable.ts index e6ebaa769c..df5efadaf6 100644 --- a/src/extensions/extension-discovery/extension-discovery.injectable.ts +++ b/src/extensions/extension-discovery/extension-discovery.injectable.ts @@ -6,7 +6,6 @@ import { getInjectable } from "@ogre-tools/injectable"; import { ExtensionDiscovery } from "./extension-discovery"; import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable"; import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable"; -import isCompatibleBundledExtensionInjectable from "./is-compatible-bundled-extension/is-compatible-bundled-extension.injectable"; import extensionsStoreInjectable from "../extensions-store/extensions-store.injectable"; import extensionInstallationStateStoreInjectable from "../extension-installation-state-store/extension-installation-state-store.injectable"; import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable"; @@ -21,34 +20,20 @@ import watchInjectable from "../../common/fs/watch/watch.injectable"; const extensionDiscoveryInjectable = getInjectable({ id: "extension-discovery", - instantiate: (di) => - new ExtensionDiscovery({ - extensionLoader: di.inject(extensionLoaderInjectable), - extensionsStore: di.inject(extensionsStoreInjectable), - - extensionInstallationStateStore: di.inject( - extensionInstallationStateStoreInjectable, - ), - - isCompatibleBundledExtension: di.inject( - isCompatibleBundledExtensionInjectable, - ), - - isCompatibleExtension: di.inject(isCompatibleExtensionInjectable), - - installExtension: di.inject(installExtensionInjectable), - installExtensions: di.inject(installExtensionsInjectable), - - extensionPackageRootDirectory: di.inject( - extensionPackageRootDirectoryInjectable, - ), - - staticFilesDirectory: di.inject(staticFilesDirectoryInjectable), - readJsonFile: di.inject(readJsonFileInjectable), - pathExists: di.inject(pathExistsInjectable), - watch: di.inject(watchInjectable), - logger: di.inject(loggerInjectable), - }), + instantiate: (di) => new ExtensionDiscovery({ + extensionLoader: di.inject(extensionLoaderInjectable), + extensionsStore: di.inject(extensionsStoreInjectable), + extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable), + isCompatibleExtension: di.inject(isCompatibleExtensionInjectable), + installExtension: di.inject(installExtensionInjectable), + installExtensions: di.inject(installExtensionsInjectable), + extensionPackageRootDirectory: di.inject(extensionPackageRootDirectoryInjectable), + staticFilesDirectory: di.inject(staticFilesDirectoryInjectable), + readJsonFile: di.inject(readJsonFileInjectable), + pathExists: di.inject(pathExistsInjectable), + watch: di.inject(watchInjectable), + logger: di.inject(loggerInjectable), + }), }); export default extensionDiscoveryInjectable; diff --git a/src/extensions/extension-discovery/extension-discovery.test.ts b/src/extensions/extension-discovery/extension-discovery.test.ts index 7feca8f99d..e5a04bd5eb 100644 --- a/src/extensions/extension-discovery/extension-discovery.test.ts +++ b/src/extensions/extension-discovery/extension-discovery.test.ts @@ -15,10 +15,10 @@ import directoryForUserDataInjectable from "../../common/app-paths/directory-for import mockFs from "mock-fs"; import { delay } from "../../renderer/utils"; import { observable, when } from "mobx"; -import appVersionInjectable from "../../common/vars/app-version.injectable"; import readJsonFileInjectable from "../../common/fs/read-json-file.injectable"; import pathExistsInjectable from "../../common/fs/path-exists.injectable"; import watchInjectable from "../../common/fs/watch/watch.injectable"; +import extensionApiVersionInjectable from "../../common/vars/extension-api-version.injectable"; console = new Console(process.stdout, process.stderr); // fix mockFS @@ -33,7 +33,7 @@ describe("ExtensionDiscovery", () => { di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); di.override(installExtensionInjectable, () => () => Promise.resolve()); - di.override(appVersionInjectable, () => "5.0.0"); + di.override(extensionApiVersionInjectable, () => "5.0.0"); readJsonFileMock = jest.fn(); di.override(readJsonFileInjectable, () => readJsonFileMock); diff --git a/src/extensions/extension-discovery/extension-discovery.ts b/src/extensions/extension-discovery/extension-discovery.ts index 1de6425c19..e6f500805a 100644 --- a/src/extensions/extension-discovery/extension-discovery.ts +++ b/src/extensions/extension-discovery/extension-discovery.ts @@ -27,12 +27,8 @@ import type { Watch } from "../../common/fs/watch/watch.injectable"; interface Dependencies { extensionLoader: ExtensionLoader; extensionsStore: ExtensionsStore; - extensionInstallationStateStore: ExtensionInstallationStateStore; - - isCompatibleBundledExtension: (manifest: LensExtensionManifest) => boolean; isCompatibleExtension: (manifest: LensExtensionManifest) => boolean; - installExtension: (name: string) => Promise; installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise; extensionPackageRootDirectory: string; @@ -102,7 +98,7 @@ export class ExtensionDiscovery { public events = new EventEmitter(); - constructor(protected dependencies : Dependencies) { + constructor(protected readonly dependencies: Dependencies) { makeObservable(this); } @@ -367,9 +363,10 @@ export class ExtensionDiscovery { const id = this.getInstalledManifestPath(manifest.name); const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled }); const extensionDir = path.dirname(manifestPath); - const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`); + const packedName = manifest.name.replaceAll("@", "").replaceAll("/", "-"); + const npmPackage = path.join(extensionDir, `${packedName}-${manifest.version}.tgz`); const absolutePath = (isProduction && await this.dependencies.pathExists(npmPackage)) ? npmPackage : extensionDir; - const isCompatible = (isBundled && this.dependencies.isCompatibleBundledExtension(manifest)) || this.dependencies.isCompatibleExtension(manifest); + const isCompatible = isBundled || this.dependencies.isCompatibleExtension(manifest); return { id, @@ -394,28 +391,11 @@ export class ExtensionDiscovery { async ensureExtensions(): Promise> { const bundledExtensions = await this.loadBundledExtensions(); - - await this.installBundledPackages(this.packageJsonPath, bundledExtensions); - const userExtensions = await this.loadFromFolder(this.localFolderPath, bundledExtensions.map((extension) => extension.manifest.name)); - - for (const extension of userExtensions) { - if (!(await this.dependencies.pathExists(extension.manifestPath))) { - try { - await this.dependencies.installExtension(extension.absolutePath); - } catch (error) { - const message = error instanceof Error - ? error.message - : String(error || "unknown error"); - const { name, version } = extension.manifest; - - this.dependencies.logger.error(`${logModule}: failed to install user extension ${name}@${version}: ${message}`); - } - } - } - const extensions = bundledExtensions.concat(userExtensions); + await this.installBundledPackages(this.packageJsonPath, extensions); + return this.extensions = new Map(extensions.map(extension => [extension.id, extension])); } @@ -424,10 +404,13 @@ export class ExtensionDiscovery { */ installBundledPackages(packageJsonPath: string, extensions: InstalledExtension[]): Promise { const dependencies = Object.fromEntries( - extensions.map(extension => [extension.manifest.name, extension.absolutePath]), + extensions.filter(extension => extension.isBundled).map(extension => [extension.manifest.name, extension.absolutePath]), + ); + const optionalDependencies = Object.fromEntries( + extensions.filter(extension => !extension.isBundled).map(extension => [extension.manifest.name, extension.absolutePath]), ); - return this.dependencies.installExtensions(packageJsonPath, { dependencies }); + return this.dependencies.installExtensions(packageJsonPath, { dependencies, optionalDependencies }); } async loadBundledExtensions(): Promise { diff --git a/src/extensions/extension-discovery/is-compatible-bundled-extension/is-compatible-bundled-extension.injectable.ts b/src/extensions/extension-discovery/is-compatible-bundled-extension/is-compatible-bundled-extension.injectable.ts deleted file mode 100644 index 7a29daf23c..0000000000 --- a/src/extensions/extension-discovery/is-compatible-bundled-extension/is-compatible-bundled-extension.injectable.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import appSemanticVersionInjectable from "../../../common/vars/app-semantic-version.injectable"; -import { isCompatibleBundledExtension } from "./is-compatible-bundled-extension"; - -const isCompatibleBundledExtensionInjectable = getInjectable({ - id: "is-compatible-bundled-extension", - instantiate: (di) => isCompatibleBundledExtension({ - appSemVer: di.inject(appSemanticVersionInjectable), - }), -}); - -export default isCompatibleBundledExtensionInjectable; diff --git a/src/extensions/extension-discovery/is-compatible-bundled-extension/is-compatible-bundled-extension.ts b/src/extensions/extension-discovery/is-compatible-bundled-extension/is-compatible-bundled-extension.ts deleted file mode 100644 index 6ae1e0b5d3..0000000000 --- a/src/extensions/extension-discovery/is-compatible-bundled-extension/is-compatible-bundled-extension.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import type { LensExtensionManifest } from "../../lens-extension"; -import { isProduction } from "../../../common/vars"; -import type { SemVer } from "semver"; - -interface Dependencies { - appSemVer: SemVer; -} - -export const isCompatibleBundledExtension = - ({ appSemVer }: Dependencies) => - (manifest: LensExtensionManifest): boolean => - !isProduction || manifest.version === appSemVer.raw; diff --git a/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.injectable.ts b/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.injectable.ts index 75e2f45d4a..de2fd4390f 100644 --- a/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.injectable.ts +++ b/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.injectable.ts @@ -3,13 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import appSemanticVersionInjectable from "../../../common/vars/app-semantic-version.injectable"; +import extensionApiVersionInjectable from "../../../common/vars/extension-api-version.injectable"; import { isCompatibleExtension } from "./is-compatible-extension"; const isCompatibleExtensionInjectable = getInjectable({ id: "is-compatible-extension", instantiate: (di) => isCompatibleExtension({ - appSemVer: di.inject(appSemanticVersionInjectable), + extensionApiVersion: di.inject(extensionApiVersionInjectable), }), }); diff --git a/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.ts b/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.ts index 717effa1c6..74cbb4fd0c 100644 --- a/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.ts +++ b/src/extensions/extension-discovery/is-compatible-extension/is-compatible-extension.ts @@ -2,16 +2,15 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import semver, { type SemVer } from "semver"; +import semver from "semver"; import type { LensExtensionManifest } from "../../lens-extension"; interface Dependencies { - appSemVer: SemVer; + extensionApiVersion: string; } -export const isCompatibleExtension = ({ appSemVer }: Dependencies): ((manifest: LensExtensionManifest) => boolean) => { +export const isCompatibleExtension = ({ extensionApiVersion }: Dependencies): ((manifest: LensExtensionManifest) => boolean) => { return (manifest: LensExtensionManifest): boolean => { - const appVersion = appSemVer.raw.split("-")[0]; // drop prerelease version if any, e.g. "-alpha.0" const manifestLensEngine = manifest.engines.lens; const validVersion = manifestLensEngine.match(/^[\^0-9]\d*\.\d+\b/); // must start from ^ or number @@ -30,7 +29,7 @@ export const isCompatibleExtension = ({ appSemVer }: Dependencies): ((manifest: }) as semver.SemVer; const supportedVersionsByExtension = semver.validRange(`^${extMajor}.${extMinor}`) as string; - return semver.satisfies(appVersion, supportedVersionsByExtension, { + return semver.satisfies(extensionApiVersion, supportedVersionsByExtension, { loose: true, includePrerelease: false, }); diff --git a/src/extensions/extension-installer/extension-installer.ts b/src/extensions/extension-installer/extension-installer.ts index 0c0a1554dc..1764435a54 100644 --- a/src/extensions/extension-installer/extension-installer.ts +++ b/src/extensions/extension-installer/extension-installer.ts @@ -25,7 +25,7 @@ export class ExtensionInstaller { constructor(private dependencies: Dependencies) {} get npmPath() { - return __non_webpack_require__.resolve("npm/bin/npm-cli"); + return __non_webpack_require__.resolve("npm"); } /** @@ -42,7 +42,7 @@ export class ExtensionInstaller { }); logger.info(`${logModule} installing dependencies at ${this.dependencies.extensionPackageRootDirectory}`); - await this.npm(["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock"]); + await this.npm(["install", "--audit=false", "--fund=false", "--only=prod", "--prefer-offline"]); logger.info(`${logModule} dependencies installed at ${this.dependencies.extensionPackageRootDirectory}`); } finally { this.installLock.release(); @@ -58,7 +58,7 @@ export class ExtensionInstaller { try { logger.info(`${logModule} installing package from ${name} to ${this.dependencies.extensionPackageRootDirectory}`); - await this.npm(["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock", "--no-save", name]); + await this.npm(["install", "--audit=false", "--fund=false", "--only=prod", "--prefer-offline", name]); logger.info(`${logModule} package ${name} installed to ${this.dependencies.extensionPackageRootDirectory}`); } finally { this.installLock.release(); diff --git a/src/extensions/extension-loader/extension/extension.injectable.ts b/src/extensions/extension-loader/extension/extension.injectable.ts index ba1bb6aea8..7ed23a3dfa 100644 --- a/src/extensions/extension-loader/extension/extension.injectable.ts +++ b/src/extensions/extension-loader/extension/extension.injectable.ts @@ -3,6 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { runInAction } from "mobx"; import type { LensExtension } from "../../lens-extension"; import { extensionRegistratorInjectionToken } from "../extension-registrator-injection-token"; @@ -27,17 +28,23 @@ const extensionInjectable = getInjectable({ getInjectablesOfExtension(instance), ); - childDi.register(...injectables); + runInAction(() => { + childDi.register(...injectables); + }); }, deregister: () => { - parentDi.deregister(extensionInjectable); + runInAction(() => { + parentDi.deregister(extensionInjectable); + }); }, }; }, }); - parentDi.register(extensionInjectable); + runInAction(() => { + parentDi.register(extensionInjectable); + }); return parentDi.inject(extensionInjectable); }, diff --git a/src/extensions/extension-store.ts b/src/extensions/extension-store.ts index 62799f9843..4217576d2f 100644 --- a/src/extensions/extension-store.ts +++ b/src/extensions/extension-store.ts @@ -8,13 +8,15 @@ import * as path from "path"; import type { LensExtension } from "./lens-extension"; import assert from "assert"; -export abstract class ExtensionStore extends BaseStore { +export abstract class ExtensionStore extends BaseStore { readonly displayName = "ExtensionStore"; protected extension?: LensExtension; loadExtension(extension: LensExtension) { this.extension = extension; + this.params.projectVersion ??= this.extension.version; + return super.load(); } diff --git a/src/extensions/npm/extensions/package-lock.json b/src/extensions/npm/extensions/package-lock.json index cb350082bc..dd8bade7d9 100644 --- a/src/extensions/npm/extensions/package-lock.json +++ b/src/extensions/npm/extensions/package-lock.json @@ -1,8 +1,1311 @@ { "name": "@k8slens/extensions", "version": "0.0.1", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "@k8slens/extensions", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@material-ui/core": "4.12.3", + "@types/node": "14.17.14", + "@types/react": "^17.0.45", + "@types/react-dom": "^17.0.16", + "@types/react-router": "^5.1.18", + "@types/react-router-dom": "^5.3.3", + "conf": "^7.0.1", + "mobx": "^6.5.0", + "mobx-react": "^7.3.0", + "react-select": "^5.3.2", + "typed-emitter": "^1.3.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dependencies": { + "@babel/highlight": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz", + "integrity": "sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", + "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.17.12.tgz", + "integrity": "sha512-spyY3E3AURfxh/RHtjx5j6hs8am5NbUBGfcZ2vB3uShSpZdQyXSf5rR5Mk76vbtlAZOelyVQ71Fg0x9SG4fsog==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.17.12" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz", + "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", + "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.9.2", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz", + "integrity": "sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==", + "dependencies": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/runtime": "^7.13.10", + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.5", + "@emotion/serialize": "^1.0.2", + "babel-plugin-macros": "^2.6.1", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.0.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.9.3", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.9.3.tgz", + "integrity": "sha512-0dgkI/JKlCXa+lEXviaMtGBL0ynpx4osh7rjOXE71q9bIF8G+XhJgvi+wDu0B0IdCVx37BffiwXlN9I3UuzFvg==", + "dependencies": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.1", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "node_modules/@emotion/memoize": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", + "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" + }, + "node_modules/@emotion/react": { + "version": "11.9.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.3.tgz", + "integrity": "sha512-g9Q1GcTOlzOEjqwuLF/Zd9LC+4FljjPjDfxSM7KmEakm+hsHXk+bYZ2q+/hTJzr0OUNkujo72pXLQvXj6H+GJQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/cache": "^11.9.3", + "@emotion/serialize": "^1.0.4", + "@emotion/utils": "^1.1.0", + "@emotion/weak-memoize": "^0.2.5", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.4.tgz", + "integrity": "sha512-1JHamSpH8PIfFwAMryO2bNka+y8+KA5yga5Ocf2d7ZEiJjb7xlLW7aknBGZqJLajuLOvJ+72vN+IBSwPlXD1Pg==", + "dependencies": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/serialize/node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + }, + "node_modules/@emotion/sheet": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.1.tgz", + "integrity": "sha512-J3YPccVRMiTZxYAY0IOq3kd+hUP8idY8Kz6B/Cyo+JuXq52Ek+zbPbSQUrVQp95aJ+lsAW7DPL1P2Z+U1jGkKA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "node_modules/@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + }, + "node_modules/@material-ui/core": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.3.tgz", + "integrity": "sha512-sdpgI/PL56QVsEJldwEe4FFaFTLUqN+rd7sSZiRCdx2E/C7z5yK0y/khAWVBH24tXwto7I1hCzNWfJGZIYJKnw==", + "deprecated": "You can now upgrade to @mui/material. See the guide: https://mui.com/guides/migration-v4/", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@material-ui/styles": "^4.11.4", + "@material-ui/system": "^4.12.1", + "@material-ui/types": "5.1.0", + "@material-ui/utils": "^4.11.2", + "@types/react-transition-group": "^4.2.0", + "clsx": "^1.0.4", + "hoist-non-react-statics": "^3.3.2", + "popper.js": "1.16.1-lts", + "prop-types": "^15.7.2", + "react-is": "^16.8.0 || ^17.0.0", + "react-transition-group": "^4.4.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/styles": { + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz", + "integrity": "sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@emotion/hash": "^0.8.0", + "@material-ui/types": "5.1.0", + "@material-ui/utils": "^4.11.3", + "clsx": "^1.0.4", + "csstype": "^2.5.2", + "hoist-non-react-statics": "^3.3.2", + "jss": "^10.5.1", + "jss-plugin-camel-case": "^10.5.1", + "jss-plugin-default-unit": "^10.5.1", + "jss-plugin-global": "^10.5.1", + "jss-plugin-nested": "^10.5.1", + "jss-plugin-props-sort": "^10.5.1", + "jss-plugin-rule-value-function": "^10.5.1", + "jss-plugin-vendor-prefixer": "^10.5.1", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/system": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.2.tgz", + "integrity": "sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.11.3", + "csstype": "^2.5.2", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/types": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", + "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==", + "peerDependencies": { + "@types/react": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/utils": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.3.tgz", + "integrity": "sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0 || ^17.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + } + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, + "node_modules/@types/node": { + "version": "14.17.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.14.tgz", + "integrity": "sha512-rsAj2u8Xkqfc332iXV12SqIsjVi07H479bOP4q94NAcjzmAvapumEhuVIt53koEf7JFrpjgNKjBga5Pnn/GL8A==" + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.45.tgz", + "integrity": "sha512-YfhQ22Lah2e3CHPsb93tRwIGNiSwkuz1/blk4e6QrWS0jQzCSNbGLtOEYhPg02W0yGTTmpajp7dCTbBAMN3qsg==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "17.0.16", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.16.tgz", + "integrity": "sha512-DWcXf8EbMrO/gWnQU7Z88Ws/p16qxGpPyjTKTpmBSFKeE+HveVubqGO1CVK7FrwlWD5MuOcvh8gtd0/XO38NdQ==", + "dependencies": { + "@types/react": "^17" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.18", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.18.tgz", + "integrity": "sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", + "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/atomically": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/conf": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/conf/-/conf-7.1.2.tgz", + "integrity": "sha512-r8/HEoWPFn4CztjhMJaWNAe5n+gPUCSaJ0oufbqDLFKsA1V8JjAG7G+p0pgoDFAws9Bpk2VtVLLXqOBA7WxLeg==", + "dependencies": { + "ajv": "^6.12.2", + "atomically": "^1.3.1", + "debounce-fn": "^4.0.0", + "dot-prop": "^5.2.0", + "env-paths": "^2.2.0", + "json-schema-typed": "^7.0.3", + "make-dir": "^3.1.0", + "onetime": "^5.1.0", + "pkg-up": "^3.1.0", + "semver": "^7.3.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/css-vendor": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", + "dependencies": { + "@babel/runtime": "^7.8.3", + "is-in-browser": "^1.0.2" + } + }, + "node_modules/csstype": { + "version": "2.6.20", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", + "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" + }, + "node_modules/debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "dependencies": { + "mimic-fn": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dom-helpers/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==" + }, + "node_modules/jss": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz", + "integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "csstype": "^3.0.2", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/jss" + } + }, + "node_modules/jss-plugin-camel-case": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz", + "integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-default-unit": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz", + "integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-global": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz", + "integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-nested": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz", + "integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-props-sort": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz", + "integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-rule-value-function": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz", + "integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-vendor-prefixer": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz", + "integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.8", + "jss": "10.9.0" + } + }, + "node_modules/jss/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, + "node_modules/mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/mobx": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.5.0.tgz", + "integrity": "sha512-pHZ/cySF00FVENDWIDzJyoObFahK6Eg4d0papqm6d7yMkxWTZ/S/csqJX1A3PsYy4t5k3z2QnlwuCfMW5lSEwA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-7.4.0.tgz", + "integrity": "sha512-gbUwaKZK09SiAleTMxNMKs1MYKTpoIEWJLTLRIR/xnALuuHET8wkL8j1nbc1/6cDkOWVyKz/ReftILx0Pdh2PQ==", + "dependencies": { + "mobx-react-lite": "^3.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.1.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/mobx-react-lite": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.4.0.tgz", + "integrity": "sha512-bRuZp3C0itgLKHu/VNxi66DN/XVkQG7xtoBVWxpvC5FhAqbOCP21+nPhULjnzEqd7xBMybp6KwytdUpZKEgpIQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.1.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/popper.js": { + "version": "1.16.1-lts", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", + "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/react-select": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.3.2.tgz", + "integrity": "sha512-W6Irh7U6Ha7p5uQQ2ZnemoCQ8mcfgOtHfw3wuMzG6FAu0P+CYicgofSLOq97BhjMx8jS+h+wwWdCBeVVZ9VqlQ==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^5.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/typed-emitter": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-1.4.0.tgz", + "integrity": "sha512-weBmoo3HhpKGgLBOYwe8EB31CzDFuaK7CCL+axXhUYhn4jo6DSkHnbefboCF5i4DQ2aMFe0C/FdTWcPdObgHyg==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.16.7", @@ -215,7 +1518,8 @@ "@material-ui/types": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", - "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==" + "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==", + "requires": {} }, "@material-ui/utils": { "version": "4.11.3", @@ -748,7 +2052,8 @@ "mobx-react-lite": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.4.0.tgz", - "integrity": "sha512-bRuZp3C0itgLKHu/VNxi66DN/XVkQG7xtoBVWxpvC5FhAqbOCP21+nPhULjnzEqd7xBMybp6KwytdUpZKEgpIQ==" + "integrity": "sha512-bRuZp3C0itgLKHu/VNxi66DN/XVkQG7xtoBVWxpvC5FhAqbOCP21+nPhULjnzEqd7xBMybp6KwytdUpZKEgpIQ==", + "requires": {} }, "object-assign": { "version": "4.1.1", diff --git a/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap b/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap index 6fff5572d7..dab11ecc9b 100644 --- a/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap +++ b/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap @@ -12,9 +12,8 @@ exports[`extension special characters in page registrations renders 1`] = ` class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your diff --git a/src/features/__snapshots__/navigate-to-extension-page.test.tsx.snap b/src/features/__snapshots__/navigate-to-extension-page.test.tsx.snap index 9487506cf4..9dbfb50aea 100644 --- a/src/features/__snapshots__/navigate-to-extension-page.test.tsx.snap +++ b/src/features/__snapshots__/navigate-to-extension-page.test.tsx.snap @@ -12,9 +12,8 @@ exports[`navigate to extension page renders 1`] = ` class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your diff --git a/src/features/add-cluster/__snapshots__/navigation-using-application-menu.test.tsx.snap b/src/features/add-cluster/__snapshots__/navigation-using-application-menu.test.tsx.snap index 87957f581a..132ef5e0b0 100644 --- a/src/features/add-cluster/__snapshots__/navigation-using-application-menu.test.tsx.snap +++ b/src/features/add-cluster/__snapshots__/navigation-using-application-menu.test.tsx.snap @@ -12,9 +12,8 @@ exports[`add-cluster - navigation using application menu renders 1`] = ` class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -279,7 +278,7 @@ exports[`add-cluster - navigation using application menu when navigating to add file. diff --git a/src/features/application-update/__snapshots__/force-user-to-update-when-too-long-time-since-update-was-downloaded.test.ts.snap b/src/features/application-update/__snapshots__/force-user-to-update-when-too-long-time-since-update-was-downloaded.test.ts.snap index e6856ccc51..93808e443c 100644 --- a/src/features/application-update/__snapshots__/force-user-to-update-when-too-long-time-since-update-was-downloaded.test.ts.snap +++ b/src/features/application-update/__snapshots__/force-user-to-update-when-too-long-time-since-update-was-downloaded.test.ts.snap @@ -13,9 +13,8 @@ exports[`force user to update when too long since update was downloaded when app class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -233,9 +232,8 @@ exports[`force user to update when too long since update was downloaded when app class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -500,9 +498,8 @@ exports[`force user to update when too long since update was downloaded when app class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your diff --git a/src/features/application-update/__snapshots__/installing-update-using-topbar-button.test.tsx.snap b/src/features/application-update/__snapshots__/installing-update-using-topbar-button.test.tsx.snap index ec1935b053..f136545fc1 100644 --- a/src/features/application-update/__snapshots__/installing-update-using-topbar-button.test.tsx.snap +++ b/src/features/application-update/__snapshots__/installing-update-using-topbar-button.test.tsx.snap @@ -13,9 +13,8 @@ exports[`encourage user to update when sufficient time passed since update was d class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -233,9 +232,8 @@ exports[`encourage user to update when sufficient time passed since update was d class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your diff --git a/src/features/application-update/__snapshots__/installing-update-using-tray.test.ts.snap b/src/features/application-update/__snapshots__/installing-update-using-tray.test.ts.snap index fcb3eb4729..85da39aa60 100644 --- a/src/features/application-update/__snapshots__/installing-update-using-tray.test.ts.snap +++ b/src/features/application-update/__snapshots__/installing-update-using-tray.test.ts.snap @@ -13,9 +13,8 @@ exports[`installing update using tray when started renders 1`] = ` class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -215,9 +214,8 @@ exports[`installing update using tray when started when user checks for updates class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -417,9 +415,8 @@ exports[`installing update using tray when started when user checks for updates class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -619,9 +616,8 @@ exports[`installing update using tray when started when user checks for updates class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -839,9 +835,8 @@ exports[`installing update using tray when started when user checks for updates class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -1059,9 +1054,8 @@ exports[`installing update using tray when started when user checks for updates class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your diff --git a/src/features/application-update/__snapshots__/installing-update.test.ts.snap b/src/features/application-update/__snapshots__/installing-update.test.ts.snap index 394dc9c96c..8d589d432a 100644 --- a/src/features/application-update/__snapshots__/installing-update.test.ts.snap +++ b/src/features/application-update/__snapshots__/installing-update.test.ts.snap @@ -13,9 +13,8 @@ exports[`installing update when started renders 1`] = ` class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -215,9 +214,8 @@ exports[`installing update when started when user checks for updates renders 1`] class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -417,9 +415,8 @@ exports[`installing update when started when user checks for updates when new up class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -619,9 +616,8 @@ exports[`installing update when started when user checks for updates when new up class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -839,9 +835,8 @@ exports[`installing update when started when user checks for updates when new up class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your @@ -1059,9 +1054,8 @@ exports[`installing update when started when user checks for updates when no new class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your diff --git a/src/features/application-update/__snapshots__/periodical-checking-of-updates.test.ts.snap b/src/features/application-update/__snapshots__/periodical-checking-of-updates.test.ts.snap index faae797792..25483ce178 100644 --- a/src/features/application-update/__snapshots__/periodical-checking-of-updates.test.ts.snap +++ b/src/features/application-update/__snapshots__/periodical-checking-of-updates.test.ts.snap @@ -13,9 +13,8 @@ exports[`periodical checking of updates given updater is enabled and configurati class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your diff --git a/src/features/application-update/__snapshots__/selection-of-update-stability.test.ts.snap b/src/features/application-update/__snapshots__/selection-of-update-stability.test.ts.snap index a8629b21b6..6c0e7da36c 100644 --- a/src/features/application-update/__snapshots__/selection-of-update-stability.test.ts.snap +++ b/src/features/application-update/__snapshots__/selection-of-update-stability.test.ts.snap @@ -13,9 +13,8 @@ exports[`selection of update stability when started renders 1`] = ` class="items" >

- Welcome to OpenLens! + Welcome to some-product-name!

To get you started we have auto-detected your clusters in your diff --git a/src/features/application-update/analytics-for-installing-update.test.ts b/src/features/application-update/analytics-for-installing-update.test.ts index 7f451b0076..625022b040 100644 --- a/src/features/application-update/analytics-for-installing-update.test.ts +++ b/src/features/application-update/analytics-for-installing-update.test.ts @@ -16,9 +16,10 @@ import processCheckingForUpdatesInjectable from "../../main/application-update/c import type { DownloadPlatformUpdate } from "../../main/application-update/download-platform-update/download-platform-update.injectable"; import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable"; import quitAndInstallUpdateInjectable from "../../main/application-update/quit-and-install-update.injectable"; -import appVersionInjectable from "../../common/vars/app-version.injectable"; import periodicalCheckForUpdatesInjectable from "../../main/application-update/periodical-check-for-updates/periodical-check-for-updates.injectable"; import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time"; +import emitEventInjectable from "../../common/app-event-bus/emit-event.injectable"; +import getBuildVersionInjectable from "../../main/vars/build-version/get-build-version.injectable"; describe("analytics for installing update", () => { let builder: ApplicationBuilder; @@ -35,7 +36,7 @@ describe("analytics for installing update", () => { analyticsListenerMock = jest.fn(); builder.beforeApplicationStart(mainDi => { - mainDi.override(appVersionInjectable, () => "42.0.0"); + mainDi.override(getBuildVersionInjectable, () => () => "42.0.0"); checkForPlatformUpdatesMock = asyncFn(); @@ -51,6 +52,8 @@ describe("analytics for installing update", () => { mainDi.override(publishIsConfiguredInjectable, () => true); + mainDi.unoverride(emitEventInjectable); + const eventBus = mainDi.inject(appEventBusInjectable); eventBus.addListener(analyticsListenerMock); @@ -65,7 +68,6 @@ describe("analytics for installing update", () => { mainDi.permitSideEffects(periodicalCheckForUpdatesInjectable); await builder.render(); - }); it("sends event to analytics for being checked periodically", () => { diff --git a/src/features/application-update/downgrading-version-update.test.ts b/src/features/application-update/downgrading-version-update.test.ts index 0462bee3d4..8f56ebd15c 100644 --- a/src/features/application-update/downgrading-version-update.test.ts +++ b/src/features/application-update/downgrading-version-update.test.ts @@ -13,8 +13,8 @@ import checkForPlatformUpdatesInjectable from "../../main/application-update/che import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable"; import selectedUpdateChannelInjectable from "../../common/application-update/selected-update-channel/selected-update-channel.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; -import appVersionInjectable from "../../common/vars/app-version.injectable"; import { updateChannels } from "../../common/application-update/update-channels"; +import getBuildVersionInjectable from "../../main/vars/build-version/get-build-version.injectable"; describe("downgrading version update", () => { let applicationBuilder: ApplicationBuilder; @@ -40,6 +40,36 @@ describe("downgrading version update", () => { }); [ + { + updateChannel: updateChannels.latest, + appVersion: "4.0.0", + downgradeIsAllowed: false, + }, + { + updateChannel: updateChannels.beta, + appVersion: "4.0.0", + downgradeIsAllowed: false, + }, + { + updateChannel: updateChannels.alpha, + appVersion: "4.0.0", + downgradeIsAllowed: false, + }, + { + updateChannel: updateChannels.latest, + appVersion: "4.0.0-latest", + downgradeIsAllowed: false, + }, + { + updateChannel: updateChannels.beta, + appVersion: "4.0.0-latest", + downgradeIsAllowed: false, + }, + { + updateChannel: updateChannels.alpha, + appVersion: "4.0.0-latest", + downgradeIsAllowed: false, + }, { updateChannel: updateChannels.latest, appVersion: "4.0.0-beta", @@ -50,16 +80,21 @@ describe("downgrading version update", () => { appVersion: "4.0.0-beta", downgradeIsAllowed: false, }, - { - updateChannel: updateChannels.beta, - appVersion: "4.0.0-beta.1", - downgradeIsAllowed: false, - }, { updateChannel: updateChannels.alpha, appVersion: "4.0.0-beta", + downgradeIsAllowed: false, + }, + { + updateChannel: updateChannels.latest, + appVersion: "4.0.0-alpha", downgradeIsAllowed: true, }, + { + updateChannel: updateChannels.beta, + appVersion: "4.0.0-alpha", + downgradeIsAllowed: false, + }, { updateChannel: updateChannels.alpha, appVersion: "4.0.0-alpha", @@ -67,7 +102,7 @@ describe("downgrading version update", () => { }, ].forEach(({ appVersion, updateChannel, downgradeIsAllowed }) => { it(`given application version "${appVersion}" and update channel "${updateChannel.id}", when checking for updates, can${downgradeIsAllowed ? "": "not"} downgrade`, async () => { - mainDi.override(appVersionInjectable, () => appVersion); + mainDi.override(getBuildVersionInjectable, () => () => appVersion); await applicationBuilder.render(); diff --git a/src/features/application-update/installing-update.test.ts b/src/features/application-update/installing-update.test.ts index 7063fcfaa1..973095d72d 100644 --- a/src/features/application-update/installing-update.test.ts +++ b/src/features/application-update/installing-update.test.ts @@ -91,7 +91,7 @@ describe("installing update", () => { it("checks for updates", () => { expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith( expect.any(Object), - { allowDowngrade: true }, + { allowDowngrade: false }, ); }); diff --git a/src/features/application-update/selection-of-update-stability.test.ts b/src/features/application-update/selection-of-update-stability.test.ts index 56937ab0b4..d948573dcd 100644 --- a/src/features/application-update/selection-of-update-stability.test.ts +++ b/src/features/application-update/selection-of-update-stability.test.ts @@ -12,7 +12,7 @@ import type { CheckForPlatformUpdates } from "../../main/application-update/chec import checkForPlatformUpdatesInjectable from "../../main/application-update/check-for-platform-updates/check-for-platform-updates.injectable"; import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; -import type { UpdateChannel, UpdateChannelId } from "../../common/application-update/update-channels"; +import type { UpdateChannel, ReleaseChannel } from "../../common/application-update/update-channels"; import { updateChannels } from "../../common/application-update/update-channels"; import type { DownloadPlatformUpdate } from "../../main/application-update/download-platform-update/download-platform-update.injectable"; import downloadPlatformUpdateInjectable from "../../main/application-update/download-platform-update/download-platform-update.injectable"; @@ -21,8 +21,8 @@ import type { IComputedValue } from "mobx"; import setUpdateOnQuitInjectable from "../../main/electron-app/features/set-update-on-quit.injectable"; import showInfoNotificationInjectable from "../../renderer/components/notifications/show-info-notification.injectable"; import processCheckingForUpdatesInjectable from "../../main/application-update/check-for-updates/process-checking-for-updates.injectable"; -import appVersionInjectable from "../../common/vars/app-version.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; +import getBuildVersionInjectable from "../../main/vars/build-version/get-build-version.injectable"; describe("selection of update stability", () => { let builder: ApplicationBuilder; @@ -89,7 +89,7 @@ describe("selection of update stability", () => { describe('given update channel "alpha" is selected, when checking for updates', () => { let selectedUpdateChannel: { value: IComputedValue; - setValue: (channelId: UpdateChannelId) => void; + setValue: (channelId: ReleaseChannel) => void; }; beforeEach(() => { @@ -105,7 +105,7 @@ describe("selection of update stability", () => { it('checks updates from update channel "alpha"', () => { expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith( updateChannels.alpha, - { allowDowngrade: true }, + { allowDowngrade: false }, ); }); @@ -132,7 +132,7 @@ describe("selection of update stability", () => { it('checks updates from update channel "beta"', () => { expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith( updateChannels.beta, - { allowDowngrade: true }, + { allowDowngrade: false }, ); }); @@ -159,7 +159,7 @@ describe("selection of update stability", () => { it('finally checks updates from update channel "latest"', () => { expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith( updateChannels.latest, - { allowDowngrade: true }, + { allowDowngrade: false }, ); }); @@ -180,7 +180,7 @@ describe("selection of update stability", () => { describe('given update channel "beta" is selected', () => { let selectedUpdateChannel: { value: IComputedValue; - setValue: (channelId: UpdateChannelId) => void; + setValue: (channelId: ReleaseChannel) => void; }; beforeEach(() => { @@ -231,16 +231,12 @@ describe("selection of update stability", () => { }); it("given valid update channel selection is stored, when checking for updates, checks for updates from the update channel", async () => { - builder.beforeApplicationStart((mainDi) => { - // TODO: Switch to more natural way of setting initial value - // TODO: UserStore is currently responsible for getting and setting initial value - const selectedUpdateChannel = mainDi.inject(selectedUpdateChannelInjectable); - - selectedUpdateChannel.setValue(updateChannels.beta.id); - }); - await builder.render(); + // TODO: Switch to more natural way of setting initial value + // TODO: UserStore is currently responsible for getting and setting initial value + mainDi.inject(selectedUpdateChannelInjectable).setValue(updateChannels.beta.id); + const processCheckingForUpdates = mainDi.inject(processCheckingForUpdatesInjectable); processCheckingForUpdates("irrelevant"); @@ -249,16 +245,12 @@ describe("selection of update stability", () => { }); it("given invalid update channel selection is stored, when checking for updates, checks for updates from the update channel", async () => { - builder.beforeApplicationStart((mainDi) => { - // TODO: Switch to more natural way of setting initial value - // TODO: UserStore is currently responsible for getting and setting initial value - const selectedUpdateChannel = mainDi.inject(selectedUpdateChannelInjectable); - - selectedUpdateChannel.setValue("something-invalid" as UpdateChannelId); - }); - await builder.render(); + // TODO: Switch to more natural way of setting initial value + // TODO: UserStore is currently responsible for getting and setting initial value + mainDi.inject(selectedUpdateChannelInjectable).setValue("something-invalid" as ReleaseChannel); + const processCheckingForUpdates = mainDi.inject(processCheckingForUpdatesInjectable); processCheckingForUpdates("irrelevant"); @@ -268,7 +260,7 @@ describe("selection of update stability", () => { it('given no update channel selection is stored and currently using stable release, when user checks for updates, checks for updates from "latest" update channel by default', async () => { builder.beforeApplicationStart((mainDi) => { - mainDi.override(appVersionInjectable, () => "1.0.0"); + mainDi.override(getBuildVersionInjectable, () => () => "1.0.0"); }); await builder.render(); @@ -279,13 +271,13 @@ describe("selection of update stability", () => { expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith( updateChannels.latest, - { allowDowngrade: true }, + { allowDowngrade: false }, ); }); it('given no update channel selection is stored and currently using alpha release, when checking for updates, checks for updates from "alpha" channel', async () => { builder.beforeApplicationStart((mainDi) => { - mainDi.override(appVersionInjectable, () => "1.0.0-alpha"); + mainDi.override(getBuildVersionInjectable, () => () => "1.0.0-alpha"); }); await builder.render(); @@ -299,7 +291,7 @@ describe("selection of update stability", () => { it('given no update channel selection is stored and currently using beta release, when checking for updates, checks for updates from "beta" channel', async () => { builder.beforeApplicationStart((mainDi) => { - mainDi.override(appVersionInjectable, () => "1.0.0-beta"); + mainDi.override(getBuildVersionInjectable, () => () => "1.0.0-beta"); }); await builder.render(); @@ -312,18 +304,12 @@ describe("selection of update stability", () => { }); it("given update channel selection is stored and currently using prerelease, when checking for updates, checks for updates from stored channel", async () => { - builder.beforeApplicationStart((mainDi) => { - mainDi.override(appVersionInjectable, () => "1.0.0-alpha"); - - // TODO: Switch to more natural way of setting initial value - // TODO: UserStore is currently responsible for getting and setting initial value - const selectedUpdateChannel = mainDi.inject(selectedUpdateChannelInjectable); - - selectedUpdateChannel.setValue(updateChannels.beta.id); - }); - await builder.render(); + // TODO: Switch to more natural way of setting initial value + // TODO: UserStore is currently responsible for getting and setting initial value + mainDi.inject(selectedUpdateChannelInjectable).setValue(updateChannels.beta.id); + const processCheckingForUpdates = mainDi.inject(processCheckingForUpdatesInjectable); processCheckingForUpdates("irrelevant"); diff --git a/src/features/cluster/__snapshots__/order-of-sidebar-items.test.tsx.snap b/src/features/cluster/__snapshots__/order-of-sidebar-items.test.tsx.snap index b9d854220c..7f76811494 100644 --- a/src/features/cluster/__snapshots__/order-of-sidebar-items.test.tsx.snap +++ b/src/features/cluster/__snapshots__/order-of-sidebar-items.test.tsx.snap @@ -39,7 +39,7 @@ exports[`cluster - order of sidebar items when rendered renders 1`] = ` > @@ -87,9 +85,7 @@ exports[`cluster - order of sidebar items when rendered renders 1`] = ` list - + Config @@ -100,17 +96,15 @@ exports[`cluster - order of sidebar items when rendered renders 1`] = ` data-testid="sidebar-item-some-parent-id" > @@ -178,9 +170,7 @@ exports[`cluster - order of sidebar items when rendered renders 1`] = ` storage - + Storage @@ -191,13 +181,11 @@ exports[`cluster - order of sidebar items when rendered renders 1`] = ` data-testid="sidebar-item-some-another-parent-id" > - + Some another parent @@ -208,7 +196,7 @@ exports[`cluster - order of sidebar items when rendered renders 1`] = ` data-testid="sidebar-item-helm" > @@ -256,9 +242,7 @@ exports[`cluster - order of sidebar items when rendered renders 1`] = ` security - + Access Control @@ -269,7 +253,7 @@ exports[`cluster - order of sidebar items when rendered renders 1`] = ` data-testid="sidebar-item-custom-resources" > - + Some other parent @@ -619,7 +599,7 @@ exports[`cluster - order of sidebar items when rendered when parent is expanded > @@ -667,9 +645,7 @@ exports[`cluster - order of sidebar items when rendered when parent is expanded list - + Config @@ -680,17 +656,15 @@ exports[`cluster - order of sidebar items when rendered when parent is expanded data-testid="sidebar-item-some-parent-id" >