From af47379b547035c24cc0106113ceb9b4a27751dd Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 7 Oct 2022 10:32:16 -0400 Subject: [PATCH] Release 6.1.3 (#6354) * Release 6.1.3 Signed-off-by: Sebastian Malton * ignore prerelease tag for kubectl version to download (#6299) Signed-off-by: Jim Ehrismann Signed-off-by: Jim Ehrismann * Split integration tests into seperate jobs from unit tests for faster CI (#6310) * Split integration tests into seperate jobs from unit tests for faster CI Signed-off-by: Sebastian Malton * Add logging Signed-off-by: Sebastian Malton * Simplify the matrix Signed-off-by: Sebastian Malton * Remove steps that are part of Makefile Signed-off-by: Sebastian Malton * Fix yml decl Signed-off-by: Sebastian Malton * Switch to using single quotes Signed-off-by: Sebastian Malton * Further clarify the test job names Signed-off-by: Sebastian Malton * Fix invocation Signed-off-by: Sebastian Malton * Attempt to fix traking stdout Signed-off-by: Sebastian Malton * Fix lint Signed-off-by: Sebastian Malton * And handling for tests failing to start Signed-off-by: Sebastian Malton * Add check for app early exiting Signed-off-by: Sebastian Malton * Add more logging to help with debugging Signed-off-by: Sebastian Malton * Cleanup attemptStart code Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton * Fix loading helm release details (#6318) * Fix loading helm release details - The helm manifest can sometimes contain KubeJsonApiDataLists instead of just KubeJsonApiData entries - Add additional logging to main for when a route handler throws so that we can gain more context in the future Signed-off-by: Sebastian Malton * Update tests Signed-off-by: Sebastian Malton * Fix usage of getHelmReleaseResources Signed-off-by: Sebastian Malton * Add test to verify handling of Lists being returned Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton * improve shellEnv (#6351) Signed-off-by: Jari Kolehmainen Signed-off-by: Jari Kolehmainen Signed-off-by: Sebastian Malton * Make creating releases automatic on merge of release PRs (#6353) * Remove unused bundled-extensions file Signed-off-by: Sebastian Malton * Remove unused release drafter workflow Signed-off-by: Sebastian Malton * Tag releases created using create-release-pr with the release label Signed-off-by: Sebastian Malton * Remove the unneeded tag-release script Signed-off-by: Sebastian Malton * Add workflow for creating release on the merging of a release PR Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton Signed-off-by: Jim Ehrismann Signed-off-by: Jari Kolehmainen Co-authored-by: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Co-authored-by: Jari Kolehmainen --- .bundled-extensions.json | 8 - .github/workflows/release-drafter.yml | 16 -- .github/workflows/release.yml | 30 ++++ .github/workflows/test.yml | 26 +-- .../__tests__/app-preferences.tests.ts | 5 +- integration/__tests__/cluster-pages.tests.ts | 6 +- .../__tests__/command-palette.tests.ts | 8 +- integration/helpers/utils.ts | 48 ++++- package.json | 5 +- scripts/create-release-pr.ts | 1 + scripts/tag-release.sh | 21 --- src/common/utils/objects.ts | 1 + ...wing-details-for-helm-release.test.ts.snap | 60 ------- .../call-for-helm-manifest.injectable.ts | 4 +- .../get-helm-release-resources.injectable.ts | 37 ++-- .../get-helm-release-resources.test.ts | 168 +++++------------- .../get-helm-release.injectable.ts | 24 ++- src/main/kubectl/kubectl.ts | 10 +- .../create-handler-for-route.injectable.ts | 80 +++++++++ src/main/router/router.injectable.ts | 13 +- src/main/router/router.ts | 92 +--------- src/main/shell-session/shell-session.ts | 6 +- src/main/utils/shell-env.ts | 88 ++++++++- .../release-details-content.tsx | 9 +- .../call-for-helm-release.injectable.ts | 2 +- .../release-details-model.injectable.tsx | 9 +- yarn.lock | 107 ++--------- 27 files changed, 374 insertions(+), 510 deletions(-) delete mode 100644 .bundled-extensions.json delete mode 100644 .github/workflows/release-drafter.yml create mode 100644 .github/workflows/release.yml delete mode 100755 scripts/tag-release.sh create mode 100644 src/main/router/create-handler-for-route.injectable.ts diff --git a/.bundled-extensions.json b/.bundled-extensions.json deleted file mode 100644 index f521c01dfd..0000000000 --- a/.bundled-extensions.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extensions": [ - "pod-menu", - "node-menu", - "metrics-cluster-feature", - "kube-object-event-status" - ] -} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml deleted file mode 100644 index ec49fec6e5..0000000000 --- a/.github/workflows/release-drafter.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Release Drafter - -on: - push: - # branches to consider in the event; optional, defaults to all - branches: - - master - -jobs: - update_release_draft: - runs-on: ubuntu-latest - steps: - # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v5 - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..b07b3a2929 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,30 @@ +name: Release Open Lens +on: + pull_request: + types: + - closed + branches: + - master + - release/v*.* +jobs: + release: + name: Release + if: ${{ github.event.pull_request.merged == 'true' && contains(github.event.pull_request.labels.*.name, 'release') }} + runs-on: ubuntu-latest + steps: + - name: Checkout Release from lens + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: butlerlogic/action-autotag@stable + id: tagger + with: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + tag_prefix: "v" + - uses: ncipollo/release-action@v1 + if: ${{ needs.tag.outputs.tagname != '' }} + with: + name: v${{ steps.open-lens-version.outputs.VERSION }} + commit: master + tag: v${{ steps.open-lens-version.outputs.VERSION }} + body: ${{ github.event.pull_request.body }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9aaeec71f6..9f50b2c7b3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,13 +7,14 @@ on: branches: - master jobs: - build: - name: Test + test: + name: ${{ matrix.type }} tests on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-20.04, macos-11, windows-2019] + type: [unit, smoke] node-version: [16.x] steps: - name: Checkout Release from lens @@ -51,25 +52,16 @@ jobs: retry_on: error command: make node_modules - - run: make build-npm - name: Generate npm package - - - uses: nick-fields/retry@v2 - name: Build bundled extensions - with: - timeout_minutes: 15 - max_attempts: 3 - retry_on: error - command: make -j2 build-extensions - - run: make test name: Run tests + if: ${{ matrix.type == 'unit' }} - run: make test-extensions name: Run In-tree Extension tests + if: ${{ matrix.type == 'unit' }} - run: make ci-validate-dev - if: contains(github.event.pull_request.labels.*.name, 'dependencies') + if: ${{ contains(github.event.pull_request.labels.*.name, 'dependencies') && matrix.type == 'unit' }} name: Validate dev mode will work - name: Install integration test dependencies @@ -77,7 +69,7 @@ jobs: uses: medyagh/setup-minikube@master with: minikube-version: latest - if: runner.os == 'Linux' + if: ${{ runner.os == 'Linux' && matrix.type == 'smoke' }} - run: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' make integration name: Run Linux integration tests @@ -88,11 +80,11 @@ jobs: shell: bash env: ELECTRON_BUILDER_EXTRA_ARGS: "--x64 --arm64" - if: runner.os == 'macOS' + if: ${{ runner.os == 'macOS' && matrix.type == 'smoke' }} - run: make integration name: Run Windows integration tests shell: bash env: ELECTRON_BUILDER_EXTRA_ARGS: "--x64 --ia32" - if: runner.os == 'Windows' + if: ${{ runner.os == 'Windows' && matrix.type == 'smoke' }} diff --git a/integration/__tests__/app-preferences.tests.ts b/integration/__tests__/app-preferences.tests.ts index 2054a5342d..6c0c8fd65b 100644 --- a/integration/__tests__/app-preferences.tests.ts +++ b/integration/__tests__/app-preferences.tests.ts @@ -13,7 +13,8 @@ import type { ElectronApplication, Page } from "playwright"; import * as utils from "../helpers/utils"; describe("preferences page tests", () => { - let window: Page, cleanup: () => Promise; + let window: Page; + let cleanup: undefined | (() => Promise); beforeEach(async () => { let app: ElectronApplication; @@ -30,7 +31,7 @@ describe("preferences page tests", () => { }, 10*60*1000); afterEach(async () => { - await cleanup(); + await cleanup?.(); }, 10*60*1000); it('shows "preferences" and can navigate through the tabs', async () => { diff --git a/integration/__tests__/cluster-pages.tests.ts b/integration/__tests__/cluster-pages.tests.ts index 15e72f0d80..e1474a4332 100644 --- a/integration/__tests__/cluster-pages.tests.ts +++ b/integration/__tests__/cluster-pages.tests.ts @@ -18,7 +18,9 @@ import { pipeline } from "@ogre-tools/fp"; const TEST_NAMESPACE = "integration-tests"; utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { - let window: Page, cleanup: () => Promise, frame: Frame; + let window: Page; + let cleanup: undefined | (() => Promise); + let frame: Frame; beforeEach(async () => { ({ window, cleanup } = await utils.start()); @@ -28,7 +30,7 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { }, 10 * 60 * 1000); afterEach(async () => { - await cleanup(); + await cleanup?.(); }, 10 * 60 * 1000); it("shows cluster context menu in sidebar", async () => { diff --git a/integration/__tests__/command-palette.tests.ts b/integration/__tests__/command-palette.tests.ts index 8356379b60..7b4d675ef4 100644 --- a/integration/__tests__/command-palette.tests.ts +++ b/integration/__tests__/command-palette.tests.ts @@ -7,15 +7,17 @@ import type { ElectronApplication, Page } from "playwright"; import * as utils from "../helpers/utils"; describe("Lens command palette", () => { - let window: Page, cleanup: () => Promise, app: ElectronApplication; - + let window: Page; + let cleanup: undefined | (() => Promise); + let app: ElectronApplication; + beforeEach(async () => { ({ window, cleanup, app } = await utils.start()); await utils.clickWelcomeButton(window); }, 10*60*1000); afterEach(async () => { - await cleanup(); + await cleanup?.(); }, 10*60*1000); describe("menu", () => { diff --git a/integration/helpers/utils.ts b/integration/helpers/utils.ts index f8eca46c26..167b3b353f 100644 --- a/integration/helpers/utils.ts +++ b/integration/helpers/utils.ts @@ -10,6 +10,7 @@ import * as uuid from "uuid"; import type { ElectronApplication, Frame, Page } from "playwright"; import { _electron as electron } from "playwright"; import { noop } from "lodash"; +import { disposer } from "../../src/common/utils"; export const appPaths: Partial> = { "win32": "./dist/win-unpacked/OpenLens.exe", @@ -26,19 +27,46 @@ export function describeIf(condition: boolean) { } async function getMainWindow(app: ElectronApplication, timeout = 50_000): Promise { - const deadline = Date.now() + timeout; + return new Promise((resolve, reject) => { + const cleanup = disposer(); + let stdoutBuf = ""; + + const onWindow = (page: Page) => { + console.log(`Page opened: ${page.url()}`); - for (; Date.now() < deadline;) { - for (const page of app.windows()) { if (page.url().startsWith("http://localhost")) { - return page; + cleanup(); + console.log(stdoutBuf); + resolve(page); } - } + }; - await new Promise(resolve => setTimeout(resolve, 2_000)); - } + app.on("window", onWindow); + cleanup.push(() => app.off("window", onWindow)); - throw new Error(`Lens did not open the main window within ${timeout}ms`); + const onClose = () => { + cleanup(); + reject(new Error("App has unnexpectedly closed")); + }; + + app.on("close", onClose); + cleanup.push(() => app.off("close", onClose)); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const stdout = app.process().stdout!; + const onData = (chunk: any) => stdoutBuf += chunk.toString(); + + stdout.on("data", onData); + cleanup.push(() => stdout.off("data", onData)); + + const timeoutId = setTimeout(() => { + cleanup(); + console.log(stdoutBuf); + reject(new Error(`Lens did not open the main window within ${timeout}ms`)); + }, timeout); + + cleanup.push(() => clearTimeout(timeoutId)); + }); } async function attemptStart() { @@ -57,7 +85,7 @@ async function attemptStart() { ...process.env, }, timeout: 100_000, - } as Parameters[0]); + }); try { const window = await getMainWindow(app); @@ -78,6 +106,8 @@ async function attemptStart() { } export async function start() { + console.log(process.env); + // this is an attempted workaround for an issue with playwright not always getting the main window when using Electron 14.2.4 (observed on windows) for (let i = 0; ; i++) { try { diff --git a/package.json b/package.json index 5c1a762906..cb9eefdd86 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "6.1.2", + "version": "6.1.3", "main": "static/build/main.js", "copyright": "© 2022 OpenLens Authors", "license": "MIT", @@ -279,8 +279,7 @@ "request-promise-native": "^1.0.9", "rfc6902": "^4.0.2", "selfsigned": "^2.1.1", - "semver": "^7.3.7", - "shell-env": "^3.0.1", + "semver": "^7.3.8", "spdy": "^4.0.2", "tar": "^6.1.11", "tcp-port-used": "^1.0.2", diff --git a/scripts/create-release-pr.ts b/scripts/create-release-pr.ts index be4a13d426..202f340969 100755 --- a/scripts/create-release-pr.ts +++ b/scripts/create-release-pr.ts @@ -296,6 +296,7 @@ const createPrArgs = [ "--base", prBase, "--title", `Release ${newVersion.format()}`, "--label", "skip-changelog", + "--label", "release", "--body-file", "-", ]; diff --git a/scripts/tag-release.sh b/scripts/tag-release.sh deleted file mode 100755 index ce2ddffb2b..0000000000 --- a/scripts/tag-release.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -while [[ $# -gt 0 ]]; do - key="$1" - - case $key in - -f|--force) - FORCE="--force" - shift # past argument - ;; - esac -done - -if [[ `git branch --show-current` =~ ^release/v ]] -then - VERSION_STRING=$(cat package.json | jq '.version' -r | xargs printf "v%s") - git tag ${VERSION_STRING} ${FORCE} - git push ${GIT_REMOTE:-origin} ${VERSION_STRING} ${FORCE} -else - echo "You must be in a release branch" -fi diff --git a/src/common/utils/objects.ts b/src/common/utils/objects.ts index 8fe95be46d..9b6355d3e7 100644 --- a/src/common/utils/objects.ts +++ b/src/common/utils/objects.ts @@ -17,6 +17,7 @@ export function keys(obj: Record): K[] { return Object.keys(obj) as K[]; } +export function entries(obj: Partial> | null | undefined): [K, V][]; export function entries(obj: Partial> | null | undefined): [K, V][]; export function entries(obj: Record | null | undefined): [K, V][]; diff --git a/src/features/helm-releases/__snapshots__/showing-details-for-helm-release.test.ts.snap b/src/features/helm-releases/__snapshots__/showing-details-for-helm-release.test.ts.snap index ced68044ac..b1b31c5e55 100644 --- a/src/features/helm-releases/__snapshots__/showing-details-for-helm-release.test.ts.snap +++ b/src/features/helm-releases/__snapshots__/showing-details-for-helm-release.test.ts.snap @@ -5523,11 +5523,6 @@ exports[`showing details for helm release given application is started when navi > Namespace -
- Age -
some-namespace
-
- 0s -
@@ -6747,11 +6737,6 @@ exports[`showing details for helm release given application is started when navi > Namespace -
- Age -
some-namespace
-
- 0s -
@@ -7971,11 +7951,6 @@ exports[`showing details for helm release given application is started when navi > Namespace -
- Age -
some-namespace
-
- 0s -
@@ -9022,11 +8992,6 @@ exports[`showing details for helm release given application is started when navi > Namespace -
- Age -
some-namespace
-
- 0s -
@@ -10075,11 +10035,6 @@ exports[`showing details for helm release given application is started when navi > Namespace -
- Age -
some-namespace
-
- 0s -
@@ -11299,11 +11249,6 @@ exports[`showing details for helm release given application is started when navi > Namespace -
- Age -
some-namespace
-
- 0s -
diff --git a/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts b/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts index 490c67273e..f0eefd720c 100644 --- a/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts +++ b/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { AsyncResult } from "../../../../../common/utils/async-result"; import execHelmInjectable from "../../../exec-helm/exec-helm.injectable"; import yaml from "js-yaml"; -import type { KubeJsonApiData } from "../../../../../common/k8s-api/kube-json-api"; +import type { KubeJsonApiData, KubeJsonApiDataList } from "../../../../../common/k8s-api/kube-json-api"; const callForHelmManifestInjectable = getInjectable({ id: "call-for-helm-manifest", @@ -18,7 +18,7 @@ const callForHelmManifestInjectable = getInjectable({ name: string, namespace: string, kubeconfigPath: string, - ): Promise> => { + ): Promise> => { const result = await execHelm([ "get", "manifest", diff --git a/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.injectable.ts b/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.injectable.ts index 025aab6b4d..ce5c60d6b3 100644 --- a/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.injectable.ts +++ b/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.injectable.ts @@ -3,48 +3,37 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import callForKubeResourcesByManifestInjectable from "./call-for-kube-resources-by-manifest/call-for-kube-resources-by-manifest.injectable"; -import { groupBy, map } from "lodash/fp"; -import type { JsonObject } from "type-fest"; -import { pipeline } from "@ogre-tools/fp"; import callForHelmManifestInjectable from "./call-for-helm-manifest/call-for-helm-manifest.injectable"; +import type { KubeJsonApiData, KubeJsonApiDataList } from "../../../../common/k8s-api/kube-json-api"; +import type { AsyncResult } from "../../../../common/utils/async-result"; export type GetHelmReleaseResources = ( name: string, namespace: string, kubeconfigPath: string, - kubectlPath: string -) => Promise; +) => Promise>; const getHelmReleaseResourcesInjectable = getInjectable({ id: "get-helm-release-resources", instantiate: (di): GetHelmReleaseResources => { const callForHelmManifest = di.inject(callForHelmManifestInjectable); - const callForKubeResourcesByManifest = di.inject(callForKubeResourcesByManifestInjectable); - return async (name, namespace, kubeconfigPath, kubectlPath) => { + return async (name, namespace, kubeconfigPath) => { const result = await callForHelmManifest(name, namespace, kubeconfigPath); if (!result.callWasSuccessful) { - throw new Error(result.error); + return result; } - const results = await pipeline( - result.response, - - groupBy((item) => item.metadata.namespace || namespace), - - (x) => Object.entries(x), - - map(([namespace, manifest]) => - callForKubeResourcesByManifest(namespace, kubeconfigPath, kubectlPath, manifest), - ), - - promises => Promise.all(promises), - ); - - return results.flat(1); + return { + callWasSuccessful: true, + response: result.response.flatMap(item => ( + Array.isArray(item.items) + ? (item as KubeJsonApiDataList).items + : item as KubeJsonApiData + )), + }; }; }, }); diff --git a/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.test.ts b/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.test.ts index 6c1b1c0566..b10bb650f1 100644 --- a/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.test.ts +++ b/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.test.ts @@ -10,9 +10,10 @@ import type { ExecHelm } from "../../exec-helm/exec-helm.injectable"; import execHelmInjectable from "../../exec-helm/exec-helm.injectable"; import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; -import type { JsonObject } from "type-fest"; import type { ExecFileWithInput } from "./call-for-kube-resources-by-manifest/exec-file-with-input/exec-file-with-input.injectable"; import execFileWithInputInjectable from "./call-for-kube-resources-by-manifest/exec-file-with-input/exec-file-with-input.injectable"; +import type { AsyncResult } from "../../../../common/utils/async-result"; +import type { KubeJsonApiData } from "../../../../common/k8s-api/kube-json-api"; describe("get helm release resources", () => { let getHelmReleaseResources: GetHelmReleaseResources; @@ -36,14 +37,13 @@ describe("get helm release resources", () => { }); describe("when called", () => { - let actualPromise: Promise; + let actualPromise: Promise>; beforeEach(() => { actualPromise = getHelmReleaseResources( "some-release", "some-namespace", "/some-kubeconfig-path", - "/some-kubectl-path", ); }); @@ -65,14 +65,16 @@ describe("get helm release resources", () => { const actual = await actualPromise; - expect(actual).toEqual([]); + expect(actual).toEqual({ + callWasSuccessful: true, + response: [], + }); }); - describe("when call for manifest resolves", () => { - beforeEach(async () => { - await execHelmMock.resolve({ - callWasSuccessful: true, - response: `--- + it("when call to manifest resolves with resources, resolves with resources", async () => { + await execHelmMock.resolve({ + callWasSuccessful: true, + response: `--- apiVersion: v1 kind: SomeKind metadata: @@ -80,135 +82,63 @@ metadata: namespace: some-namespace --- apiVersion: v1 -kind: SomeKind +kind: SomeOtherKind metadata: name: some-resource-without-namespace --- apiVersion: v1 +kind: List +items: + - apiVersion: monitoring.coreos.com/v1 + kind: ServiceMonitor + metadata: + name: collection-sumologic-fluentd-logs + namespace: some-namespace +--- +apiVersion: v1 kind: SomeKind metadata: name: some-resource-with-different-namespace namespace: some-other-namespace --- `, - }); }); - it("calls for resources from each namespace separately using the manifest as input", () => { - expect(execFileWithStreamInputMock.mock.calls).toEqual([ - [ - { - filePath: "/some-kubectl-path", - commandArguments: ["get", "--kubeconfig", "/some-kubeconfig-path", "-f", "-", "--namespace", "some-namespace", "--output", "json"], - input: `--- -apiVersion: v1 -kind: SomeKind -metadata: - name: some-resource-with-same-namespace - namespace: some-namespace ---- -apiVersion: v1 -kind: SomeKind -metadata: - name: some-resource-without-namespace ---- -`, + expect(await actualPromise).toEqual({ + callWasSuccessful: true, + response: [ + { + apiVersion: "v1", + kind: "SomeKind", + metadata: { + name: "some-resource-with-same-namespace", + namespace: "some-namespace", }, - ], - - [ - { - filePath: "/some-kubectl-path", - commandArguments: ["get", "--kubeconfig", "/some-kubeconfig-path", "-f", "-", "--namespace", "some-other-namespace", "--output", "json"], - input: `--- -apiVersion: v1 -kind: SomeKind -metadata: - name: some-resource-with-different-namespace - namespace: some-other-namespace ---- -`, + }, + { + apiVersion: "v1", + kind: "SomeOtherKind", + metadata: { + name: "some-resource-without-namespace", }, - ], - ]); - }); - - it("when all calls for resources resolve, resolves with combined result", async () => { - await execFileWithStreamInputMock.resolveSpecific( - ([{ commandArguments }]) => - commandArguments.includes("some-namespace"), - { - callWasSuccessful: true, - - response: JSON.stringify({ - items: [{ some: "item" }], - - kind: "List", - - metadata: { - resourceVersion: "", - selfLink: "", - }, - }), }, - ); - - await execFileWithStreamInputMock.resolveSpecific( - ([{ commandArguments }]) => - commandArguments.includes("some-other-namespace"), { - callWasSuccessful: true, - - response: JSON.stringify({ - items: [{ some: "other-item" }], - - kind: "List", - - metadata: { - resourceVersion: "", - selfLink: "", - }, - }), + apiVersion: "monitoring.coreos.com/v1", + kind: "ServiceMonitor", + metadata: { + name: "collection-sumologic-fluentd-logs", + namespace: "some-namespace", + }, }, - ); - - const actual = await actualPromise; - - expect(actual).toEqual([{ some: "item" }, { some: "other-item" }]); - }); - - it("given some call fails, when all calls have finished, rejects with failure", async () => { - await execFileWithStreamInputMock.resolveSpecific( - ([{ commandArguments }]) => - commandArguments.includes("some-namespace"), - { - callWasSuccessful: true, - - response: JSON.stringify({ - items: [{ some: "item" }], - - kind: "List", - - metadata: { - resourceVersion: "", - selfLink: "", - }, - }), + apiVersion: "v1", + kind: "SomeKind", + metadata: { + name: "some-resource-with-different-namespace", + namespace: "some-other-namespace", + }, }, - ); - - execFileWithStreamInputMock.resolveSpecific( - ([{ commandArguments }]) => - commandArguments.includes("some-other-namespace"), - - { - callWasSuccessful: false, - error: "some-error", - }, - ); - - return expect(actualPromise).rejects.toEqual(expect.any(Error)); + ], }); }); }); diff --git a/src/main/helm/helm-service/get-helm-release.injectable.ts b/src/main/helm/helm-service/get-helm-release.injectable.ts index 3733b2816d..71d3fe6184 100644 --- a/src/main/helm/helm-service/get-helm-release.injectable.ts +++ b/src/main/helm/helm-service/get-helm-release.injectable.ts @@ -19,12 +19,10 @@ const getHelmReleaseInjectable = getInjectable({ return async (cluster: Cluster, releaseName: string, namespace: string) => { const kubeconfigPath = await cluster.getProxyKubeconfigPath(); - const kubectl = await cluster.ensureKubectl(); - const kubectlPath = await kubectl.getPath(); logger.debug("Fetch release"); - const args = [ + const result = await execHelm([ "status", releaseName, "--namespace", @@ -33,11 +31,11 @@ const getHelmReleaseInjectable = getInjectable({ kubeconfigPath, "--output", "json", - ]; - - const result = await execHelm(args); + ]); if (!result.callWasSuccessful) { + logger.warn(`Failed to exectute helm: ${result.error}`); + return undefined; } @@ -47,14 +45,22 @@ const getHelmReleaseInjectable = getInjectable({ return undefined; } - release.resources = await getHelmReleaseResources( + const resourcesResult = await getHelmReleaseResources( releaseName, namespace, kubeconfigPath, - kubectlPath, ); - return release; + if (!resourcesResult.callWasSuccessful) { + logger.warn(`Failed to get helm release resources: ${resourcesResult.error}`); + + return undefined; + } + + return { + ...release, + resources: resourcesResult.response, + }; }; }, diff --git a/src/main/kubectl/kubectl.ts b/src/main/kubectl/kubectl.ts index 182d540692..2c99beec22 100644 --- a/src/main/kubectl/kubectl.ts +++ b/src/main/kubectl/kubectl.ts @@ -9,7 +9,7 @@ import { promiseExecFile } from "../../common/utils/promise-exec"; import logger from "../logger"; import { ensureDir, pathExists } from "fs-extra"; import * as lockFile from "proper-lockfile"; -import { SemVer } from "semver"; +import { SemVer, coerce } from "semver"; import { defaultPackageMirror, packageMirrors } from "../../common/user-store/preferences-helpers"; import got from "got/dist/source"; import { promisify } from "util"; @@ -45,11 +45,12 @@ export class Kubectl { constructor(protected readonly dependencies: KubectlDependencies, clusterVersion: string) { let version: SemVer; + const bundledVersion = new SemVer(this.dependencies.bundledKubectlVersion); try { version = new SemVer(clusterVersion); } catch { - version = new SemVer(this.dependencies.bundledKubectlVersion); + version = bundledVersion; } const fromMajorMinor = this.dependencies.kubectlVersionMap.get(`${version.major}.${version.minor}`); @@ -62,7 +63,10 @@ export class Kubectl { this.kubectlVersion = fromMajorMinor; logger.debug(`Set kubectl version ${this.kubectlVersion} for cluster version ${clusterVersion} using version map`); } else { - this.kubectlVersion = version.format(); + /* this is the version (without possible prelease tag) to get from the download mirror */ + const ver = coerce(version.format()) ?? bundledVersion; + + this.kubectlVersion = ver.format(); logger.debug(`Set kubectl version ${this.kubectlVersion} for cluster version ${clusterVersion} using fallback`); } diff --git a/src/main/router/create-handler-for-route.injectable.ts b/src/main/router/create-handler-for-route.injectable.ts new file mode 100644 index 0000000000..60f62fd282 --- /dev/null +++ b/src/main/router/create-handler-for-route.injectable.ts @@ -0,0 +1,80 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { ServerResponse } from "http"; +import loggerInjectable from "../../common/logger.injectable"; +import { object } from "../../common/utils"; +import type { LensApiRequest, Route } from "./route"; +import { contentTypes } from "./router-content-types"; + +export type RouteHandler = (request: LensApiRequest, response: ServerResponse) => Promise; +export type CreateHandlerForRoute = (route: Route) => RouteHandler; + +interface LensServerResponse { + statusCode: number; + content: any; + headers: { + [name: string]: string; + }; +} + +const writeServerResponseFor = (serverResponse: ServerResponse) => ({ + statusCode, + content, + headers, +}: LensServerResponse) => { + serverResponse.statusCode = statusCode; + + for (const [name, value] of object.entries(headers)) { + serverResponse.setHeader(name, value); + } + + if (content instanceof Buffer) { + serverResponse.write(content); + serverResponse.end(); + } else if (content) { + serverResponse.end(content); + } else { + serverResponse.end(); + } +}; + +const createHandlerForRouteInjectable = getInjectable({ + id: "create-handler-for-route", + instantiate: (di): CreateHandlerForRoute => { + const logger = di.inject(loggerInjectable); + + return (route) => async (request, response) => { + const writeServerResponse = writeServerResponseFor(response); + + try { + const result = await route.handler(request); + + if (!result) { + const mappedResult = contentTypes.txt.resultMapper({ + statusCode: 204, + response: undefined, + }); + + writeServerResponse(mappedResult); + } else if (!result.proxy) { + const contentType = result.contentType || contentTypes.json; + + writeServerResponse(contentType.resultMapper(result)); + } + } catch(error) { + const mappedResult = contentTypes.txt.resultMapper({ + statusCode: 500, + error: error ? String(error) : "unknown error", + }); + + logger.error(`[ROUTER]: route ${route.path}, called with ${request.path}, threw an error`, error); + writeServerResponse(mappedResult); + } + }; + }, +}); + +export default createHandlerForRouteInjectable; diff --git a/src/main/router/router.injectable.ts b/src/main/router/router.injectable.ts index 23d6c78fc4..4ac7f03f91 100644 --- a/src/main/router/router.injectable.ts +++ b/src/main/router/router.injectable.ts @@ -7,6 +7,7 @@ import { getInjectable, getInjectionToken, lifecycleEnum } from "@ogre-tools/inj import { Router } from "./router"; import parseRequestInjectable from "./parse-request.injectable"; import type { Route } from "./route"; +import createHandlerForRouteInjectable from "./create-handler-for-route.injectable"; export const routeInjectionToken = getInjectionToken>({ id: "route-injection-token", @@ -28,13 +29,11 @@ export function getRouteInjectable< const routerInjectable = getInjectable({ id: "router", - instantiate: (di) => { - const routes = di.injectMany(routeInjectionToken); - - return new Router(routes, { - parseRequest: di.inject(parseRequestInjectable), - }); - }, + instantiate: (di) => new Router({ + parseRequest: di.inject(parseRequestInjectable), + routes: di.injectMany(routeInjectionToken), + createHandlerForRoute: di.inject(createHandlerForRouteInjectable), + }), }); export default routerInjectable; diff --git a/src/main/router/router.ts b/src/main/router/router.ts index 5b07822142..9cb070ed1d 100644 --- a/src/main/router/router.ts +++ b/src/main/router/router.ts @@ -5,12 +5,11 @@ import Call from "@hapi/call"; import type http from "http"; -import { toPairs } from "lodash/fp"; import type { Cluster } from "../../common/cluster/cluster"; -import { contentTypes } from "./router-content-types"; -import type { LensApiRequest, LensApiResult, Route } from "./route"; +import type { LensApiRequest, Route } from "./route"; import type { ServerIncomingMessage } from "../lens-proxy/lens-proxy"; import type { ParseRequest } from "./parse-request.injectable"; +import type { CreateHandlerForRoute, RouteHandler } from "./create-handler-for-route.injectable"; export interface RouterRequestOpts { req: http.IncomingMessage; @@ -22,15 +21,17 @@ export interface RouterRequestOpts { interface Dependencies { parseRequest: ParseRequest; + createHandlerForRoute: CreateHandlerForRoute; + readonly routes: Route[]; } export class Router { - protected router = new Call.Router>(); + private readonly router = new Call.Router(); - constructor(routes: Route[], private dependencies: Dependencies) { - routes.forEach(route => { - this.router.add({ method: route.method, path: route.path }, handleRoute(route)); - }); + constructor(private readonly dependencies: Dependencies) { + for (const route of this.dependencies.routes) { + this.router.add({ method: route.method, path: route.path }, this.dependencies.createHandlerForRoute(route)); + } } public async route(cluster: Cluster | undefined, req: ServerIncomingMessage, res: http.ServerResponse): Promise { @@ -52,7 +53,6 @@ export class Router { protected async getRequest(opts: RouterRequestOpts): Promise> { const { req, res, url, cluster, params } = opts; - const { payload } = await this.dependencies.parseRequest(req, null, { parse: true, output: "data", @@ -70,77 +70,3 @@ export class Router { }; } } - -const handleRoute = (route: Route) => async (request: LensApiRequest, response: http.ServerResponse) => { - let result: LensApiResult | void; - - const writeServerResponse = writeServerResponseFor(response); - - try { - result = await route.handler(request); - } catch(error) { - const mappedResult = contentTypes.txt.resultMapper({ - statusCode: 500, - error: error ? String(error) : "unknown error", - }); - - writeServerResponse(mappedResult); - - return; - } - - if (!result) { - const mappedResult = contentTypes.txt.resultMapper({ - statusCode: 204, - response: undefined, - }); - - writeServerResponse(mappedResult); - - return; - } - - if (result.proxy) { - return; - } - - const contentType = result.contentType || contentTypes.json; - - const mappedResult = contentType.resultMapper(result); - - writeServerResponse(mappedResult); -}; - -const writeServerResponseFor = - (serverResponse: http.ServerResponse) => - ({ - statusCode, - content, - headers, - }: { - statusCode: number; - content: any; - headers: { [name: string]: string }; - }) => { - serverResponse.statusCode = statusCode; - - const headerPairs = toPairs(headers); - - headerPairs.forEach(([name, value]) => { - serverResponse.setHeader(name, value); - }); - - if (content instanceof Buffer) { - serverResponse.write(content); - - serverResponse.end(); - - return; - } - - if (content) { - serverResponse.end(content); - } else { - serverResponse.end(); - } - }; diff --git a/src/main/shell-session/shell-session.ts b/src/main/shell-session/shell-session.ts index 42286ee24d..8176d090fa 100644 --- a/src/main/shell-session/shell-session.ts +++ b/src/main/shell-session/shell-session.ts @@ -10,7 +10,7 @@ import { shellEnv } from "../utils/shell-env"; import { app } from "electron"; import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars"; import path from "path"; -import os from "os"; +import os, { userInfo } from "os"; import { isMac, isWindows } from "../../common/vars"; import { UserStore } from "../../common/user-store"; import * as pty from "node-pty"; @@ -313,9 +313,9 @@ export abstract class ShellSession { } protected async getShellEnv() { - const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(await shellEnv()))); - const pathStr = [await this.kubectlBinDirP, ...this.getPathEntries(), process.env.PATH].join(path.delimiter); const shell = UserStore.getInstance().resolvedShell; + const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(await shellEnv(shell || userInfo().shell)))); + const pathStr = [await this.kubectlBinDirP, ...this.getPathEntries(), process.env.PATH].join(path.delimiter); delete env.DEBUG; // don't pass DEBUG into shells diff --git a/src/main/utils/shell-env.ts b/src/main/utils/shell-env.ts index abcbeb5bed..6f4cce0be8 100644 --- a/src/main/utils/shell-env.ts +++ b/src/main/utils/shell-env.ts @@ -3,11 +3,85 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import shellEnvironment from "shell-env"; +import { spawn } from "child_process"; +import { randomUUID } from "crypto"; +import { basename } from "path"; +import { isWindows } from "../../common/vars"; import logger from "../logger"; export type EnvironmentVariables = Partial>; + +async function unixShellEnvironment(shell: string): Promise { + const runAsNode = process.env["ELECTRON_RUN_AS_NODE"]; + const noAttach = process.env["ELECTRON_NO_ATTACH_CONSOLE"]; + const env = { + ...process.env, + ELECTRON_RUN_AS_NODE: "1", + ELECTRON_NO_ATTACH_CONSOLE: "1", + }; + const mark = randomUUID().replace(/-/g, ""); + const regex = new RegExp(`${mark}(.*)${mark}`); + const shellName = basename(shell); + let command: string; + let shellArgs: string[]; + + if (/^pwsh(-preview)?$/.test(shellName)) { + // Older versions of PowerShell removes double quotes sometimes so we use "double single quotes" which is how + // you escape single quotes inside of a single quoted string. + command = `& '${process.execPath}' -p '''${mark}'' + JSON.stringify(process.env) + ''${mark}'''`; + shellArgs = ["-Login", "-Command"]; + } else { + command = `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`; + + if (shellName === "tcsh") { + shellArgs = ["-ic"]; + } else { + shellArgs = ["-ilc"]; + } + } + + return new Promise((resolve, reject) => { + const shellProcess = spawn(shell, [...shellArgs, command], { + detached: true, + stdio: ["ignore", "pipe", "pipe"], + env, + }); + const stdout: Buffer[] = []; + + shellProcess.on("error", (err) => reject(err)); + shellProcess.stdout.on("data", b => stdout.push(b)); + shellProcess.on("close", (code, signal) => { + if (code || signal) { + return reject(new Error(`Unexpected return code from spawned shell (code: ${code}, signal: ${signal})`)); + } + + try { + const rawOutput = Buffer.concat(stdout).toString("utf-8"); + const match = regex.exec(rawOutput); + const strippedRawOutput = match ? match[1] : "{}"; + const resolvedEnv = JSON.parse(strippedRawOutput); + + if (runAsNode) { + resolvedEnv["ELECTRON_RUN_AS_NODE"] = runAsNode; + } else { + delete resolvedEnv["ELECTRON_RUN_AS_NODE"]; + } + + if (noAttach) { + resolvedEnv["ELECTRON_NO_ATTACH_CONSOLE"] = noAttach; + } else { + delete resolvedEnv["ELECTRON_NO_ATTACH_CONSOLE"]; + } + + resolve(resolvedEnv); + } catch(err) { + reject(err); + } + }); + }); +} + let shellSyncFailed = false; /** @@ -16,20 +90,18 @@ let shellSyncFailed = false; * Subsequent calls after such a timeout simply log an error message without trying * to get the environment, unless forceRetry is true. * @param shell the shell to get the environment from - * @param forceRetry if true will always try to get the environment, otherwise if - * a previous call to this function failed then this call will fail too. * @returns object containing the shell's environment variables. An empty object is * returned if the call fails. */ -export async function shellEnv(shell?: string, forceRetry = false) : Promise { - if (forceRetry) { - shellSyncFailed = false; +export async function shellEnv(shell: string) : Promise { + if (isWindows) { + return {}; } - + if (!shellSyncFailed) { try { return await Promise.race([ - shellEnvironment(shell), + unixShellEnvironment(shell), new Promise((_resolve, reject) => setTimeout(() => { reject(new Error("Resolving shell environment is taking very long. Please review your shell configuration.")); }, 30_000)), diff --git a/src/renderer/components/+helm-releases/release-details/release-details-content.tsx b/src/renderer/components/+helm-releases/release-details/release-details-content.tsx index b2bacbd479..02e3255304 100644 --- a/src/renderer/components/+helm-releases/release-details/release-details-content.tsx +++ b/src/renderer/components/+helm-releases/release-details/release-details-content.tsx @@ -19,7 +19,6 @@ import { kebabCase } from "lodash/fp"; import { Badge } from "../../badge"; import { SubTitle } from "../../layout/sub-title"; import { Table, TableCell, TableHead, TableRow } from "../../table"; -import { ReactiveDuration } from "../../duration/reactive-duration"; import { Checkbox } from "../../checkbox"; import { MonacoEditor } from "../../monaco-editor"; import { Spinner } from "../../spinner"; @@ -131,12 +130,10 @@ const ResourceGroup = ({ Name {isNamespaced && Namespace} - - Age {resources.map( - ({ creationTimestamp, detailsUrl, name, namespace, uid }) => ( + ({ detailsUrl, name, namespace, uid }) => ( {detailsUrl ? {name} : name} @@ -145,10 +142,6 @@ const ResourceGroup = ({ {isNamespaced && ( {namespace} )} - - - - ), )} diff --git a/src/renderer/components/+helm-releases/release-details/release-details-model/call-for-helm-release/call-for-helm-release.injectable.ts b/src/renderer/components/+helm-releases/release-details/release-details-model/call-for-helm-release/call-for-helm-release.injectable.ts index bfcc947d08..a8e945e92d 100644 --- a/src/renderer/components/+helm-releases/release-details/release-details-model/call-for-helm-release/call-for-helm-release.injectable.ts +++ b/src/renderer/components/+helm-releases/release-details/release-details-model/call-for-helm-release/call-for-helm-release.injectable.ts @@ -11,7 +11,7 @@ import type { AsyncResult } from "../../../../../../common/utils/async-result"; export interface DetailedHelmRelease { release: HelmReleaseDto; - details: HelmReleaseDetails; + details?: HelmReleaseDetails; } export type CallForHelmRelease = ( diff --git a/src/renderer/components/+helm-releases/release-details/release-details-model/release-details-model.injectable.tsx b/src/renderer/components/+helm-releases/release-details/release-details-model/release-details-model.injectable.tsx index ebedbcdcdf..e5ae3a5c0d 100644 --- a/src/renderer/components/+helm-releases/release-details/release-details-model/release-details-model.injectable.tsx +++ b/src/renderer/components/+helm-releases/release-details/release-details-model/release-details-model.injectable.tsx @@ -228,12 +228,12 @@ export class ReleaseDetailsModel { } @computed get notes() { - return this.details.info.notes; + return this.details?.info.notes ?? ""; } @computed get groupedResources(): MinimalResourceGroup[] { return pipeline( - this.details.resources, + this.details?.resources ?? [], groupBy((resource) => resource.kind), (grouped) => Object.entries(grouped), @@ -277,20 +277,17 @@ export interface MinimalResource { name: string; namespace: string | undefined; detailsUrl: string | undefined; - creationTimestamp: string | undefined; } const toMinimalResourceFor = (getResourceDetailsUrl: GetResourceDetailsUrl, kind: string) => (resource: KubeJsonApiData): MinimalResource => { - const { creationTimestamp, name, namespace, uid } = resource.metadata; + const { name, namespace, uid } = resource.metadata; return { uid, name, namespace, - creationTimestamp, - detailsUrl: getResourceDetailsUrl( kind, resource.apiVersion, diff --git a/yarn.lock b/yarn.lock index 5184d88236..61f770d8ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4518,17 +4518,6 @@ cross-fetch@^3.0.4: dependencies: node-fetch "2.6.7" -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -4808,11 +4797,6 @@ default-gateway@^6.0.3: dependencies: execa "^5.0.0" -default-shell@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/default-shell/-/default-shell-1.0.1.tgz#752304bddc6174f49eb29cb988feea0b8813c8bc" - integrity sha1-dSMEvdxhdPSespy5iP7qC4gTyLw= - defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -5845,19 +5829,6 @@ execa@5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -6416,7 +6387,7 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^4.0.0, get-stream@^4.1.0: +get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== @@ -7428,11 +7399,6 @@ is-shared-array-buffer@^1.0.2: dependencies: call-bind "^1.0.2" -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -9305,11 +9271,6 @@ neo-async@^2.6.0, neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - no-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" @@ -9574,13 +9535,6 @@ npm-registry-fetch@^13.0.0, npm-registry-fetch@^13.0.1, npm-registry-fetch@^13.3 npm-package-arg "^9.0.1" proc-log "^2.0.0" -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -9605,7 +9559,6 @@ npm@^8.19.2: "@npmcli/fs" "^2.1.0" "@npmcli/map-workspaces" "^2.0.3" "@npmcli/package-json" "^2.0.0" - "@npmcli/promise-spawn" "^3.0.0" "@npmcli/run-script" "^4.2.1" abbrev "~1.1.1" archy "~1.0.0" @@ -9616,7 +9569,6 @@ npm@^8.19.2: cli-table3 "^0.6.2" columnify "^1.6.0" fastest-levenshtein "^1.0.12" - fs-minipass "^2.1.0" glob "^8.0.1" graceful-fs "^4.2.10" hosted-git-info "^5.1.0" @@ -9636,7 +9588,6 @@ npm@^8.19.2: libnpmteam "^4.0.4" libnpmversion "^3.0.7" make-fetch-happen "^10.2.0" - minimatch "^5.1.0" minipass "^3.1.6" minipass-pipeline "^1.2.4" mkdirp "^1.0.4" @@ -9896,11 +9847,6 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -10094,11 +10040,6 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" @@ -11346,7 +11287,7 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: +semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -11363,6 +11304,13 @@ semver@^7.0.0, semver@^7.1.1, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semve dependencies: lru-cache "^6.0.0" +semver@^7.3.8: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + semver@~7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -11486,13 +11434,6 @@ sharp@^0.31.0: tar-fs "^2.1.1" tunnel-agent "^0.6.0" -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -11500,25 +11441,11 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-env@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shell-env/-/shell-env-3.0.1.tgz#515a62f6cbd5e139365be2535745e8e53438ce77" - integrity sha512-b09fpMipAQ9ObwvIeKoQFLDXcEcCpYUUZanlad4OYQscw2I49C/u97OPQg9jWYo36bRDn62fbe07oWYqovIvKA== - dependencies: - default-shell "^1.0.1" - execa "^1.0.0" - strip-ansi "^5.2.0" - shell-quote@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" @@ -11551,7 +11478,7 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -11952,7 +11879,7 @@ strip-ansi@^3.0.0: dependencies: ansi-regex "^2.0.0" -strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== @@ -11981,11 +11908,6 @@ strip-color@^0.1.0: resolved "https://registry.yarnpkg.com/strip-color/-/strip-color-0.1.0.tgz#106f65d3d3e6a2d9401cac0eb0ce8b8a702b4f7b" integrity sha512-p9LsUieSjWNNAxVCXLeilaDlmuUOrDS5/dF9znM1nZc7EGX5+zEFC0bEevsNIaldjlks+2jns5Siz6F9iK6jwA== -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -13161,13 +13083,6 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"