diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml index eafc6befc0..b010600c6c 100644 --- a/.github/workflows/check-docs.yml +++ b/.github/workflows/check-docs.yml @@ -8,7 +8,7 @@ jobs: if: ${{ contains(github.event.pull_request.labels.*.name, 'area/documentation') }} strategy: matrix: - node-version: [12.x] + node-version: [14.x] steps: - name: Checkout Release from lens uses: actions/checkout@v2 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index e847eab0f5..18ec977859 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x] + node-version: [14.x] steps: - name: Checkout Release from lens uses: actions/checkout@v2 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index afbcc74367..84b8020605 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x] + node-version: [14.x] steps: - name: Set up Python 3.7 uses: actions/setup-python@v2 @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x] + node-version: [14.x] needs: verify-docs steps: - name: Set up Python 3.7 diff --git a/.github/workflows/mkdocs-manual.yml b/.github/workflows/mkdocs-manual.yml index 632fccfa7b..a0695d0e7d 100644 --- a/.github/workflows/mkdocs-manual.yml +++ b/.github/workflows/mkdocs-manual.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x] + node-version: [14.x] steps: - name: Set up Python 3.7 uses: actions/setup-python@v2 @@ -29,7 +29,7 @@ jobs: with: fetch-depth: 0 ref: '${{ github.event.inputs.version }}' - + - name: Using Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: @@ -53,15 +53,15 @@ jobs: rm -fr ./docs/clusters ./docs/contributing ./docs/faq ./docs/getting-started ./docs/helm ./docs/support ./docs/supporting sed -i '/Protocol Handlers/d' ./mkdocs.yml sed -i '/IPC/d' ./mkdocs.yml - sed -i 's#../../clusters/adding-clusters.md#https://docs.k8slens.dev/latest/clusters/adding-clusters/#g' ./docs/extensions/get-started/your-first-extension.md + sed -i 's#../../clusters/adding-clusters.md#https://docs.k8slens.dev/latest/clusters/adding-clusters/#g' ./docs/extensions/get-started/your-first-extension.md sed -i 's#clusters/adding-clusters.md#https://docs.k8slens.dev/latest/clusters/adding-clusters/#g' ./docs/README.md - sed -i 's#../../contributing/README.md#https://docs.k8slens.dev/latest/contributing/#g' ./docs/extensions/guides/generator.md + sed -i 's#../../contributing/README.md#https://docs.k8slens.dev/latest/contributing/#g' ./docs/extensions/guides/generator.md - name: git config run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" - + - name: mkdocs deploy new release run: | mike deploy --push --force ${{ github.event.inputs.version }} diff --git a/.github/workflows/publish-master-npm.yml b/.github/workflows/publish-master-npm.yml index 2be9721d3e..25e434c0f4 100644 --- a/.github/workflows/publish-master-npm.yml +++ b/.github/workflows/publish-master-npm.yml @@ -11,7 +11,7 @@ jobs: ${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'area/extension') }} strategy: matrix: - node-version: [12.x] + node-version: [14.x] steps: - name: Checkout Release uses: actions/checkout@v2 diff --git a/.github/workflows/publish-release-npm.yml b/.github/workflows/publish-release-npm.yml index 5a8bd9aff5..0bfc06a106 100644 --- a/.github/workflows/publish-release-npm.yml +++ b/.github/workflows/publish-release-npm.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x] + node-version: [14.x] steps: - name: Checkout Release uses: actions/checkout@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2ce53e59e2..3058f28fe0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-16.04, macos-10.15, windows-2019] - node-version: [12.x] + node-version: [14.x] steps: - name: Checkout Release from lens uses: actions/checkout@v2 diff --git a/.yarnrc b/.yarnrc index f11de8a2a9..9f7a2e491b 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "9.4.4" +target "12.0.15" runtime "electron" diff --git a/build/download_kubectl.ts b/build/download_kubectl.ts index 819b73dd02..4400a5f5d0 100644 --- a/build/download_kubectl.ts +++ b/build/download_kubectl.ts @@ -25,7 +25,7 @@ import md5File from "md5-file"; import requestPromise from "request-promise-native"; import { ensureDir, pathExists } from "fs-extra"; import path from "path"; -import { noop } from "../src/common/utils"; +import { noop } from "lodash"; class KubectlDownloader { public kubectlVersion: string; @@ -117,7 +117,7 @@ class KubectlDownloader { } const downloadVersion = packageInfo.config.bundledKubectlVersion; -const baseDir = path.join(process.env.INIT_CWD, "binaries", "client"); +const baseDir = path.join(__dirname, "..", "binaries", "client"); const downloads = [ { platform: "linux", arch: "amd64", target: path.join(baseDir, "linux", "x64", "kubectl") }, { platform: "darwin", arch: "amd64", target: path.join(baseDir, "darwin", "x64", "kubectl") }, diff --git a/integration/__tests__/app-preferences.tests.ts b/integration/__tests__/app-preferences.tests.ts new file mode 100644 index 0000000000..4afa8cfd96 --- /dev/null +++ b/integration/__tests__/app-preferences.tests.ts @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE + namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the + TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube + cluster and vice versa. +*/ +import type { Page } from "playwright"; +import * as utils from "../helpers/utils"; + +describe("preferences page tests", () => { + let window: Page, cleanup: () => Promise; + + beforeEach(async () => { + ({ window, cleanup } = await utils.start()); + await utils.clickWelcomeButton(window); + await window.keyboard.press("Meta+,"); + }, 10*60*1000); + + afterEach(async () => { + await cleanup(); + }, 10*60*1000); + + it('shows "preferences" and can navigate through the tabs', async () => { + const pages = [ + { + id: "application", + header: "Application", + }, + { + id: "proxy", + header: "Proxy", + }, + { + id: "kubernetes", + header: "Kubernetes", + }, + ]; + + for (const { id, header } of pages) { + await window.click(`[data-testid=${id}-tab]`); + await window.waitForSelector(`[data-testid=${id}-header] >> text=${header}`); + } + }, 10*60*1000); + + it("ensures helm repos", async () => { + await window.click("[data-testid=kubernetes-tab]"); + await window.waitForSelector("[data-testid=repository-name]", { + timeout: 100_000, + }); + await window.click("#HelmRepoSelect"); + await window.waitForSelector("div.Select__option"); + }, 10*60*1000); +}); diff --git a/integration/__tests__/app.tests.ts b/integration/__tests__/app.tests.ts deleted file mode 100644 index 5bf707fb71..0000000000 --- a/integration/__tests__/app.tests.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright (c) 2021 OpenLens Authors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE - namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the - TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube - cluster and vice versa. -*/ -import type { Application } from "spectron"; -import * as utils from "../helpers/utils"; -import { listHelmRepositories } from "../helpers/utils"; -import { fail } from "assert"; - -jest.setTimeout(2 * 60 * 1000); // 2 minutes so that we can get better errors from spectron - -// FIXME (!): improve / simplify all css-selectors + use [data-test-id="some-id"] (already used in some tests below) -describe("Lens integration tests", () => { - let app: Application; - - describe("app start", () => { - utils.beforeAllWrapped(async () => { - app = await utils.setup(); - }); - - utils.afterAllWrapped(() => utils.tearDown(app)); - - it('shows "add cluster"', async () => { - await app.electron.ipcRenderer.send("test-menu-item-click", "File", "Add Cluster"); - await app.client.waitUntilTextExists("h2", "Add Clusters from Kubeconfig"); - }); - - describe("preferences page", () => { - it('shows "preferences"', async () => { - const appName: string = process.platform === "darwin" ? "OpenLens" : "File"; - - await app.electron.ipcRenderer.send("test-menu-item-click", appName, "Preferences"); - await app.client.waitUntilTextExists("[data-testid=application-header]", "Application"); - }); - - it.each([ - ["application", "Application"], - ["proxy", "Proxy"], - ["kubernetes", "Kubernetes"], - ])("Can click the %s tab and see the %s header", async (tab, header) => { - await app.client.click(`[data-testid=${tab}-tab]`); - await app.client.waitUntilTextExists(`[data-testid=${tab}-header]`, header); - }); - - it("ensures helm repos", async () => { - const repos = await listHelmRepositories(); - - if (repos.length === 0) { - fail("Lens failed to add any repositories"); - } - - await app.client.click("[data-testid=kubernetes-tab]"); - await app.client.waitUntilTextExists("[data-testid=repository-name]", repos[0].name); // wait for the helm-cli to fetch the repo(s) - await app.client.click("#HelmRepoSelect"); // click the repo select to activate the drop-down - await app.client.waitUntilTextExists("div.Select__option", ""); // wait for at least one option to appear (any text) - }); - }); - - it.skip('quits Lens"', async () => { - await app.client.keys(["Meta", "Q"]); - await app.client.keys("Meta"); - }); - }); -}); diff --git a/integration/__tests__/cluster-pages.tests.ts b/integration/__tests__/cluster-pages.tests.ts index 3ffb922dea..8dae67dcaf 100644 --- a/integration/__tests__/cluster-pages.tests.ts +++ b/integration/__tests__/cluster-pages.tests.ts @@ -25,450 +25,439 @@ TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube cluster and vice versa. */ -import type { Application } from "spectron"; import * as utils from "../helpers/utils"; -import { minikubeReady, waitForMinikubeDashboard } from "../helpers/minikube"; -import { exec } from "child_process"; -import * as util from "util"; +import { minikubeReady } from "../helpers/minikube"; +import type { Frame, Page } from "playwright"; -export const promiseExec = util.promisify(exec); +const TEST_NAMESPACE = "integration-tests"; -jest.setTimeout(2 * 60 * 1000); // 2 minutes so that we can get better errors from spectron +function getSidebarSelectors(itemId: string) { + const root = `.SidebarItem[data-test-id="${itemId}"]`; -// FIXME (!): improve / simplify all css-selectors + use [data-test-id="some-id"] (already used in some tests below) -describe("Lens cluster pages", () => { - const TEST_NAMESPACE = "integration-tests"; - const BACKSPACE = "\uE003"; - let app: Application; - const ready = minikubeReady(TEST_NAMESPACE); + return { + expandSubMenu: `${root} .nav-item`, + subMenuLink: (href: string) => `.Sidebar .sub-menu a[href^="/${href}"]`, + }; +} - utils.describeIf(ready)("test common pages", () => { - let clusterAdded = false; - const addCluster = async () => { - await app.client.waitUntilTextExists("div", "Catalog"); - await waitForMinikubeDashboard(app); - await app.client.click('a[href="/nodes"]'); - await app.client.waitUntilTextExists("div.TableCell", "Ready"); - }; +function getLoadedSelector(page: CommonPage): string { + if (page.expectedText) { + return `${page.expectedSelector} >> text='${page.expectedText}'`; + } - describe("cluster add", () => { - utils.beforeAllWrapped(async () => { - app = await utils.setup(); - }); + return page.expectedSelector; +} - utils.afterAllWrapped(() => utils.tearDown(app)); +interface CommonPage { + name: string; + href: string; + expectedSelector: string; + expectedText?: string; +} - it("allows to add a cluster", async () => { - await addCluster(); - clusterAdded = true; - }); - }); +interface TopPageTest { + page: CommonPage; +} - const appStartAddCluster = async () => { - if (clusterAdded) { - app = await utils.setup(); - await addCluster(); +interface SubPageTest { + drawerId: string; + pages: CommonPage[]; +} + +type CommonPageTest = TopPageTest | SubPageTest; + +function isTopPageTest(test: CommonPageTest): test is TopPageTest { + return typeof (test as any).page === "object"; +} + +const commonPageTests: CommonPageTest[] = [{ + page: { + name: "Cluster", + href: "cluster", + expectedSelector: "div.ClusterOverview div.label", + expectedText: "CPU" + } +}, +{ + page: { + name: "Nodes", + href: "nodes", + expectedSelector: "h5.title", + expectedText: "Nodes" + } +}, +{ + drawerId: "workloads", + pages: [{ + name: "Overview", + href: "workloads", + expectedSelector: "h5.box", + expectedText: "Overview" + }, + { + name: "Pods", + href: "pods", + expectedSelector: "h5.title", + expectedText: "Pods" + }, + { + name: "Deployments", + href: "deployments", + expectedSelector: "h5.title", + expectedText: "Deployments" + }, + { + name: "DaemonSets", + href: "daemonsets", + expectedSelector: "h5.title", + expectedText: "Daemon Sets" + }, + { + name: "StatefulSets", + href: "statefulsets", + expectedSelector: "h5.title", + expectedText: "Stateful Sets" + }, + { + name: "ReplicaSets", + href: "replicasets", + expectedSelector: "h5.title", + expectedText: "Replica Sets" + }, + { + name: "Jobs", + href: "jobs", + expectedSelector: "h5.title", + expectedText: "Jobs" + }, + { + name: "CronJobs", + href: "cronjobs", + expectedSelector: "h5.title", + expectedText: "Cron Jobs" + }] +}, +{ + drawerId: "config", + pages: [{ + name: "ConfigMaps", + href: "configmaps", + expectedSelector: "h5.title", + expectedText: "Config Maps" + }, + { + name: "Secrets", + href: "secrets", + expectedSelector: "h5.title", + expectedText: "Secrets" + }, + { + name: "Resource Quotas", + href: "resourcequotas", + expectedSelector: "h5.title", + expectedText: "Resource Quotas" + }, + { + name: "Limit Ranges", + href: "limitranges", + expectedSelector: "h5.title", + expectedText: "Limit Ranges" + }, + { + name: "HPA", + href: "hpa", + expectedSelector: "h5.title", + expectedText: "Horizontal Pod Autoscalers" + }, + { + name: "Pod Disruption Budgets", + href: "poddisruptionbudgets", + expectedSelector: "h5.title", + expectedText: "Pod Disruption Budgets" + }] +}, +{ + drawerId: "networks", + pages: [{ + name: "Services", + href: "services", + expectedSelector: "h5.title", + expectedText: "Services" + }, + { + name: "Endpoints", + href: "endpoints", + expectedSelector: "h5.title", + expectedText: "Endpoints" + }, + { + name: "Ingresses", + href: "ingresses", + expectedSelector: "h5.title", + expectedText: "Ingresses" + }, + { + name: "Network Policies", + href: "network-policies", + expectedSelector: "h5.title", + expectedText: "Network Policies" + }] +}, +{ + drawerId: "storage", + pages: [{ + name: "Persistent Volume Claims", + href: "persistent-volume-claims", + expectedSelector: "h5.title", + expectedText: "Persistent Volume Claims" + }, + { + name: "Persistent Volumes", + href: "persistent-volumes", + expectedSelector: "h5.title", + expectedText: "Persistent Volumes" + }, + { + name: "Storage Classes", + href: "storage-classes", + expectedSelector: "h5.title", + expectedText: "Storage Classes" + }] +}, +{ + page: { + name: "Namespaces", + href: "namespaces", + expectedSelector: "h5.title", + expectedText: "Namespaces" + } +}, +{ + page: { + name: "Events", + href: "events", + expectedSelector: "h5.title", + expectedText: "Events" + } +}, +{ + drawerId: "apps", + pages: [{ + name: "Charts", + href: "apps/charts", + expectedSelector: "div.HelmCharts input", + }, + { + name: "Releases", + href: "apps/releases", + expectedSelector: "h5.title", + expectedText: "Releases" + }] +}, +{ + drawerId: "users", + pages: [{ + name: "Service Accounts", + href: "service-accounts", + expectedSelector: "h5.title", + expectedText: "Service Accounts" + }, + { + name: "Roles", + href: "roles", + expectedSelector: "h5.title", + expectedText: "Roles" + }, + { + name: "Cluster Roles", + href: "cluster-roles", + expectedSelector: "h5.title", + expectedText: "Cluster Roles" + }, + { + name: "Role Bindings", + href: "role-bindings", + expectedSelector: "h5.title", + expectedText: "Role Bindings" + }, + { + name: "Cluster Role Bindings", + href: "cluster-role-bindings", + expectedSelector: "h5.title", + expectedText: "Cluster Role Bindings" + }, + { + name: "Pod Security Policies", + href: "pod-security-policies", + expectedSelector: "h5.title", + expectedText: "Pod Security Policies" + }] +}, +{ + drawerId: "custom-resources", + pages: [{ + name: "Definitions", + href: "crd/definitions", + expectedSelector: "h5.title", + expectedText: "Custom Resources" + }] +}]; + +utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { + let window: Page, cleanup: () => Promise, frame: Frame; + + beforeEach(async () => { + ({ window, cleanup } = await utils.start()); + await utils.clickWelcomeButton(window); + + frame = await utils.lauchMinikubeClusterFromCatalog(window); + }, 10*60*1000); + + afterEach(async () => { + await cleanup(); + }, 10*60*1000); + + it("should navigate around common cluster pages", async () => { + for (const test of commonPageTests) { + if (isTopPageTest(test)) { + const { href, expectedText, expectedSelector } = test.page; + const menuButton = await frame.waitForSelector(`a[href^="/${href}"]`); + + await menuButton.click(); + await frame.waitForSelector(`${expectedSelector} >> text='${expectedText}'`); + + continue; } - }; - function getSidebarSelectors(itemId: string) { - const root = `.SidebarItem[data-test-id="${itemId}"]`; + const { drawerId, pages } = test; + const selectors = getSidebarSelectors(drawerId); + const mainPageSelector = `${selectors.subMenuLink(pages[0].href)} >> text='${pages[0].name}'`; - return { - expandSubMenu: `${root} .nav-item`, - subMenuLink: (href: string) => `.Sidebar .sub-menu a[href^="/${href}"]`, - }; + await frame.click(selectors.expandSubMenu); + await frame.waitForSelector(mainPageSelector); + + for (const page of pages) { + const subPageButton = await frame.waitForSelector(selectors.subMenuLink(page.href)); + + await subPageButton.click(); + await frame.waitForSelector(getLoadedSelector(page)); + } + + await frame.click(selectors.expandSubMenu); + await frame.waitForSelector(mainPageSelector, { state: "hidden" }); + } + }, 10*60*1000); + + it("show logs and highlight the log search entries", async () => { + await frame.click(`a[href="/workloads"]`); + await frame.click(`a[href="/pods"]`); + + const namespacesSelector = await frame.waitForSelector(".NamespaceSelect"); + + await namespacesSelector.click(); + await namespacesSelector.type("kube-system"); + await namespacesSelector.press("Enter"); + await namespacesSelector.click(); + + const kubeApiServerRow = await frame.waitForSelector("div.TableCell >> text=kube-apiserver"); + + await kubeApiServerRow.click(); + await frame.waitForSelector(".Drawer", { state: "visible" }); + + const logButton = await frame.waitForSelector("ul.KubeObjectMenu li.MenuItem i.Icon span[data-icon-name='subject']"); + + await logButton.click(); + + // Check if controls are available + await frame.waitForSelector(".LogList .VirtualList"); + await frame.waitForSelector(".LogResourceSelector"); + + const logSearchInput = await frame.waitForSelector(".LogSearch .SearchInput input"); + + await logSearchInput.type(":"); + await frame.waitForSelector(".LogList .list span.active"); + + const showTimestampsButton = await frame.waitForSelector(".LogControls .show-timestamps"); + + await showTimestampsButton.click(); + + const showPreviousButton = await frame.waitForSelector(".LogControls .show-previous"); + + await showPreviousButton.click(); + }, 10*60*1000); + + it("should show the default namespaces", async () => { + await frame.click('a[href="/namespaces"]'); + await frame.waitForSelector("div.TableCell >> text='default'"); + await frame.waitForSelector("div.TableCell >> text='kube-system'"); + }, 10*60*1000); + + it(`should create the ${TEST_NAMESPACE} and a pod in the namespace`, async () => { + await frame.click('a[href="/namespaces"]'); + await frame.click("button.add-button"); + await frame.waitForSelector("div.AddNamespaceDialog >> text='Create Namespace'"); + + const namespaceNameInput = await frame.waitForSelector(".AddNamespaceDialog input"); + + await namespaceNameInput.type(TEST_NAMESPACE); + await namespaceNameInput.press("Enter"); + + await frame.waitForSelector(`div.TableCell >> text=${TEST_NAMESPACE}`); + + if ((await frame.innerText(`a[href^="/workloads"] .expand-icon`)) === "keyboard_arrow_down") { + await frame.click(`a[href^="/workloads"]`); } - describe("cluster pages", () => { - utils.beforeAllWrapped(appStartAddCluster); - utils.afterAllWrapped(() => utils.tearDown(app)); + await frame.click(`a[href^="/pods"]`); - const tests: { - drawer?: string - drawerId?: string - pages: { - name: string, - href: string, - expectedSelector: string, - expectedText: string - }[] - }[] = [{ - drawer: "", - drawerId: "", - pages: [{ - name: "Cluster", - href: "cluster", - expectedSelector: "div.ClusterOverview div.label", - expectedText: "CPU" - }] - }, - { - drawer: "", - drawerId: "", - pages: [{ - name: "Nodes", - href: "nodes", - expectedSelector: "h5.title", - expectedText: "Nodes" - }] - }, - { - drawer: "Workloads", - drawerId: "workloads", - pages: [{ - name: "Overview", - href: "workloads", - expectedSelector: "h5.box", - expectedText: "Overview" - }, - { - name: "Pods", - href: "pods", - expectedSelector: "h5.title", - expectedText: "Pods" - }, - { - name: "Deployments", - href: "deployments", - expectedSelector: "h5.title", - expectedText: "Deployments" - }, - { - name: "DaemonSets", - href: "daemonsets", - expectedSelector: "h5.title", - expectedText: "Daemon Sets" - }, - { - name: "StatefulSets", - href: "statefulsets", - expectedSelector: "h5.title", - expectedText: "Stateful Sets" - }, - { - name: "ReplicaSets", - href: "replicasets", - expectedSelector: "h5.title", - expectedText: "Replica Sets" - }, - { - name: "Jobs", - href: "jobs", - expectedSelector: "h5.title", - expectedText: "Jobs" - }, - { - name: "CronJobs", - href: "cronjobs", - expectedSelector: "h5.title", - expectedText: "Cron Jobs" - }] - }, - { - drawer: "Configuration", - drawerId: "config", - pages: [{ - name: "ConfigMaps", - href: "configmaps", - expectedSelector: "h5.title", - expectedText: "Config Maps" - }, - { - name: "Secrets", - href: "secrets", - expectedSelector: "h5.title", - expectedText: "Secrets" - }, - { - name: "Resource Quotas", - href: "resourcequotas", - expectedSelector: "h5.title", - expectedText: "Resource Quotas" - }, - { - name: "Limit Ranges", - href: "limitranges", - expectedSelector: "h5.title", - expectedText: "Limit Ranges" - }, - { - name: "HPA", - href: "hpa", - expectedSelector: "h5.title", - expectedText: "Horizontal Pod Autoscalers" - }, - { - name: "Pod Disruption Budgets", - href: "poddisruptionbudgets", - expectedSelector: "h5.title", - expectedText: "Pod Disruption Budgets" - }] - }, - { - drawer: "Network", - drawerId: "networks", - pages: [{ - name: "Services", - href: "services", - expectedSelector: "h5.title", - expectedText: "Services" - }, - { - name: "Endpoints", - href: "endpoints", - expectedSelector: "h5.title", - expectedText: "Endpoints" - }, - { - name: "Ingresses", - href: "ingresses", - expectedSelector: "h5.title", - expectedText: "Ingresses" - }, - { - name: "Network Policies", - href: "network-policies", - expectedSelector: "h5.title", - expectedText: "Network Policies" - }] - }, - { - drawer: "Storage", - drawerId: "storage", - pages: [{ - name: "Persistent Volume Claims", - href: "persistent-volume-claims", - expectedSelector: "h5.title", - expectedText: "Persistent Volume Claims" - }, - { - name: "Persistent Volumes", - href: "persistent-volumes", - expectedSelector: "h5.title", - expectedText: "Persistent Volumes" - }, - { - name: "Storage Classes", - href: "storage-classes", - expectedSelector: "h5.title", - expectedText: "Storage Classes" - }] - }, - { - drawer: "", - drawerId: "", - pages: [{ - name: "Namespaces", - href: "namespaces", - expectedSelector: "h5.title", - expectedText: "Namespaces" - }] - }, - { - drawer: "", - drawerId: "", - pages: [{ - name: "Events", - href: "events", - expectedSelector: "h5.title", - expectedText: "Events" - }] - }, - { - drawer: "Apps", - drawerId: "apps", - pages: [{ - name: "Charts", - href: "apps/charts", - expectedSelector: "div.HelmCharts input", - expectedText: "" - }, - { - name: "Releases", - href: "apps/releases", - expectedSelector: "h5.title", - expectedText: "Releases" - }] - }, - { - drawer: "Access Control", - drawerId: "users", - pages: [{ - name: "Service Accounts", - href: "service-accounts", - expectedSelector: "h5.title", - expectedText: "Service Accounts" - }, - { - name: "Role Bindings", - href: "role-bindings", - expectedSelector: "h5.title", - expectedText: "Role Bindings" - }, - { - name: "Roles", - href: "roles", - expectedSelector: "h5.title", - expectedText: "Roles" - }, - { - name: "Pod Security Policies", - href: "pod-security-policies", - expectedSelector: "h5.title", - expectedText: "Pod Security Policies" - }] - }, - { - drawer: "Custom Resources", - drawerId: "custom-resources", - pages: [{ - name: "Definitions", - href: "crd/definitions", - expectedSelector: "h5.title", - expectedText: "Custom Resources" - }] - }]; + const namespacesSelector = await frame.waitForSelector(".NamespaceSelect"); - tests.forEach(({ drawer = "", drawerId = "", pages }) => { - const selectors = getSidebarSelectors(drawerId); + await namespacesSelector.click(); + await namespacesSelector.type(TEST_NAMESPACE); + await namespacesSelector.press("Enter"); + await namespacesSelector.click(); - if (drawer !== "") { - it(`shows ${drawer} drawer`, async () => { - expect(clusterAdded).toBe(true); - await app.client.click(selectors.expandSubMenu); - await app.client.waitUntilTextExists(selectors.subMenuLink(pages[0].href), pages[0].name); - }); + await frame.click(".Icon.new-dock-tab"); - pages.forEach(({ name, href, expectedSelector, expectedText }) => { - it(`shows ${drawer}->${name} page`, async () => { - expect(clusterAdded).toBe(true); - await app.client.click(selectors.subMenuLink(href)); - await app.client.waitUntilTextExists(expectedSelector, expectedText); - }); - }); - - it(`hides ${drawer} drawer`, async () => { - expect(clusterAdded).toBe(true); - await app.client.click(selectors.expandSubMenu); - await expect(app.client.waitUntilTextExists(selectors.subMenuLink(pages[0].href), pages[0].name, 100)).rejects.toThrow(); - }); - } else { - const { href, name, expectedText, expectedSelector } = pages[0]; - - it(`shows page ${name}`, async () => { - expect(clusterAdded).toBe(true); - await app.client.click(`a[href^="/${href}"]`); - await app.client.waitUntilTextExists(expectedSelector, expectedText); - }); + try { + await frame.click("li.MenuItem.create-resource-tab", { + // NOTE: the following shouldn't be required, but is because without it a TypeError is thrown + // see: https://github.com/microsoft/playwright/issues/8229 + position: { + y: 0, + x: 0, } }); - }); + } catch (error) { + console.log(error); + await frame.waitForTimeout(100_000); + } - describe("viewing pod logs", () => { - utils.beforeEachWrapped(appStartAddCluster); - utils.afterEachWrapped(() => utils.tearDown(app)); + const inputField = await frame.waitForSelector(".CreateResource div.react-monaco-editor-container"); - it(`shows a log for a pod`, async () => { - expect(clusterAdded).toBe(true); - // Go to Pods page - await app.client.click(getSidebarSelectors("workloads").expandSubMenu); - await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods"); - await app.client.click('a[href^="/pods"]'); - await app.client.click(".NamespaceSelect"); - await app.client.keys("kube-system"); - await app.client.keys("Enter");// "\uE007" - await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver"); - let podMenuItemEnabled = false; + await inputField.click(); + await inputField.type("apiVersion: v1", { delay: 10 }); + await inputField.press("Enter", { delay: 10 }); + await inputField.type("kind: Pod", { delay: 10 }); + await inputField.press("Enter", { delay: 10 }); + await inputField.type("metadata:", { delay: 10 }); + await inputField.press("Enter", { delay: 10 }); + await inputField.type(" name: nginx-create-pod-test", { delay: 10 }); + await inputField.press("Enter", { delay: 10 }); + await inputField.type(`namespace: ${TEST_NAMESPACE}`, { delay: 10 }); + await inputField.press("Enter", { delay: 10 }); + await inputField.press("Backspace", { delay: 10 }); + await inputField.type("spec:", { delay: 10 }); + await inputField.press("Enter", { delay: 10 }); + await inputField.type(" containers:", { delay: 10 }); + await inputField.press("Enter", { delay: 10 }); + await inputField.type("- name: nginx-create-pod-test", { delay: 10 }); + await inputField.press("Enter", { delay: 10 }); + await inputField.type(" image: nginx:alpine", { delay: 10 }); + await inputField.press("Enter", { delay: 10 }); - // Wait until extensions are enabled on renderer - while (!podMenuItemEnabled) { - const logs = await app.client.getRenderProcessLogs(); - - podMenuItemEnabled = !!logs.find(entry => entry.message.includes("[EXTENSION]: enabled lens-pod-menu@")); - - if (!podMenuItemEnabled) { - await new Promise(r => setTimeout(r, 1000)); - } - } - - // Open logs tab in dock - await app.client.click(".list .TableRow:first-child"); - await app.client.waitForVisible(".Drawer"); - const logsButton = "ul.KubeObjectMenu li.MenuItem i.Icon span[data-icon-name='subject']"; - - await app.client.waitForVisible(logsButton); - await app.client.click(logsButton); - - // Check if controls are available - await app.client.waitForVisible(".LogList .VirtualList"); - await app.client.waitForVisible(".LogResourceSelector"); - //await app.client.waitForVisible(".LogSearch .SearchInput"); - await app.client.waitForVisible(".LogSearch .SearchInput input"); - // Search for semicolon - await app.client.keys(":"); - await app.client.waitForVisible(".LogList .list span.active"); - // Click through controls - await app.client.click(".LogControls .show-timestamps"); - await app.client.click(".LogControls .show-previous"); - }); - }); - - describe("cluster operations", () => { - utils.beforeEachWrapped(appStartAddCluster); - utils.afterEachWrapped(() => utils.tearDown(app)); - - it("shows default namespace", async () => { - expect(clusterAdded).toBe(true); - await app.client.click('a[href="/namespaces"]'); - await app.client.waitUntilTextExists("div.TableCell", "default"); - await app.client.waitUntilTextExists("div.TableCell", "kube-system"); - }); - - it(`creates ${TEST_NAMESPACE} namespace`, async () => { - expect(clusterAdded).toBe(true); - await app.client.click('a[href="/namespaces"]'); - await app.client.waitUntilTextExists("div.TableCell", "default"); - await app.client.waitUntilTextExists("div.TableCell", "kube-system"); - await app.client.click("button.add-button"); - await app.client.waitUntilTextExists("div.AddNamespaceDialog", "Create Namespace"); - await app.client.keys(`${TEST_NAMESPACE}\n`); - await app.client.waitForExist(`.name=${TEST_NAMESPACE}`); - }); - - it(`creates a pod in ${TEST_NAMESPACE} namespace`, async () => { - expect(clusterAdded).toBe(true); - await app.client.click(getSidebarSelectors("workloads").expandSubMenu); - await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods"); - await app.client.click('a[href^="/pods"]'); - - await app.client.click(".NamespaceSelect"); - await app.client.keys(TEST_NAMESPACE); - await app.client.keys("Enter");// "\uE007" - await app.client.click(".Icon.new-dock-tab"); - await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource"); - await app.client.click("li.MenuItem.create-resource-tab"); - await app.client.waitForVisible(".CreateResource div.react-monaco-editor-container"); - // Write pod manifest to editor - await app.client.click(".CreateResource div.react-monaco-editor-container"); - await app.client.keys("apiVersion: v1\n"); - await app.client.keys("kind: Pod\n"); - await app.client.keys("metadata:\n"); - await app.client.keys(" name: nginx-create-pod-test\n"); - await app.client.keys(`namespace: ${TEST_NAMESPACE}\n`); - await app.client.keys(`${BACKSPACE}spec:\n`); - await app.client.keys(" containers:\n"); - await app.client.keys("- name: nginx-create-pod-test\n"); - await app.client.keys(" image: nginx:alpine\n"); - // Create deployment - await app.client.waitForEnabled("button.Button=Create & Close"); - await app.client.click("button.Button=Create & Close"); - // Wait until first bits of pod appears on dashboard - await app.client.waitForExist(".name=nginx-create-pod-test"); - // Open pod details - await app.client.click(".name=nginx-create-pod-test"); - await app.client.waitUntilTextExists("div.drawer-title-text", "Pod: nginx-create-pod-test"); - }); - }); - }); + await frame.click("button.Button >> text='Create & Close'"); + await frame.click("div.TableCell >> text=nginx-create-pod-test"); + await frame.waitForSelector("div.drawer-title-text >> text='Pod: nginx-create-pod-test'"); + }, 10*60*1000); }); diff --git a/integration/__tests__/command-palette.tests.ts b/integration/__tests__/command-palette.tests.ts index 4695f48b33..1f2e49114b 100644 --- a/integration/__tests__/command-palette.tests.ts +++ b/integration/__tests__/command-palette.tests.ts @@ -19,25 +19,25 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { Application } from "spectron"; +import type { Page } from "playwright"; import * as utils from "../helpers/utils"; -jest.setTimeout(2 * 60 * 1000); // 2 minutes so that we can get better errors from spectron - describe("Lens command palette", () => { - let app: Application; + let window: Page, cleanup: () => Promise; + + beforeEach(async () => { + ({ window, cleanup } = await utils.start()); + await utils.clickWelcomeButton(window); + }, 10*60*1000); + + afterEach(async () => { + await cleanup(); + }, 10*60*1000); describe("menu", () => { - utils.beforeAllWrapped(async () => { - app = await utils.setup(); - }); - - utils.afterAllWrapped(() => utils.tearDown(app)); - - it("opens command dialog from menu", async () => { - await app.electron.ipcRenderer.send("test-menu-item-click", "View", "Command Palette..."); - await app.client.waitUntilTextExists(".Select__option", "Hotbar: Switch"); - await app.client.keys("Escape"); - }); + it("opens command dialog from keyboard shortcut", async () => { + await window.keyboard.press("Meta+Shift+p"); + await window.waitForSelector(".Select__option >> text=Hotbar: Switch"); + }, 10*60*1000); }); }); diff --git a/integration/helpers/minikube.ts b/integration/helpers/minikube.ts index 8698bd6be2..b7c2356dab 100644 --- a/integration/helpers/minikube.ts +++ b/integration/helpers/minikube.ts @@ -19,7 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { spawnSync } from "child_process"; -import type { Application } from "spectron"; export function minikubeReady(testNamespace: string): boolean { // determine if minikube is running @@ -57,18 +56,3 @@ export function minikubeReady(testNamespace: string): boolean { return true; } - -export async function waitForMinikubeDashboard(app: Application) { - await app.client.waitUntilTextExists("div.TableCell", "minikube"); - await app.client.waitForExist(".Input.SearchInput input"); - await app.client.setValue(".Input.SearchInput input", "minikube"); - await app.client.waitUntilTextExists("div.TableCell", "minikube"); - await app.client.click("div.TableRow"); - await app.client.waitUntilTextExists("div.drawer-title-text", "KubernetesCluster: minikube"); - await app.client.waitForExist("div.EntityIcon div.HotbarIcon div div.MuiAvatar-root"); - await app.client.click("div.EntityIcon div.HotbarIcon div div.MuiAvatar-root"); - await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started"); - await app.client.waitForExist(`iframe[name="minikube"]`); - await app.client.frame("minikube"); - await app.client.waitUntilTextExists("span.link-text", "Cluster"); -} diff --git a/integration/helpers/utils.ts b/integration/helpers/utils.ts index 15fec4d20b..f0e82809fb 100644 --- a/integration/helpers/utils.ts +++ b/integration/helpers/utils.ts @@ -18,49 +18,20 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { Application } from "spectron"; -import * as util from "util"; -import { exec } from "child_process"; +import { createHash } from "crypto"; +import { mkdirp, remove } from "fs-extra"; +import * as os from "os"; +import * as path from "path"; +import * as uuid from "uuid"; +import { ElectronApplication, Frame, Page, _electron as electron } from "playwright"; +import { noop } from "lodash"; -const AppPaths: Partial> = { +export const AppPaths: Partial> = { "win32": "./dist/win-unpacked/OpenLens.exe", "linux": "./dist/linux-unpacked/open-lens", "darwin": "./dist/mac/OpenLens.app/Contents/MacOS/OpenLens", }; -interface DoneCallback { - (...args: any[]): any; - fail(error?: string | { message: string }): any; -} - -/** - * This is necessary because Jest doesn't do this correctly. - * @param fn The function to call - */ -export function wrapJestLifecycle(fn: () => Promise | void): (done: DoneCallback) => void { - return function (done: DoneCallback) { - (async () => fn())() - .then(() => done()) - .catch(error => done.fail(error)); - }; -} - -export function beforeAllWrapped(fn: () => Promise | void): void { - beforeAll(wrapJestLifecycle(fn)); -} - -export function beforeEachWrapped(fn: () => Promise | void): void { - beforeEach(wrapJestLifecycle(fn)); -} - -export function afterAllWrapped(fn: () => Promise | void): void { - afterAll(wrapJestLifecycle(fn)); -} - -export function afterEachWrapped(fn: () => Promise | void): void { - afterEach(wrapJestLifecycle(fn)); -} - export function itIf(condition: boolean) { return condition ? it : it.skip; } @@ -69,71 +40,80 @@ export function describeIf(condition: boolean) { return condition ? describe : describe.skip; } -export const keys = { - backspace: "\uE003" -}; +async function getMainWindow(app: ElectronApplication, timeout = 50_000): Promise { + const deadline = Date.now() + timeout; -export async function setup(): Promise { - const app = new Application({ - path: AppPaths[process.platform], // path to electron app - args: [], - startTimeout: 60000, - waitTimeout: 10000, - env: { - CICD: "true" + for (; Date.now() < deadline;) { + for (const page of app.windows()) { + if (page.url().startsWith("http://localhost")) { + return page; + } } - }); - await app.start(); - // Wait for splash screen to be closed - while (await app.client.getWindowCount() > 1); - await app.client.windowByIndex(0); - await app.client.waitUntilWindowLoaded(); - await showCatalog(app); - - return app; -} - -export async function showCatalog(app: Application) { - await app.client.waitForExist("#hotbarIcon-catalog-entity .Icon"); - await app.client.click("#hotbarIcon-catalog-entity .Icon"); -} - -type AsyncPidGetter = () => Promise; - -export async function tearDown(app?: Application) { - if (!app?.isRunning()) { - return; + await new Promise(resolve => setTimeout(resolve, 2_000)); } - const pid = await (app.mainProcess.pid as any as AsyncPidGetter)(); + throw new Error(`Lens did not open the main window within ${timeout}ms`); +} - await app.stop(); +export async function start() { + const CICD = path.join(os.tmpdir(), "lens-integration-testing", uuid.v4()); + + // Make sure that the directory is clear + await remove(CICD).catch(noop); + await mkdirp(CICD); + + const app = await electron.launch({ + args: ["--integration-testing"], // this argument turns off the blocking of quit + executablePath: AppPaths[process.platform], + bypassCSP: true, + env: { + CICD, + ...process.env + }, + timeout: 100_000, + } as Parameters[0]); try { - process.kill(pid, "SIGKILL"); - } catch (e) { - console.error(e); + const window = await getMainWindow(app); + + return { + app, + window, + cleanup: async () => { + await app.close(); + await remove(CICD).catch(noop); + }, + }; + } catch (error) { + await app.close(); + await remove(CICD).catch(noop); + throw error; } } -export const promiseExec = util.promisify(exec); - -type HelmRepository = { - name: string; - url: string; -}; - -export async function listHelmRepositories(): Promise{ - for (let i = 0; i < 10; i += 1) { - try { - const { stdout } = await promiseExec("helm repo list -o json"); - - return JSON.parse(stdout); - } catch { - await new Promise(r => setTimeout(r, 2000)); // if no repositories, wait for Lens adding bitnami repository - } - } - - return []; +export async function clickWelcomeButton(window: Page) { + await window.click("#hotbarIcon-catalog-entity .Icon"); +} + +function minikubeEntityId() { + return createHash("md5").update(`${path.join(os.homedir(), ".kube", "config")}:minikube`).digest("hex"); +} + +/** + * From the catalog, click the minikube entity and wait for it to connect, returning its frame + */ +export async function lauchMinikubeClusterFromCatalog(window: Page): Promise { + await window.waitForSelector("div.TableCell"); + await window.click("div.TableCell >> text='minikube'"); + await window.waitForSelector("div.drawer-title-text >> text='KubernetesCluster: minikube'"); + await window.click("div.EntityIcon div.HotbarIcon div div.MuiAvatar-root"); + + const minikubeFrame = await window.waitForSelector(`#cluster-frame-${minikubeEntityId()}`); + + const frame = await minikubeFrame.contentFrame(); + + await frame.waitForSelector("div.Sidebar"); + + return frame; } diff --git a/package.json b/package.json index 64f940dc77..3b8627e481 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "build:linux": "yarn run compile && electron-builder --linux --dir", "build:mac": "yarn run compile && electron-builder --mac --dir", "build:win": "yarn run compile && electron-builder --win --dir", - "integration": "jest --runInBand integration", + "integration": "jest --runInBand --detectOpenHandles --forceExit integration", "dist": "yarn run compile && electron-builder --publish onTag", "dist:win": "yarn run compile && electron-builder --publish onTag --x64 --ia32", "dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null", @@ -52,7 +52,7 @@ "sentryDsn": "" }, "engines": { - "node": ">=12 <13" + "node": ">=14 <15" }, "jest": { "collectCoverage": false, @@ -180,6 +180,7 @@ } }, "dependencies": { + "@electron/remote": "^1.2.1", "@hapi/call": "^8.0.1", "@hapi/subtext": "^7.0.3", "@kubernetes/client-node": "^0.15.1", @@ -326,7 +327,7 @@ "css-loader": "^5.2.6", "deepdash": "^5.3.5", "dompurify": "^2.0.17", - "electron": "^9.4.4", + "electron": "^12.0.17", "electron-builder": "^22.10.5", "electron-notarize": "^0.3.0", "esbuild": "^0.12.12", @@ -352,6 +353,7 @@ "node-loader": "^1.0.3", "node-sass": "^4.14.1", "nodemon": "^2.0.12", + "playwright": "^1.14.0", "postcss": "^8.3.6", "postcss-loader": "4.0.3", "postinstall-postinstall": "^2.1.0", @@ -367,12 +369,11 @@ "react-window": "^1.8.5", "sass-loader": "^8.0.2", "sharp": "^0.29.0", - "spectron": "11.0.0", "style-loader": "^2.0.0", "tailwindcss": "^2.2.4", "ts-jest": "26.5.6", "ts-loader": "^7.0.5", - "ts-node": "^8.10.2", + "ts-node": "^10.1.0", "type-fest": "^1.0.2", "typed-emitter": "^1.3.1", "typedoc": "0.21.0-beta.2", diff --git a/src/common/__tests__/user-store.test.ts b/src/common/__tests__/user-store.test.ts index c5a15aec28..ba31f06db6 100644 --- a/src/common/__tests__/user-store.test.ts +++ b/src/common/__tests__/user-store.test.ts @@ -37,7 +37,6 @@ import { Console } from "console"; import { SemVer } from "semver"; import electron from "electron"; import { stdout, stderr } from "process"; -import { beforeEachWrapped } from "../../../integration/helpers/utils"; import { ThemeStore } from "../../renderer/theme.store"; import type { ClusterStoreModel } from "../cluster-store"; @@ -45,7 +44,7 @@ console = new Console(stdout, stderr); describe("user store tests", () => { describe("for an empty config", () => { - beforeEachWrapped(() => { + beforeEach(() => { mockFs({ tmp: { "config.json": "{}", "kube_config": "{}" } }); (UserStore.createInstance() as any).refreshNewContexts = jest.fn(() => Promise.resolve()); @@ -94,7 +93,7 @@ describe("user store tests", () => { }); describe("migrations", () => { - beforeEachWrapped(() => { + beforeEach(() => { mockFs({ "tmp": { "config.json": JSON.stringify({ diff --git a/src/common/base-store.ts b/src/common/base-store.ts index c311bd8808..54f6d2a5a2 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -22,7 +22,7 @@ import path from "path"; import Config from "conf"; import type { Options as ConfOptions } from "conf/dist/source/types"; -import { app, ipcMain, ipcRenderer, remote } from "electron"; +import { ipcMain, ipcRenderer } from "electron"; import { IReactionOptions, makeObservable, reaction, runInAction } from "mobx"; import { getAppVersion, Singleton, toJS, Disposer } from "./utils"; import logger from "../main/logger"; @@ -30,6 +30,7 @@ import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc"; import isEqual from "lodash/isEqual"; import { isTestEnv } from "./vars"; import { kebabCase } from "lodash"; +import { getPath } from "./utils/getPath"; export interface BaseStoreParams extends ConfOptions { syncOptions?: IReactionOptions; @@ -88,7 +89,7 @@ export abstract class BaseStore extends Singleton { } protected cwd() { - return (app || remote.app).getPath("userData"); + return getPath("userData"); } protected async saveToFile(model: T) { diff --git a/src/common/ipc/index.ts b/src/common/ipc/index.ts index 13c6af2fcb..ad1cf2a76c 100644 --- a/src/common/ipc/index.ts +++ b/src/common/ipc/index.ts @@ -19,6 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +export const dialogShowOpenDialogHandler = "dialog:show-open-dialog"; + export * from "./ipc"; export * from "./invalid-kubeconfig"; export * from "./update-available.ipc"; diff --git a/src/common/ipc/ipc.ts b/src/common/ipc/ipc.ts index 074d73176c..f93a9486aa 100644 --- a/src/common/ipc/ipc.ts +++ b/src/common/ipc/ipc.ts @@ -23,12 +23,14 @@ // https://www.electronjs.org/docs/api/ipc-main // https://www.electronjs.org/docs/api/ipc-renderer -import { ipcMain, ipcRenderer, remote, webContents } from "electron"; +import { ipcMain, ipcRenderer, webContents } from "electron"; import { toJS } from "../utils/toJS"; import logger from "../../main/logger"; import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames"; import type { Disposer } from "../utils"; +const remote = ipcMain ? null : require("@electron/remote"); + const subFramesChannel = "ipc:get-sub-frames"; export async function requestMain(channel: string, ...args: any[]) { diff --git a/src/common/logger.ts b/src/common/logger.ts index 826b576614..15506f7a46 100644 --- a/src/common/logger.ts +++ b/src/common/logger.ts @@ -19,13 +19,14 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { app, ipcMain, remote } from "electron"; +import { ipcMain } from "electron"; import winston, { format } from "winston"; import type Transport from "winston-transport"; import { consoleFormat } from "winston-console-format"; import { isDebugging, isTestEnv } from "./vars"; import BrowserConsole from "winston-transport-browserconsole"; import { SentryTransport } from "./logger-transports"; +import { getPath } from "./utils/getPath"; const logLevel = process.env.LOG_LEVEL ? process.env.LOG_LEVEL @@ -71,7 +72,7 @@ if (!isTestEnv) { handleExceptions: false, level: logLevel, filename: "lens.log", - dirname: (app ?? remote?.app)?.getPath("logs"), + dirname: getPath("logs"), maxsize: 16 * 1024, maxFiles: 16, tailable: true, diff --git a/src/common/user-store/user-store.ts b/src/common/user-store/user-store.ts index 75e207a2ca..ee0022da1b 100644 --- a/src/common/user-store/user-store.ts +++ b/src/common/user-store/user-store.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { app, remote } from "electron"; +import { app } from "electron"; import semver from "semver"; import { action, computed, observable, reaction, makeObservable } from "mobx"; import { BaseStore } from "../base-store"; @@ -32,7 +32,8 @@ import { fileNameMigration } from "../../migrations/user-store"; import { ObservableToggleSet, toJS } from "../../renderer/utils"; import { DESCRIPTORS, KubeconfigSyncValue, UserPreferencesModel, EditorConfiguration } from "./preferences-helpers"; import logger from "../../main/logger"; -import type {monaco} from "react-monaco-editor"; +import type { monaco } from "react-monaco-editor"; +import { getPath } from "../utils/getPath"; export interface UserStoreModel { lastSeenAppVersion: string; @@ -69,7 +70,7 @@ export class UserStore extends BaseStore /* implements UserStore @observable shell?: string; @observable downloadBinariesPath?: string; @observable kubectlBinariesPath?: string; - + /** * Download kubectl binaries matching cluster version */ @@ -86,7 +87,7 @@ export class UserStore extends BaseStore /* implements UserStore * Monaco editor configs */ @observable editorConfiguration:EditorConfiguration = {tabSize: null, miniMap: null, lineNumbers: null}; - + /** * The set of file/folder paths to be synced */ @@ -115,7 +116,7 @@ export class UserStore extends BaseStore /* implements UserStore }); }, { fireImmediately: true, - }); + }); } // Returns monaco editor options for selected editor type (the place, where a particular instance of the editor is mounted) @@ -244,5 +245,5 @@ export class UserStore extends BaseStore /* implements UserStore * @returns string */ export function getDefaultKubectlDownloadPath(): string { - return path.join((app || remote.app).getPath("userData"), "binaries"); + return path.join(getPath("userData"), "binaries"); } diff --git a/src/common/utils/getPath.ts b/src/common/utils/getPath.ts new file mode 100644 index 0000000000..01df73975b --- /dev/null +++ b/src/common/utils/getPath.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { app, ipcMain } from "electron"; + +const remote = ipcMain ? null : require("@electron/remote"); + +/** + * calls getPath either on app or on the remote's app + * + * @deprecated Use a different method for accessing the getPath function + */ +export function getPath(name: Parameters[0]): string { + if (app) { + return app.getPath(name); + } + + return remote.app.getPath(name); +} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 3d7f4f9838..6035b9f408 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -40,6 +40,7 @@ export * from "./downloadFile"; export * from "./formatDuration"; export * from "./escapeRegExp"; export * from "./extended-map"; +export * from "./getPath"; export * from "./getRandId"; export * from "./hash-set"; export * from "./local-kubeconfig"; diff --git a/src/common/utils/local-kubeconfig.ts b/src/common/utils/local-kubeconfig.ts index 04ab27bfa4..8af595e5ec 100644 --- a/src/common/utils/local-kubeconfig.ts +++ b/src/common/utils/local-kubeconfig.ts @@ -19,13 +19,13 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { app, remote } from "electron"; import path from "path"; import * as uuid from "uuid"; import type { ClusterId } from "../cluster-types"; +import { getPath } from "./getPath"; export function storedKubeConfigFolder(): string { - return path.resolve((app || remote.app).getPath("userData"), "kubeconfigs"); + return path.resolve(getPath("userData"), "kubeconfigs"); } export function getCustomKubeConfigPath(clusterId: ClusterId = uuid.v4()): string { diff --git a/src/common/vars.ts b/src/common/vars.ts index dec81f313e..6462be4db5 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -35,6 +35,9 @@ export const isTestEnv = !!process.env.JEST_WORKER_ID; export const isDevelopment = !isTestEnv && !isProduction; export const isPublishConfigured = Object.keys(packageInfo.build).includes("publish"); +export const integrationTestingArg = "--integration-testing"; +export const isIntegrationTesting = process.argv.includes(integrationTestingArg); + export const productName = packageInfo.productName; export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`; export const publicPath = "/build/" as string; diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index 0f860a964d..2074de1183 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -19,14 +19,14 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { app, ipcRenderer, remote } from "electron"; +import { ipcRenderer } from "electron"; import { EventEmitter } from "events"; import { isEqual } from "lodash"; import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx"; import path from "path"; import { ClusterStore } from "../common/cluster-store"; import { broadcastMessage, ipcMainOn, ipcRendererOn, requestMain, ipcMainHandle } from "../common/ipc"; -import { Disposer, getHostedClusterId, Singleton, toJS } from "../common/utils"; +import { Disposer, getHostedClusterId, Singleton, toJS, getPath } from "../common/utils"; import logger from "../main/logger"; import type { InstalledExtension } from "./extension-discovery"; import { ExtensionsStore } from "./extensions-store"; @@ -36,7 +36,7 @@ import type { LensRendererExtension } from "./lens-renderer-extension"; import * as registries from "./registries"; export function extensionPackagesRoot() { - return path.join((app || remote.app).getPath("userData")); + return path.join(getPath("userData")); } const logModule = "[EXTENSIONS-LOADER]"; diff --git a/src/main/cluster.ts b/src/main/cluster.ts index b3eef9613f..319f2d23df 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -33,7 +33,8 @@ import { VersionDetector } from "./cluster-detectors/version-detector"; import { DetectorRegistry } from "./cluster-detectors/detector-registry"; import plimit from "p-limit"; import { toJS } from "../common/utils"; -import { initialNodeShellImage, ClusterState, ClusterMetadataKey, ClusterRefreshOptions, ClusterStatus, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel } from "../common/cluster-types"; +import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel } from "../common/cluster-types"; +import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../common/cluster-types"; /** * Cluster diff --git a/src/main/exit-app.ts b/src/main/exit-app.ts index 2f9e3b00c7..8f9f85d2a4 100644 --- a/src/main/exit-app.ts +++ b/src/main/exit-app.ts @@ -26,14 +26,9 @@ import { ClusterManager } from "./cluster-manager"; import logger from "./logger"; export function exitApp() { - console.log("before windowManager"); const windowManager = WindowManager.getInstance(false); - - console.log("before clusterManager"); const clusterManager = ClusterManager.getInstance(false); - console.log("after clusterManager"); - appEventBus.emit({ name: "service", action: "close" }); windowManager?.hide(); clusterManager?.stop(); diff --git a/src/main/extension-filesystem.ts b/src/main/extension-filesystem.ts index 46afe45d6e..d7c213e483 100644 --- a/src/main/extension-filesystem.ts +++ b/src/main/extension-filesystem.ts @@ -21,13 +21,13 @@ import { randomBytes } from "crypto"; import { SHA256 } from "crypto-js"; -import { app, remote } from "electron"; import fse from "fs-extra"; import { action, makeObservable, observable } from "mobx"; import path from "path"; import { BaseStore } from "../common/base-store"; import type { LensExtensionId } from "../extensions/lens-extension"; import { toJS } from "../common/utils"; +import { getPath } from "../common/utils/getPath"; interface FSProvisionModel { extensions: Record; // extension names to paths @@ -55,7 +55,7 @@ export class FilesystemProvisionerStore extends BaseStore { if (!this.registeredExtensions.has(extensionName)) { const salt = randomBytes(32).toString("hex"); const hashedName = SHA256(`${extensionName}/${salt}`).toString(); - const dirPath = path.resolve((app || remote.app).getPath("userData"), "extension_data", hashedName); + const dirPath = path.resolve(getPath("userData"), "extension_data", hashedName); this.registeredExtensions.set(extensionName, dirPath); } diff --git a/src/main/index.ts b/src/main/index.ts index c8a9d105d2..ce3cafd926 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -22,12 +22,12 @@ // Main process import "../common/system-ca"; +import { initialize as initializeRemote } from "@electron/remote/main"; import * as Mobx from "mobx"; import * as LensExtensionsCommonApi from "../extensions/common-api"; import * as LensExtensionsMainApi from "../extensions/main-api"; import { app, autoUpdater, dialog, powerMonitor } from "electron"; -import { appName, isMac, productName } from "../common/vars"; -import path from "path"; +import { appName, isIntegrationTesting, isMac, productName } from "../common/vars"; import { LensProxy } from "./lens-proxy"; import { WindowManager } from "./window-manager"; import { ClusterManager } from "./cluster-manager"; @@ -63,13 +63,13 @@ import { ensureDir } from "fs-extra"; import { Router } from "./router"; import { initMenu } from "./menu"; import { initTray } from "./tray"; +import * as path from "path"; import { kubeApiRequest, shellApiRequest } from "./proxy-functions"; +const onCloseCleanup = disposer(); +const onQuitCleanup = disposer(); + SentryInit(); - -const workingDir = path.join(app.getPath("appData"), appName); -const cleanup = disposer(); - app.setName(appName); logger.info(`📟 Setting ${productName} as protocol client for lens://`); @@ -80,14 +80,16 @@ if (app.setAsDefaultProtocolClient("lens")) { logger.info("📟 Protocol client register failed ❗"); } -if (!process.env.CICD) { - app.setPath("userData", workingDir); +if (process.env.CICD) { + app.setPath("appData", process.env.CICD); + app.setPath("userData", path.join(process.env.CICD, appName)); } if (process.env.LENS_DISABLE_GPU) { app.disableHardwareAcceleration(); } +initializeRemote(); configurePackages(); mangleProxyEnv(); initializers.initIpcMainHandlers(); @@ -121,7 +123,7 @@ app.on("second-instance", (event, argv) => { }); app.on("ready", async () => { - logger.info(`🚀 Starting ${productName} from "${workingDir}"`); + logger.info(`🚀 Starting ${productName} from "${app.getPath("exe")}"`); logger.info("🐚 Syncing shell environment"); await shellSync(); @@ -209,7 +211,7 @@ app.on("ready", async () => { logger.info("🖥️ Starting WindowManager"); const windowManager = WindowManager.createInstance(); - cleanup.push( + onQuitCleanup.push( initMenu(windowManager), initTray(windowManager), ); @@ -221,7 +223,7 @@ app.on("ready", async () => { } ipcMainOn(IpcRendererNavigationEvents.LOADED, async () => { - cleanup.push(pushCatalogToRenderer(catalogEntityRegistry)); + onCloseCleanup.push(pushCatalogToRenderer(catalogEntityRegistry)); await ensureDir(storedKubeConfigFolder()); KubeconfigSyncManager.getInstance().startSync(); startUpdateChecking(); @@ -269,7 +271,7 @@ app.on("activate", (event, hasVisibleWindows) => { /** * This variable should is used so that `autoUpdater.installAndQuit()` works */ -let blockQuit = true; +let blockQuit = !isIntegrationTesting; autoUpdater.on("before-quit-for-update", () => blockQuit = false); @@ -282,7 +284,7 @@ app.on("will-quit", (event) => { appEventBus.emit({ name: "app", action: "close" }); ClusterManager.getInstance(false)?.stop(); // close cluster connections KubeconfigSyncManager.getInstance(false)?.stopSync(); - cleanup(); + onCloseCleanup(); if (lprm) { // This is set to false here so that LPRM can wait to send future lens:// @@ -298,7 +300,8 @@ app.on("will-quit", (event) => { return; // skip exit to make tray work, to quit go to app's global menu or tray's menu } - LensProtocolRouterMain.getInstance(false)?.cleanup(); + lprm?.cleanup(); + onQuitCleanup(); }); app.on("open-url", (event, rawUrl) => { diff --git a/src/main/initializers/ipc.ts b/src/main/initializers/ipc.ts index efcd52a5a4..3c3e14f923 100644 --- a/src/main/initializers/ipc.ts +++ b/src/main/initializers/ipc.ts @@ -19,20 +19,21 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { IpcMainInvokeEvent } from "electron"; +import { BrowserWindow, dialog, IpcMainInvokeEvent } from "electron"; import { KubernetesCluster } from "../../common/catalog-entities"; import { clusterFrameMap } from "../../common/cluster-frames"; import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterDeleteHandler } from "../../common/cluster-ipc"; import { ClusterStore } from "../../common/cluster-store"; import type { ClusterId } from "../../common/cluster-types"; import { appEventBus } from "../../common/event-bus"; -import { ipcMainHandle } from "../../common/ipc"; +import { dialogShowOpenDialogHandler, ipcMainHandle } from "../../common/ipc"; import { catalogEntityRegistry } from "../catalog"; import { ClusterManager } from "../cluster-manager"; import { bundledKubectlPath } from "../kubectl"; import logger from "../logger"; import { promiseExecFile } from "../promise-exec"; import { ResourceApplier } from "../resource-applier"; +import { WindowManager } from "../window-manager"; export function initIpcMainHandlers() { ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => { @@ -138,4 +139,10 @@ export function initIpcMainHandlers() { throw `${clusterId} is not a valid cluster id`; } }); + + ipcMainHandle(dialogShowOpenDialogHandler, async (event, dialogOpts: Electron.OpenDialogOptions) => { + await WindowManager.getInstance().ensureMainWindow(); + + return dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), dialogOpts); + }); } diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index 87166e597a..fbfd044dab 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -19,7 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { app, remote } from "electron"; import path from "path"; import fs from "fs"; import { promiseExec } from "./promise-exec"; @@ -32,6 +31,7 @@ import { customRequest } from "../common/request"; import { getBundledKubectlVersion } from "../common/utils/app-version"; import { isDevelopment, isWindows, isTestEnv } from "../common/vars"; import { SemVer } from "semver"; +import { getPath } from "../common/utils/getPath"; const bundledVersion = getBundledKubectlVersion(); const kubectlMap: Map = new Map([ @@ -84,7 +84,7 @@ export class Kubectl { protected dirname: string; static get kubectlDir() { - return path.join((app || remote.app).getPath("userData"), "binaries", "kubectl"); + return path.join(getPath("userData"), "binaries", "kubectl"); } public static readonly bundledKubectlVersion: string = bundledVersion; diff --git a/src/main/menu.ts b/src/main/menu.ts index b1ffff6a57..34bd724d0a 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -83,7 +83,7 @@ export function buildMenu(windowManager: WindowManager) { accelerator: "CmdOrCtrl+,", click() { navigate(preferencesURL()); - } + }, }, { label: "Extensions", @@ -106,7 +106,7 @@ export function buildMenu(windowManager: WindowManager) { exitApp(); } } - ] + ], }; const fileMenu: MenuItemConstructorOptions = { label: "File", @@ -150,7 +150,7 @@ export function buildMenu(windowManager: WindowManager) { } } ]) - ] + ], }; const editMenu: MenuItemConstructorOptions = { label: "Edit", diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index f1715a4284..6adba1a987 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -89,7 +89,8 @@ export class WindowManager extends Singleton { nodeIntegration: true, nodeIntegrationInSubFrames: true, enableRemoteModule: true, - webviewTag: true + webviewTag: true, + contextIsolation: false, }, }); this.windowState.manage(this.mainWindow); @@ -257,7 +258,10 @@ export class WindowManager extends Singleton { resizable: false, show: false, webPreferences: { - nodeIntegration: true + nodeIntegration: true, + enableRemoteModule: true, + contextIsolation: false, + nodeIntegrationInSubFrames: true, } }); await this.splashWindow.loadURL("static://splash.html"); diff --git a/src/migrations/user-store/file-name-migration.ts b/src/migrations/user-store/file-name-migration.ts index 122cd78102..10d7a7c99b 100644 --- a/src/migrations/user-store/file-name-migration.ts +++ b/src/migrations/user-store/file-name-migration.ts @@ -20,11 +20,11 @@ */ import fse from "fs-extra"; -import { app, remote } from "electron"; import path from "path"; +import { getPath } from "../../common/utils/getPath"; export function fileNameMigration() { - const userDataPath = (app || remote.app).getPath("userData"); + const userDataPath = getPath("userData"); const configJsonPath = path.join(userDataPath, "config.json"); const lensUserStoreJsonPath = path.join(userDataPath, "lens-user-store.json"); diff --git a/src/renderer/components/+apps-helm-charts/helm-charts.tsx b/src/renderer/components/+apps-helm-charts/helm-charts.tsx index 169b2c258d..8a47654adc 100644 --- a/src/renderer/components/+apps-helm-charts/helm-charts.tsx +++ b/src/renderer/components/+apps-helm-charts/helm-charts.tsx @@ -96,7 +96,7 @@ export class HelmCharts extends Component { searchProps: { ...searchProps, placeholder: "Search Helm Charts...", - }, + } })} renderTableHeader={[ { className: "icon", showWithColumn: columnId.name }, diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index 3d616b4b20..eb5bf4edec 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -21,7 +21,7 @@ import "./extensions.scss"; -import { remote, shell } from "electron"; +import { shell } from "electron"; import fse from "fs-extra"; import _ from "lodash"; import { makeObservable, observable, reaction, when } from "mobx"; @@ -46,6 +46,8 @@ import { InstalledExtensions } from "./installed-extensions"; import { Notice } from "./notice"; import { SettingLayout } from "../layout/setting-layout"; import { docsUrl } from "../../../common/vars"; +import { dialog } from "../../remote-helpers"; +import { getPath } from "../../../common/utils/getPath"; function getMessageFromError(error: any): string { if (!error || typeof error !== "object") { @@ -466,9 +468,8 @@ async function installFromInput(input: string) { const supportedFormats = ["tar", "tgz"]; async function installFromSelectFileDialog() { - const { dialog, BrowserWindow, app } = remote; - const { canceled, filePaths } = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), { - defaultPath: app.getPath("downloads"), + const { canceled, filePaths } = await dialog.showOpenDialog({ + defaultPath: getPath("downloads"), properties: ["openFile", "multiSelections"], message: `Select extensions to install (formats: ${supportedFormats.join(", ")}), `, buttonLabel: "Use configuration", diff --git a/src/renderer/components/+preferences/add-helm-repo-dialog.tsx b/src/renderer/components/+preferences/add-helm-repo-dialog.tsx index 36388088e1..8ec68dc395 100644 --- a/src/renderer/components/+preferences/add-helm-repo-dialog.tsx +++ b/src/renderer/components/+preferences/add-helm-repo-dialog.tsx @@ -22,7 +22,7 @@ import "./add-helm-repo-dialog.scss"; import React from "react"; -import { remote, FileFilter } from "electron"; +import type { FileFilter } from "electron"; import { observable, makeObservable } from "mobx"; import { observer } from "mobx-react"; import { Dialog, DialogProps } from "../dialog"; @@ -35,6 +35,7 @@ import { SubTitle } from "../layout/sub-title"; import { Icon } from "../icon"; import { Notifications } from "../notifications"; import { HelmRepo, HelmRepoManager } from "../../../main/helm/helm-repo-manager"; +import { dialog } from "../../remote-helpers"; interface Props extends Partial { onAddRepo: Function @@ -88,8 +89,7 @@ export class AddHelmRepoDialog extends React.Component { } async selectFileDialog(type: FileType, fileFilter: FileFilter) { - const { dialog, BrowserWindow } = remote; - const { canceled, filePaths } = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), { + const { canceled, filePaths } = await dialog.showOpenDialog({ defaultPath: this.getFilePath(type), properties: ["openFile", "showHiddenFiles"], message: `Select file`, diff --git a/src/renderer/components/cluster-manager/lens-views.ts b/src/renderer/components/cluster-manager/lens-views.ts index 3fcce1ed6f..830fe20cb3 100644 --- a/src/renderer/components/cluster-manager/lens-views.ts +++ b/src/renderer/components/cluster-manager/lens-views.ts @@ -50,6 +50,7 @@ export async function initView(clusterId: ClusterId) { const parentElem = document.getElementById("lens-views"); const iframe = document.createElement("iframe"); + iframe.id = `cluster-frame-${cluster.id}`; iframe.name = cluster.contextName; iframe.setAttribute("src", getClusterFrameUrl(clusterId)); iframe.addEventListener("load", () => { diff --git a/src/renderer/components/path-picker/index.ts b/src/renderer/components/path-picker/index.ts new file mode 100644 index 0000000000..4280faec89 --- /dev/null +++ b/src/renderer/components/path-picker/index.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +export * from "./path-picker"; diff --git a/src/renderer/initializers/catalog-category-registry.tsx b/src/renderer/initializers/catalog-category-registry.tsx index 56eefc3c07..60fba44f9e 100644 --- a/src/renderer/initializers/catalog-category-registry.tsx +++ b/src/renderer/initializers/catalog-category-registry.tsx @@ -27,7 +27,7 @@ import { UserStore } from "../../common/user-store"; import { getAllEntries } from "../components/+preferences/kubeconfig-syncs"; import { runInAction } from "mobx"; import { isWindows } from "../../common/vars"; -import { PathPicker } from "../components/path-picker/path-picker"; +import { PathPicker } from "../components/path-picker"; import { Notifications } from "../components/notifications"; import { Link } from "react-router-dom"; diff --git a/src/renderer/ipc/index.tsx b/src/renderer/ipc/index.tsx index 139f4bd362..62dd96a264 100644 --- a/src/renderer/ipc/index.tsx +++ b/src/renderer/ipc/index.tsx @@ -117,7 +117,7 @@ function HotbarTooManyItemsHandler(): void { Notifications.error(`Cannot have more than ${defaultHotbarCells} items pinned to a hotbar`); } -export function registerIpcHandlers() { +export function registerIpcListeners() { onCorrect({ source: ipcRenderer, channel: UpdateAvailableChannel, diff --git a/src/renderer/keyboard-shortcuts/index.ts b/src/renderer/keyboard-shortcuts/index.ts new file mode 100644 index 0000000000..7a3757ea7a --- /dev/null +++ b/src/renderer/keyboard-shortcuts/index.ts @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { ipcRenderer } from "electron"; +import { navigate } from "../navigation"; + +/** + * The definition of a keyboard shortcut + */ +interface Shortcut { + code?: string; + key?: string; + metaKey?: boolean; + altKey?: boolean; + shiftKey?: boolean; + ctrlKey?: boolean; + action: () => void; +} + +const shortcuts: Shortcut[] = [ + { + key: "p", + metaKey: true, + shiftKey: true, + action: () => ipcRenderer.emit("command-palette:open"), + }, + { + code: "Comma", + metaKey: true, + action: () => navigate("/preferences"), + }, +]; + +function shortcutMatches(shortcut: Shortcut, event: KeyboardEvent): boolean { + if (typeof shortcut.metaKey === "boolean" && shortcut.metaKey !== event.metaKey) { + return false; + } + + if (typeof shortcut.altKey === "boolean" && shortcut.altKey !== event.altKey) { + return false; + } + + if (typeof shortcut.shiftKey === "boolean" && shortcut.shiftKey !== event.shiftKey) { + return false; + } + + if (typeof shortcut.ctrlKey === "boolean" && shortcut.ctrlKey !== event.ctrlKey) { + return false; + } + + if (typeof shortcut.code === "string" && shortcut.code !== event.code) { + return false; + } + + if (typeof shortcut.key === "string" && shortcut.key !== event.key) { + return false; + } + + return true; +} + +export function registerKeyboardShortcuts() { + window.addEventListener("keydown", event => { + for (const shortcut of shortcuts) { + if (shortcutMatches(shortcut, event)) { + shortcut.action(); + } + } + }); +} diff --git a/src/renderer/lens-app.tsx b/src/renderer/lens-app.tsx index 35151dcee2..4d19de1ae5 100644 --- a/src/renderer/lens-app.tsx +++ b/src/renderer/lens-app.tsx @@ -32,10 +32,11 @@ import { ExtensionLoader } from "../extensions/extension-loader"; import { broadcastMessage } from "../common/ipc"; import { CommandContainer } from "./components/command-palette/command-container"; import { bindProtocolAddRouteHandlers, LensProtocolRouterRenderer } from "./protocol-handler"; -import { registerIpcHandlers } from "./ipc"; +import { registerIpcListeners } from "./ipc"; import { ipcRenderer } from "electron"; import { IpcRendererNavigationEvents } from "./navigation/events"; import { catalogEntityRegistry } from "./api/catalog-entity-registry"; +import { registerKeyboardShortcuts } from "./keyboard-shortcuts"; @observer export class LensApp extends React.Component { @@ -48,7 +49,8 @@ export class LensApp extends React.Component { window.addEventListener("offline", () => broadcastMessage("network:offline")); window.addEventListener("online", () => broadcastMessage("network:online")); - registerIpcHandlers(); + registerKeyboardShortcuts(); + registerIpcListeners(); } componentDidMount() { diff --git a/src/renderer/remote-helpers/dialog.ts b/src/renderer/remote-helpers/dialog.ts new file mode 100644 index 0000000000..ddd51d5c91 --- /dev/null +++ b/src/renderer/remote-helpers/dialog.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { dialogShowOpenDialogHandler, requestMain } from "../../common/ipc"; + +export async function showOpenDialog(options: Electron.OpenDialogOptions): Promise { + return requestMain(dialogShowOpenDialogHandler, options); +} diff --git a/src/renderer/remote-helpers/index.ts b/src/renderer/remote-helpers/index.ts new file mode 100644 index 0000000000..dffa82b28e --- /dev/null +++ b/src/renderer/remote-helpers/index.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import * as dialog from "./dialog"; + +export { dialog }; diff --git a/src/renderer/utils/createStorage.ts b/src/renderer/utils/createStorage.ts index 3636180c35..854a7aef2f 100755 --- a/src/renderer/utils/createStorage.ts +++ b/src/renderer/utils/createStorage.ts @@ -22,13 +22,12 @@ // Keeps window.localStorage state in external JSON-files. // Because app creates random port between restarts => storage session wiped out each time. import path from "path"; -import { app, remote } from "electron"; import { comparer, observable, reaction, toJS, when } from "mobx"; import fse from "fs-extra"; import { StorageHelper } from "./storageHelper"; import { ClusterStore } from "../../common/cluster-store"; import logger from "../../main/logger"; -import { getHostedClusterId } from "../../common/utils"; +import { getHostedClusterId, getPath } from "../../common/utils"; const storage = observable({ initialized: false, @@ -47,7 +46,7 @@ export function createStorage(key: string, defaultValue: T) { export function createAppStorage(key: string, defaultValue: T, clusterId?: string | undefined) { const { logPrefix } = StorageHelper; - const folder = path.resolve((app || remote.app).getPath("userData"), "lens-local-storage"); + const folder = path.resolve(getPath("userData"), "lens-local-storage"); const fileName = `${clusterId ?? "app"}.json`; const filePath = path.resolve(folder, fileName); diff --git a/webpack.renderer.ts b/webpack.renderer.ts index c0353ef1f8..5be6ba09dc 100755 --- a/webpack.renderer.ts +++ b/webpack.renderer.ts @@ -82,7 +82,6 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura optimization: { minimize: false }, - module: { rules: [ { diff --git a/yarn.lock b/yarn.lock index 4bb51fdadd..95c8989f8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -349,7 +349,7 @@ ajv "^6.12.0" ajv-keywords "^3.4.1" -"@electron/get@^1.0.1", "@electron/get@^1.12.2": +"@electron/get@^1.0.1": version "1.12.2" resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.12.2.tgz#6442066afb99be08cefb9a281e4b4692b33764f3" integrity sha512-vAuHUbfvBQpYTJ5wB7uVIDq5c/Ry0fiTBMs7lnEYAo/qXXppIVcWdfBr57u6eRnKdVso7KSiH6p/LbQAG6Izrg== @@ -365,6 +365,11 @@ global-agent "^2.0.2" global-tunnel-ng "^2.7.1" +"@electron/remote@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@electron/remote/-/remote-1.2.1.tgz#665b9fc2c6a60f9e5039bf235e2c60ccd0242c32" + integrity sha512-yKh60I8KjezQkZqeuN5Nu2O/Z72+tgNgzvAa8QQPLtQbsrCOaeIWdXZQqierz4jQ5jzTNUk6KIcK3V2kFeaxaQ== + "@electron/universal@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.0.4.tgz#231ac246c39d45b80e159bd21c3f9027dcaa10f5" @@ -1222,6 +1227,26 @@ "@babel/runtime" "^7.12.5" "@testing-library/dom" "^7.28.1" +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + +"@tsconfig/node16@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + "@types/aria-query@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" @@ -1707,6 +1732,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.24.tgz#c57511e3a19c4b5e9692bb2995c40a3a52167944" integrity sha512-5SCfvCxV74kzR3uWgTYiGxrd69TbT1I6+cMx1A5kEly/IVveJBimtAMlXiEyVFn5DvUFewQWxOOiJhlxeQwxgA== +"@types/node@^14.6.2": + version "14.17.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.5.tgz#b59daf6a7ffa461b5648456ca59050ba8e40ed54" + integrity sha512-bjqH2cX/O33jXT/UmReo2pM7DIJREPMnarixbQ57DOOzzFaI6D2+IcwaJQaJpv0M1E9TIhPCYVxrkcityLjlqA== + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -2061,7 +2091,7 @@ resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.4.tgz#805c0612b3a0c124cf99f517364142946b74ba3b" integrity sha512-OjJdqx6QlbyZw9LShPwRW+Kmiegeg3eWNI41MQQKaG3vjdU2L9SRElntM51HmHBY1cu7izxQJ1lMYioQh3XMBg== -"@types/webdriverio@^4.13.0", "@types/webdriverio@^4.8.0": +"@types/webdriverio@^4.13.0": version "4.13.3" resolved "https://registry.yarnpkg.com/@types/webdriverio/-/webdriverio-4.13.3.tgz#c1571c4e62724135c0b11e7d7e36b07af5168856" integrity sha512-AfSQM1xTO9Ax+u9uSQPDuw69DQ0qA2RMoKHn86jCgWNcwKVUjGMSP4sfSl3JOfcZN8X/gWvn7znVPp2/g9zcJA== @@ -2559,11 +2589,6 @@ ansi-colors@^3.0.0, ansi-colors@^3.2.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== -ansi-escapes@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - ansi-escapes@^4.2.1: version "4.3.1" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" @@ -2699,32 +2724,6 @@ aproba@^1.0.3, aproba@^1.1.1, aproba@^1.1.2: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== -archiver-utils@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-1.3.0.tgz#e50b4c09c70bf3d680e32ff1b7994e9f9d895174" - integrity sha1-5QtMCccL89aA4y/xt5lOn52JUXQ= - dependencies: - glob "^7.0.0" - graceful-fs "^4.1.0" - lazystream "^1.0.0" - lodash "^4.8.0" - normalize-path "^2.0.0" - readable-stream "^2.0.0" - -archiver@~2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-2.1.1.tgz#ff662b4a78201494a3ee544d3a33fe7496509ebc" - integrity sha1-/2YrSnggFJSj7lRNOjP+dJZQnrw= - dependencies: - archiver-utils "^1.3.0" - async "^2.0.0" - buffer-crc32 "^0.2.1" - glob "^7.0.0" - lodash "^4.8.0" - readable-stream "^2.0.0" - tar-stream "^1.5.0" - zip-stream "^1.2.0" - archy@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" @@ -2927,7 +2926,7 @@ async@0.9.x: resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= -async@^2.0.0, async@^2.6.2: +async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -3077,14 +3076,6 @@ babel-preset-jest@^26.6.2: babel-plugin-jest-hoist "^26.6.2" babel-preset-current-node-syntax "^1.0.0" -babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -3174,14 +3165,6 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" - integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA== - dependencies: - readable-stream "^2.3.5" - safe-buffer "^5.1.1" - bl@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" @@ -3415,20 +3398,7 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - -buffer-alloc@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - -buffer-crc32@^0.2.1, buffer-crc32@~0.2.3: +buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= @@ -3438,11 +3408,6 @@ buffer-equal@1.0.0: resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= - buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -3738,11 +3703,6 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== -chardet@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" - integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= - chart.js@^2.9.4: version "2.9.4" resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684" @@ -3902,13 +3862,6 @@ cli-columns@^3.1.2: string-width "^2.0.0" strip-ansi "^3.0.1" -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= - dependencies: - restore-cursor "^2.0.0" - cli-table3@^0.5.0, cli-table3@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202" @@ -3927,11 +3880,6 @@ cli-truncate@^1.1.0: slice-ansi "^1.0.0" string-width "^2.0.0" -cli-width@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" - integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== - cliui@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" @@ -4162,7 +4110,7 @@ commander@^5.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^6.0.0: +commander@^6.0.0, commander@^6.1.0: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== @@ -4177,16 +4125,6 @@ component-emitter@^1.2.1: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== -compress-commons@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-1.2.2.tgz#524a9f10903f3a813389b0225d27c48bb751890f" - integrity sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8= - dependencies: - buffer-crc32 "^0.2.1" - crc32-stream "^2.0.0" - normalize-path "^2.0.0" - readable-stream "^2.0.0" - compressible@~2.0.16: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -4368,11 +4306,6 @@ core-js-pure@^3.0.0: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== -core-js@^2.4.0: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - core-js@^3.6.5: version "3.6.5" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" @@ -4405,15 +4338,7 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" -crc32-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-2.0.0.tgz#e3cdd3b4df3168dd74e3de3fbbcb7b297fe908f4" - integrity sha1-483TtN8xaN10494/u8t7KX/pCPQ= - dependencies: - crc "^3.4.4" - readable-stream "^2.0.0" - -crc@^3.4.4, crc@^3.8.0: +crc@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== @@ -4458,6 +4383,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-fetch@^3.0.4: version "3.0.6" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" @@ -4557,7 +4487,7 @@ css-loader@^5.2.6: schema-utils "^3.0.0" semver "^7.3.5" -css-parse@^2.0.0, css-parse@~2.0.0: +css-parse@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4" integrity sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q= @@ -4587,11 +4517,6 @@ css-unit-converter@^1.1.1: resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== -css-value@~0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/css-value/-/css-value-0.0.1.tgz#5efd6c2eea5ea1fd6b6ac57ec0427b18452424ea" - integrity sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo= - css-vendor@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d" @@ -4817,11 +4742,6 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -deepmerge@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.0.1.tgz#25c1c24f110fb914f80001b925264dd77f3f4312" - integrity sha512-VIPwiMJqJ13ZQfaCsIFnp5Me9tnjURiaIFxfz7EH0Ci0dTSQpZtSLrqOicXqEd/z2r+z+Klk9GzmnRsgpgbOsQ== - default-gateway@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" @@ -4966,11 +4886,6 @@ detective@^5.2.0: defined "^1.0.0" minimist "^1.1.1" -dev-null@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dev-null/-/dev-null-0.1.1.tgz#5a205ce3c2b2ef77b6238d6ba179eb74c6a0e818" - integrity sha1-WiBc48Ky73e2I41roXnrdMag6Bg= - dezalgo@^1.0.0, dezalgo@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" @@ -5261,11 +5176,6 @@ ejs@^3.1.6: dependencies: jake "^10.6.1" -ejs@~2.5.6: - version "2.5.9" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.9.tgz#7ba254582a560d267437109a68354112475b0ce5" - integrity sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ== - electron-builder@^22.10.5: version "22.10.5" resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-22.10.5.tgz#03b156b93e6012609027c3aaa69201a3ad21e454" @@ -5286,14 +5196,6 @@ electron-builder@^22.10.5: update-notifier "^5.1.0" yargs "^16.2.0" -electron-chromedriver@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/electron-chromedriver/-/electron-chromedriver-9.0.0.tgz#c7629fe6b9721140f3a380144f99960c2bc3b5c1" - integrity sha512-+MuukzicyfduXO/4yQv9ygLKaScttJNbWtg77A9fs2YhbkISjObWaCF3eJNZL+edZXRfaF/6D4XuXvklQCmwQg== - dependencies: - "@electron/get" "^1.12.2" - extract-zip "^2.0.0" - electron-devtools-installer@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/electron-devtools-installer/-/electron-devtools-installer-3.2.0.tgz#acc48d24eb7033fe5af284a19667e73b78d406d0" @@ -5357,13 +5259,13 @@ electron@*: "@types/node" "^12.0.12" extract-zip "^1.0.3" -electron@^9.4.4: - version "9.4.4" - resolved "https://registry.yarnpkg.com/electron/-/electron-9.4.4.tgz#2a74a0655a74bd326216672c5ae6ed3a44451446" - integrity sha512-dcPlTrMWQu5xuSm6sYV42KK/BRIqh3erM8v/WtZqaDmG7pkCeJpvw26Dgbqhdt78XmqqGiN96giEe6A3S9vpAQ== +electron@^12.0.17: + version "12.0.17" + resolved "https://registry.yarnpkg.com/electron/-/electron-12.0.17.tgz#9707c9bfd0a29ae63b8b66f1b3acf8894f7b63f4" + integrity sha512-jkOMKSEj/X9i++5LD7NKqYK/ORi6H0kHYk6rrvcJNQfbDpYX5lxNNexZ2ikPeKxS2B84+WcSFrw5Ce9y8B+pmA== dependencies: "@electron/get" "^1.0.1" - "@types/node" "^12.0.12" + "@types/node" "^14.6.2" extract-zip "^1.0.3" elliptic@^6.0.0, elliptic@^6.5.2: @@ -6068,15 +5970,6 @@ extend@^3.0.0, extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^2.0.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" - integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== - dependencies: - chardet "^0.4.0" - iconv-lite "^0.4.17" - tmp "^0.0.33" - extglob@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" @@ -6101,7 +5994,7 @@ extract-zip@^1.0.3: mkdirp "^0.5.4" yauzl "^2.10.0" -extract-zip@^2.0.0: +extract-zip@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== @@ -6209,13 +6102,6 @@ figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= - dependencies: - escape-string-regexp "^1.0.5" - file-entry-cache@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" @@ -6612,7 +6498,7 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gaze@^1.0.0, gaze@~1.1.2: +gaze@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== @@ -6960,7 +6846,7 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== @@ -6970,7 +6856,7 @@ graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2 resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= -grapheme-splitter@^1.0.2, grapheme-splitter@^1.0.4: +grapheme-splitter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== @@ -7027,11 +6913,6 @@ has-bigints@^1.0.1: resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" - integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -7395,7 +7276,7 @@ iconv-corefoundation@^1.1.5: cli-truncate "^1.1.0" node-addon-api "^1.6.3" -iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.4: +iconv-lite@0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -7630,26 +7511,6 @@ init-package-json@^1.10.3: validate-npm-package-license "^3.0.1" validate-npm-package-name "^3.0.0" -inquirer@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" - integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ== - dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^2.0.4" - figures "^2.0.0" - lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rx-lite "^4.0.8" - rx-lite-aggregates "^4.0.8" - string-width "^2.1.0" - strip-ansi "^4.0.0" - through "^2.3.6" - internal-ip@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" @@ -8684,6 +8545,11 @@ joycon@^3.0.1: resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.0.1.tgz#9074c9b08ccf37a6726ff74a18485f85efcaddaf" integrity sha512-SJcJNBg32dGgxhPtM0wQqxqV0ax9k/9TaUskGDSJkSFSQOEWWvQ3zzWdGQRIUry2j1zA5+ReH13t0Mf3StuVZA== +jpeg-js@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" + integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== + js-base64@^2.1.8: version "2.5.2" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.2.tgz#313b6274dda718f714d00b3330bbae6e38e90209" @@ -9041,13 +8907,6 @@ lazy-val@^1.0.4: resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.4.tgz#882636a7245c2cfe6e0a4e3ba6c5d68a137e5c65" integrity sha512-u93kb2fPbIrfzBuLjZE+w+fJbUUMhNDXxNmMfaqNgpfQf1CO5ZSe2LfsnBqVAk7i/2NF48OSoRj+Xe2VT+lE8Q== -lazystream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" - integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= - dependencies: - readable-stream "^2.0.5" - lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -9424,7 +9283,7 @@ lodash.without@~4.4.0: resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= -lodash@4.x, lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.8.0, lodash@~4.17.10: +lodash@4.x, lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@~4.17.10: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -9768,7 +9627,7 @@ mime@^2.4.4: resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== -mime@^2.5.0: +mime@^2.4.6, mime@^2.5.0: version "2.5.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== @@ -9847,11 +9706,6 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1. resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= - minipass@^2.3.5, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" @@ -9916,7 +9770,7 @@ mkdirp@1.x, mkdirp@^1.0.3, mkdirp@~1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.0, mkdirp@~0.5.1: +"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -10023,11 +9877,6 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= - mute-stream@~0.0.4: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -10339,7 +10188,7 @@ normalize-package-data@^3.0.0: semver "^7.3.4" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0, normalize-path@^2.1.1: +normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= @@ -10391,11 +10240,6 @@ npm-install-checks@^3.0.2: dependencies: semver "^2.3.0 || 3.x || 4 || 5" -npm-install-package@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/npm-install-package/-/npm-install-package-2.1.0.tgz#d7efe3cfcd7ab00614b896ea53119dc9ab259125" - integrity sha1-1+/jz816sAYUuJbqUxGdyaslkSU= - npm-lifecycle@^3.0.0, npm-lifecycle@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz#9882d3642b8c82c815782a12e6a1bfeed0026309" @@ -10805,13 +10649,6 @@ one-time@^1.0.0: dependencies: fn.name "1.x.x" -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= - dependencies: - mimic-fn "^1.0.0" - onetime@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" @@ -10874,14 +10711,6 @@ opn@^5.5.0: dependencies: is-wsl "^1.1.0" -optimist@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -10932,7 +10761,7 @@ os-locale@^2.0.0: lcid "^1.0.0" mem "^1.1.0" -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= @@ -11371,6 +11200,26 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" +playwright@^1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.14.0.tgz#18301b11f5278a446d36b5cf96f67db36ce2cd20" + integrity sha512-aR5oZ1iVsjQkGfYCjgYAmyMAVu0MQ0i8MgdnfdqDu9EVLfbnpuuFmTv/Rb7/Yjno1kOrDUP9+RyNC+zfG3wozA== + dependencies: + commander "^6.1.0" + debug "^4.1.1" + extract-zip "^2.0.1" + https-proxy-agent "^5.0.0" + jpeg-js "^0.4.2" + mime "^2.4.6" + pngjs "^5.0.0" + progress "^2.0.3" + proper-lockfile "^4.1.1" + proxy-from-env "^1.1.0" + rimraf "^3.0.2" + stack-utils "^2.0.3" + ws "^7.4.6" + yazl "^2.5.1" + plist@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.2.tgz#74bbf011124b90421c22d15779cee60060ba95bc" @@ -11380,6 +11229,11 @@ plist@^3.0.1: xmlbuilder "^9.0.7" xmldom "^0.5.0" +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + popper.js@1.16.1-lts: version "1.16.1-lts" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05" @@ -11694,7 +11548,7 @@ proper-lockfile@^1.2.0: graceful-fs "^4.1.2" retry "^0.10.0" -proper-lockfile@^4.1.2: +proper-lockfile@^4.1.1, proper-lockfile@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== @@ -11723,6 +11577,11 @@ proxy-addr@~2.0.5: forwarded "~0.1.2" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -11819,11 +11678,6 @@ purgecss@^4.0.3: postcss "^8.2.1" postcss-selector-parser "^6.0.2" -q@~1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - qrcode-terminal@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" @@ -12224,7 +12078,7 @@ read@1, read@~1.0.1, read@~1.0.7: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -12321,11 +12175,6 @@ redux@^4.0.0, redux@^4.0.4: loose-envify "^1.4.0" symbol-observable "^1.2.0" -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - regenerator-runtime@^0.13.4: version "0.13.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" @@ -12443,7 +12292,7 @@ request-promise-native@^1.0.8, request-promise-native@^1.0.9: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.83.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -12581,14 +12430,6 @@ responselike@^2.0.0: dependencies: lowercase-keys "^2.0.0" -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -12614,11 +12455,6 @@ rfc4648@^1.3.0: resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.3.0.tgz#2a69c76f05bc0e388feab933672de9b492af95f1" integrity sha512-x36K12jOflpm1V8QjPq3I+pt7Z1xzeZIjiC8J2Oxd7bE1efTrOG241DTYVJByP/SxR9jl1t7iZqYxDX864jgBQ== -rgb2hex@^0.1.9: - version "0.1.10" - resolved "https://registry.yarnpkg.com/rgb2hex/-/rgb2hex-0.1.10.tgz#4fdd432665273e2d5900434940ceba0a04c8a8a8" - integrity sha512-vKz+kzolWbL3rke/xeTE2+6vHmZnNxGyDnaVW4OckntAIcc7DcZzWkQSfxMDwqHS8vhgySnIFyBUH7lIk6PxvQ== - rimraf@2, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -12665,11 +12501,6 @@ rsvp@^4.8.4: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== -run-async@^2.2.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - run-parallel@^1.1.9: version "1.1.10" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef" @@ -12682,18 +12513,6 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rx-lite-aggregates@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" - integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= - dependencies: - rx-lite "*" - -rx-lite@*, rx-lite@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" - integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= - rxjs@^6.5.2: version "6.5.5" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" @@ -13346,18 +13165,6 @@ spdy@^4.0.2: select-hose "^2.0.0" spdy-transport "^3.0.0" -spectron@11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/spectron/-/spectron-11.0.0.tgz#79d785e6b8898638e77b5186711e3910ed4ca09b" - integrity sha512-YRiB0TTpJa8ofNML/k1fJShe+m7U/E2HnFZsdZK57ekWIzlTHF+Lq7ZvuKGxMbpooU/OZkLObZfitemxhBVH4w== - dependencies: - "@types/webdriverio" "^4.8.0" - dev-null "^0.1.1" - electron-chromedriver "^9.0.0" - request "^2.87.0" - split "^1.0.0" - webdriverio "^4.13.0" - split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" @@ -13370,7 +13177,7 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" -split@^1.0.0, split@^1.0.1: +split@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== @@ -13421,6 +13228,13 @@ stack-utils@^2.0.2: dependencies: escape-string-regexp "^2.0.0" +stack-utils@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" + integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== + dependencies: + escape-string-regexp "^2.0.0" + stackframe@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" @@ -13530,7 +13344,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -13799,13 +13613,6 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@~5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.0.1.tgz#1c5331f22250c84202805b2f17adf16699f3a39a" - integrity sha512-7FQGOlSQ+AQxBNXJpVDj8efTA/FtyB5wcNE1omXXJ0cq6jm1jjDwuROlYDbnzHqdNPqliWFhcioCWSyav+xBnA== - dependencies: - has-flag "^2.0.0" - supports-hyperlinks@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47" @@ -13891,19 +13698,6 @@ tar-fs@^2.0.0, tar-fs@^2.1.1: pump "^3.0.0" tar-stream "^2.1.4" -tar-stream@^1.5.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" - integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== - dependencies: - bl "^1.0.0" - buffer-alloc "^1.2.0" - end-of-stream "^1.0.0" - fs-constants "^1.0.0" - readable-stream "^2.3.0" - to-buffer "^1.1.1" - xtend "^4.0.0" - tar-stream@^2.1.4: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" @@ -14061,7 +13855,7 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3", through@^2.3.6: +through@2, "through@>=2.2.7 <3": version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -14105,13 +13899,6 @@ tmp-promise@^3.0.2: dependencies: tmp "^0.2.0" -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - tmp@^0.2.0, tmp@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" @@ -14129,11 +13916,6 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= -to-buffer@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" - integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -14273,12 +14055,17 @@ ts-loader@^7.0.5: micromatch "^4.0.0" semver "^6.0.0" -ts-node@^8.10.2: - version "8.10.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" - integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== +ts-node@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.1.0.tgz#e656d8ad3b61106938a867f69c39a8ba6efc966e" + integrity sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA== dependencies: + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.1" arg "^4.1.0" + create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" source-map-support "^0.5.17" @@ -14712,7 +14499,7 @@ url-parse@^1.4.3, url-parse@^1.5.1: querystringify "^2.1.1" requires-port "^1.0.0" -url@^0.11.0, url@~0.11.0: +url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= @@ -14917,39 +14704,6 @@ wcwidth@^1.0.0: dependencies: defaults "^1.0.3" -wdio-dot-reporter@~0.0.8: - version "0.0.10" - resolved "https://registry.yarnpkg.com/wdio-dot-reporter/-/wdio-dot-reporter-0.0.10.tgz#facfb7c9c5984149951f59cbc3cd0752101cf0e0" - integrity sha512-A0TCk2JdZEn3M1DSG9YYbNRcGdx/YRw19lTiRpgwzH4qqWkO/oRDZRmi3Snn4L2j54KKTfPalBhlOtc8fojVgg== - -webdriverio@^4.13.0: - version "4.14.4" - resolved "https://registry.yarnpkg.com/webdriverio/-/webdriverio-4.14.4.tgz#f7a94e9a6530819796088f42b009833d83de0386" - integrity sha512-Knp2vzuzP5c5ybgLu+zTwy/l1Gh0bRP4zAr8NWcrStbuomm9Krn9oRF0rZucT6AyORpXinETzmeowFwIoo7mNA== - dependencies: - archiver "~2.1.0" - babel-runtime "^6.26.0" - css-parse "^2.0.0" - css-value "~0.0.1" - deepmerge "~2.0.1" - ejs "~2.5.6" - gaze "~1.1.2" - glob "~7.1.1" - grapheme-splitter "^1.0.2" - inquirer "~3.3.0" - json-stringify-safe "~5.0.1" - mkdirp "~0.5.1" - npm-install-package "~2.1.0" - optimist "~0.6.1" - q "~1.5.0" - request "^2.83.0" - rgb2hex "^0.1.9" - safe-buffer "~5.1.1" - supports-color "~5.0.0" - url "~0.11.0" - wdio-dot-reporter "~0.0.8" - wgxpath "~1.0.0" - webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -15099,11 +14853,6 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== -wgxpath@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wgxpath/-/wgxpath-1.0.0.tgz#eef8a4b9d558cc495ad3a9a2b751597ecd9af690" - integrity sha1-7vikudVYzEla06mit1FZfs2a9pA= - what-input@^5.2.10: version "5.2.10" resolved "https://registry.yarnpkg.com/what-input/-/what-input-5.2.10.tgz#f79f5b65cf95d75e55e6d580bb0a6b98174cad4e" @@ -15241,11 +14990,6 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= - worker-farm@^1.6.0, worker-farm@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" @@ -15547,6 +15291,13 @@ yauzl@^2.10.0: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" +yazl@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" + integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== + dependencies: + buffer-crc32 "~0.2.3" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" @@ -15556,13 +15307,3 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zip-stream@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-1.2.0.tgz#a8bc45f4c1b49699c6b90198baacaacdbcd4ba04" - integrity sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ= - dependencies: - archiver-utils "^1.3.0" - compress-commons "^1.2.0" - lodash "^4.8.0" - readable-stream "^2.0.0"