diff --git a/lerna.json b/lerna.json index a712b8a78b..2d83ed90c6 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ "packages": [ "packages/*" ], - "version": "6.4.0-beta.17", + "version": "6.4.0", "npmClient": "yarn", "npmClientArgs": [ "--network-timeout=100000" diff --git a/packages/core/package.json b/packages/core/package.json index 6baf203523..c69d5db711 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -3,7 +3,7 @@ "productName": "", "description": "Lens Desktop Core", "homepage": "https://github.com/lensapp/lens", - "version": "6.4.0-beta.17", + "version": "6.4.0", "repository": { "type": "git", "url": "git+https://github.com/lensapp/lens.git" diff --git a/packages/core/src/common/__tests__/create-resource-stack.test.ts b/packages/core/src/common/__tests__/create-resource-stack.test.ts new file mode 100644 index 0000000000..340e5c86ee --- /dev/null +++ b/packages/core/src/common/__tests__/create-resource-stack.test.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { DiContainer } from "@ogre-tools/injectable"; +import kubectlApplyAllInjectable from "../../main/kubectl/kubectl-apply-all.injectable"; +import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; +import type { KubernetesCluster } from "../catalog-entities"; +import readDirectoryInjectable from "../fs/read-directory.injectable"; +import readFileInjectable from "../fs/read-file.injectable"; +import createResourceStackInjectable from "../k8s/create-resource-stack.injectable"; +import appPathsStateInjectable from "../app-paths/app-paths-state.injectable"; +import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; + +describe("create resource stack tests", () => { + let di: DiContainer; + let cluster: KubernetesCluster; + + beforeEach(async () => { + di = getDiForUnitTesting({ doGeneralOverrides: true }); + cluster = { + getId: () => "test-cluster", + } as any; + + di.override(readDirectoryInjectable, () => () => Promise.resolve(["file1"]) as any); + di.override(readFileInjectable, () => () => Promise.resolve("filecontents")); + di.override(appPathsStateInjectable, () => ({ + get: () => ({}), + })); + di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data"); + + }); + + describe("kubectlApplyFolder", () => { + it("returns response", async () => { + di.override(kubectlApplyAllInjectable, () => () => Promise.resolve({ + callWasSuccessful: true as const, + response: "success", + })); + + const createResourceStack = di.inject(createResourceStackInjectable); + const resourceStack = createResourceStack(cluster, "test"); + + const response = await resourceStack.kubectlApplyFolder("/foo/bar"); + + expect(response).toEqual("success"); + }); + + it("throws on error", async () => { + di.override(kubectlApplyAllInjectable, () => () => Promise.resolve({ + callWasSuccessful: false as const, + error: "No permissions", + })); + + const createResourceStack = di.inject(createResourceStackInjectable); + const resourceStack = createResourceStack(cluster, "test"); + + await expect(() => resourceStack.kubectlApplyFolder("/foo/bar")).rejects.toThrow("No permissions"); + }); + }); +}); diff --git a/packages/core/src/common/fs/fs.injectable.ts b/packages/core/src/common/fs/fs.injectable.ts index f80375095c..600e4eaa40 100644 --- a/packages/core/src/common/fs/fs.injectable.ts +++ b/packages/core/src/common/fs/fs.injectable.ts @@ -7,7 +7,7 @@ import type { ReadOptions } from "fs-extra"; import fse from "fs-extra"; /** - * NOTE: Add corrisponding a corrisponding override of this injecable in `src/test-utils/override-fs-with-fakes.ts` + * NOTE: Add corresponding override of this injectable in `src/test-utils/override-fs-with-fakes.ts` */ const fsInjectable = getInjectable({ id: "fs", diff --git a/packages/core/src/common/k8s/resource-stack.ts b/packages/core/src/common/k8s/resource-stack.ts index 771b48b413..5bdafdc58d 100644 --- a/packages/core/src/common/k8s/resource-stack.ts +++ b/packages/core/src/common/k8s/resource-stack.ts @@ -51,7 +51,7 @@ export class ResourceStack { this.dependencies.logger.warn(`[RESOURCE-STACK]: failed to apply resources: ${result.error}`); - return ""; + throw new Error(result.error); } /** diff --git a/packages/core/src/common/logger.injectable.ts b/packages/core/src/common/logger.injectable.ts index 82ff682c46..8e9dd2a6a7 100644 --- a/packages/core/src/common/logger.injectable.ts +++ b/packages/core/src/common/logger.injectable.ts @@ -3,13 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; +import { createLogger, format } from "winston"; import type { Logger } from "./logger"; -import winstonLoggerInjectable from "./winston-logger.injectable"; +import { loggerTransportInjectionToken } from "./logger/transports"; const loggerInjectable = getInjectable({ id: "logger", instantiate: (di): Logger => { - const baseLogger = di.inject(winstonLoggerInjectable); + const baseLogger = createLogger({ + format: format.combine( + format.splat(), + format.simple(), + ), + transports: di.injectMany(loggerTransportInjectionToken), + }); return { debug: (message, ...data) => baseLogger.debug(message, ...data), diff --git a/packages/core/src/common/vars/store-migration-version.injectable.ts b/packages/core/src/common/vars/store-migration-version.injectable.ts index 4a4b232da8..37169463ae 100644 --- a/packages/core/src/common/vars/store-migration-version.injectable.ts +++ b/packages/core/src/common/vars/store-migration-version.injectable.ts @@ -3,11 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { applicationInformationToken } from "./application-information-token"; const storeMigrationVersionInjectable = getInjectable({ id: "store-migration-version", - instantiate: (di) => di.inject(applicationInformationToken).version, + instantiate: () => "6.4.0", }); export default storeMigrationVersionInjectable; diff --git a/packages/core/src/common/winston-logger.injectable.ts b/packages/core/src/common/winston-logger.injectable.ts deleted file mode 100644 index ec3854d8b9..0000000000 --- a/packages/core/src/common/winston-logger.injectable.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { createLogger, format } from "winston"; -import { loggerTransportInjectionToken } from "./logger/transports"; - -const winstonLoggerInjectable = getInjectable({ - id: "winston-logger", - instantiate: (di) => createLogger({ - format: format.combine( - format.splat(), - format.simple(), - ), - transports: di.injectMany(loggerTransportInjectionToken), - }), -}); - -export default winstonLoggerInjectable; diff --git a/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts b/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts index 378f519bb7..5e2a4cd84d 100644 --- a/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts +++ b/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts @@ -8,8 +8,8 @@ import extensionLoaderInjectable from "../extension-loader/extension-loader.inje import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable"; import extensionsStoreInjectable from "../extensions-store/extensions-store.injectable"; import extensionInstallationStateStoreInjectable from "../extension-installation-state-store/extension-installation-state-store.injectable"; -import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable"; -import extensionPackageRootDirectoryInjectable from "../extension-installer/extension-package-root-directory/extension-package-root-directory.injectable"; +import installExtensionInjectable from "../install-extension/install-extension.injectable"; +import extensionPackageRootDirectoryInjectable from "../install-extension/extension-package-root-directory.injectable"; import readJsonFileInjectable from "../../common/fs/read-json-file.injectable"; import loggerInjectable from "../../common/logger.injectable"; import pathExistsInjectable from "../../common/fs/path-exists.injectable"; diff --git a/packages/core/src/extensions/extension-discovery/extension-discovery.test.ts b/packages/core/src/extensions/extension-discovery/extension-discovery.test.ts index d71f8c5292..77e9cd0623 100644 --- a/packages/core/src/extensions/extension-discovery/extension-discovery.test.ts +++ b/packages/core/src/extensions/extension-discovery/extension-discovery.test.ts @@ -7,7 +7,7 @@ import type { FSWatcher } from "chokidar"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import extensionDiscoveryInjectable from "../extension-discovery/extension-discovery.injectable"; import type { ExtensionDiscovery } from "../extension-discovery/extension-discovery"; -import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable"; +import installExtensionInjectable from "../install-extension/install-extension.injectable"; import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import { delay } from "../../renderer/utils"; import { observable, runInAction, when } from "mobx"; diff --git a/packages/core/src/extensions/extension-installer/extension-installer.injectable.ts b/packages/core/src/extensions/extension-installer/extension-installer.injectable.ts deleted file mode 100644 index 92b4436701..0000000000 --- a/packages/core/src/extensions/extension-installer/extension-installer.injectable.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import pathToNpmCliInjectable from "../../common/app-paths/path-to-npm-cli.injectable"; -import loggerInjectable from "../../common/logger.injectable"; -import { ExtensionInstaller } from "./extension-installer"; -import extensionPackageRootDirectoryInjectable from "./extension-package-root-directory/extension-package-root-directory.injectable"; - -const extensionInstallerInjectable = getInjectable({ - id: "extension-installer", - - instantiate: (di) => new ExtensionInstaller({ - extensionPackageRootDirectory: di.inject(extensionPackageRootDirectoryInjectable), - logger: di.inject(loggerInjectable), - pathToNpmCli: di.inject(pathToNpmCliInjectable), - }), -}); - -export default extensionInstallerInjectable; diff --git a/packages/core/src/extensions/extension-installer/extension-installer.ts b/packages/core/src/extensions/extension-installer/extension-installer.ts deleted file mode 100644 index 223477d0c4..0000000000 --- a/packages/core/src/extensions/extension-installer/extension-installer.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import AwaitLock from "await-lock"; -import child_process from "child_process"; -import type { Logger } from "../../common/logger"; - -const logModule = "[EXTENSION-INSTALLER]"; - -interface Dependencies { - readonly extensionPackageRootDirectory: string; - readonly logger: Logger; - readonly pathToNpmCli: string; -} - -const baseNpmInstallArgs = [ - "install", - "--audit=false", - "--fund=false", - // NOTE: we do not omit the `optional` dependencies because that is how we specify the non-bundled extensions - "--omit=dev", - "--omit=peer", - "--prefer-offline", -]; - -/** - * Installs dependencies for extensions - */ -export class ExtensionInstaller { - private readonly installLock = new AwaitLock(); - - constructor(private readonly dependencies: Dependencies) {} - - /** - * Install single package using npm - */ - installPackage = async (name: string): Promise => { - // Mutual exclusion to install packages in sequence - await this.installLock.acquireAsync(); - - try { - this.dependencies.logger.info(`${logModule} installing package from ${name} to ${this.dependencies.extensionPackageRootDirectory}`); - await this.npm(...baseNpmInstallArgs, name); - this.dependencies.logger.info(`${logModule} package ${name} installed to ${this.dependencies.extensionPackageRootDirectory}`); - } finally { - this.installLock.release(); - } - }; - - private npm(...args: string[]): Promise { - return new Promise((resolve, reject) => { - const child = child_process.fork(this.dependencies.pathToNpmCli, args, { - cwd: this.dependencies.extensionPackageRootDirectory, - silent: true, - env: {}, - }); - let stderr = ""; - - child.stderr?.on("data", data => { - stderr += String(data); - }); - - child.on("close", (code) => { - if (code !== 0) { - reject(new Error(stderr)); - } else { - resolve(); - } - }); - - child.on("error", error => { - reject(error); - }); - }); - } -} diff --git a/packages/core/src/extensions/extension-installer/install-extension/install-extension.injectable.ts b/packages/core/src/extensions/extension-installer/install-extension/install-extension.injectable.ts deleted file mode 100644 index 940c5987a5..0000000000 --- a/packages/core/src/extensions/extension-installer/install-extension/install-extension.injectable.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import extensionInstallerInjectable from "../extension-installer.injectable"; - -const installExtensionInjectable = getInjectable({ - id: "install-extension", - instantiate: (di) => di.inject(extensionInstallerInjectable).installPackage, -}); - -export default installExtensionInjectable; diff --git a/packages/core/src/extensions/extension-installer/extension-package-root-directory/extension-package-root-directory.injectable.ts b/packages/core/src/extensions/install-extension/extension-package-root-directory.injectable.ts similarity index 76% rename from packages/core/src/extensions/extension-installer/extension-package-root-directory/extension-package-root-directory.injectable.ts rename to packages/core/src/extensions/install-extension/extension-package-root-directory.injectable.ts index 72bd0ad8c2..ffa0a7666d 100644 --- a/packages/core/src/extensions/extension-installer/extension-package-root-directory/extension-package-root-directory.injectable.ts +++ b/packages/core/src/extensions/install-extension/extension-package-root-directory.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; const extensionPackageRootDirectoryInjectable = getInjectable({ id: "extension-package-root-directory", diff --git a/packages/core/src/extensions/install-extension/install-extension.injectable.ts b/packages/core/src/extensions/install-extension/install-extension.injectable.ts new file mode 100644 index 0000000000..ca46772eb3 --- /dev/null +++ b/packages/core/src/extensions/install-extension/install-extension.injectable.ts @@ -0,0 +1,111 @@ +/** + * 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 { fork } from "child_process"; +import AwaitLock from "await-lock"; +import pathToNpmCliInjectable from "../../common/app-paths/path-to-npm-cli.injectable"; +import extensionPackageRootDirectoryInjectable from "./extension-package-root-directory.injectable"; +import prefixedLoggerInjectable from "../../common/logger/prefixed-logger.injectable"; +import readJsonFileInjectable from "../../common/fs/read-json-file.injectable"; +import joinPathsInjectable from "../../common/path/join-paths.injectable"; +import type { PackageJson } from "../common-api"; +import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable"; +import { once } from "lodash"; +import { isErrnoException } from "../../common/utils"; + +const baseNpmInstallArgs = [ + "install", + "--save-optional", + "--audit=false", + "--fund=false", + // NOTE: we do not omit the `optional` dependencies because that is how we specify the non-bundled extensions + "--omit=dev", + "--omit=peer", + "--prefer-offline", +]; + +export type InstallExtension = (name: string) => Promise; + +const installExtensionInjectable = getInjectable({ + id: "install-extension", + instantiate: (di): InstallExtension => { + const pathToNpmCli = di.inject(pathToNpmCliInjectable); + const extensionPackageRootDirectory = di.inject(extensionPackageRootDirectoryInjectable); + const readJsonFile = di.inject(readJsonFileInjectable); + const writeJsonFile = di.inject(writeJsonFileInjectable); + const joinPaths = di.inject(joinPathsInjectable); + const logger = di.inject(prefixedLoggerInjectable, "EXTENSION-INSTALLER"); + + const forkNpm = (...args: string[]) => new Promise((resolve, reject) => { + const child = fork(pathToNpmCli, args, { + cwd: extensionPackageRootDirectory, + silent: true, + env: {}, + }); + let stderr = ""; + + child.stderr?.on("data", data => { + stderr += String(data); + }); + + child.on("close", (code) => { + if (code !== 0) { + reject(new Error(stderr)); + } else { + resolve(); + } + }); + + child.on("error", error => { + reject(error); + }); + }); + + const packageJsonPath = joinPaths(extensionPackageRootDirectory, "package.json"); + + /** + * NOTES: + * - We have to keep the `package.json` because `npm install` removes files from `node_modules` + * if they are no longer in the `package.json` + * - In v6.2.X we saved bundled extensions as `"dependencies"` and external extensions as + * `"optionalDependencies"` at startup. This was done because `"optionalDependencies"` can + * fail to install and that is OK. + * - We continue to maintain this behavior here by only installing new dependencies as + * `"optionalDependencies"` + */ + const fixupPackageJson = once(async () => { + try { + const packageJson = await readJsonFile(packageJsonPath) as PackageJson; + + delete packageJson.dependencies; + + await writeJsonFile(packageJsonPath, packageJson); + } catch (error) { + if (isErrnoException(error) && error.code === "ENOENT") { + return; + } + + throw error; + } + }); + + const installLock = new AwaitLock(); + + return async (name) => { + await installLock.acquireAsync(); + await fixupPackageJson(); + + try { + logger.info(`installing package for extension "${name}"`); + await forkNpm(...baseNpmInstallArgs, name); + logger.info(`installed package for extension "${name}"`); + } finally { + installLock.release(); + } + }; + }, +}); + +export default installExtensionInjectable; diff --git a/packages/core/src/renderer/bootstrap.tsx b/packages/core/src/renderer/bootstrap.tsx index 818cd033f7..88688d4a75 100644 --- a/packages/core/src/renderer/bootstrap.tsx +++ b/packages/core/src/renderer/bootstrap.tsx @@ -46,9 +46,7 @@ export async function bootstrap(di: DiContainer) { } try { - await initializeApp(() => { - unmountComponentAtNode(rootElem); - }); + await initializeApp(() => unmountComponentAtNode(rootElem)); } catch (error) { console.error(`[BOOTSTRAP]: view initialization error: ${error}`, { origin: location.href, diff --git a/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts b/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts index a9a923f860..c640264ee3 100644 --- a/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts +++ b/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts @@ -12,7 +12,6 @@ import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event. import loadExtensionsInjectable from "../../load-extensions.injectable"; import loggerInjectable from "../../../../common/logger.injectable"; import showErrorNotificationInjectable from "../../../components/notifications/show-error-notification.injectable"; -import closeRendererLogFileInjectable from "../../../logger/close-renderer-log-file.injectable"; const initClusterFrameInjectable = getInjectable({ id: "init-cluster-frame", @@ -30,7 +29,6 @@ const initClusterFrameInjectable = getInjectable({ emitAppEvent: di.inject(emitAppEventInjectable), logger: di.inject(loggerInjectable), showErrorNotification: di.inject(showErrorNotificationInjectable), - closeFileLogging: di.inject(closeRendererLogFileInjectable), }); }, }); diff --git a/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts b/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts index 31f4bfe96e..109ae0f0bc 100644 --- a/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts +++ b/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts @@ -2,7 +2,6 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { once } from "lodash"; import type { Cluster } from "../../../../common/cluster/cluster"; import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry"; import type { ShowNotification } from "../../../components/notifications"; @@ -19,7 +18,6 @@ interface Dependencies { emitAppEvent: EmitAppEvent; logger: Logger; showErrorNotification: ShowNotification; - closeFileLogging: () => void; } const logPrefix = "[CLUSTER-FRAME]:"; @@ -32,7 +30,6 @@ export const initClusterFrame = ({ emitAppEvent, logger, showErrorNotification, - closeFileLogging, }: Dependencies) => async (unmountRoot: () => void) => { // TODO: Make catalogEntityRegistry already initialized when passed as dependency @@ -76,14 +73,11 @@ export const initClusterFrame = ({ }); }); - const onCloseFrame = once(() => { + window.onbeforeunload = () => { logger.info( `${logPrefix} Unload dashboard, clusterId=${(hostedCluster.id)}, frameId=${frameRoutingId}`, ); - closeFileLogging(); - unmountRoot(); - }); - window.addEventListener("beforeunload", onCloseFrame); - window.addEventListener("pagehide", onCloseFrame); + unmountRoot(); + }; }; diff --git a/packages/core/src/renderer/frames/root-frame/init-root-frame.injectable.ts b/packages/core/src/renderer/frames/root-frame/init-root-frame.injectable.ts index 2f698c5c89..8d2c3a43be 100644 --- a/packages/core/src/renderer/frames/root-frame/init-root-frame.injectable.ts +++ b/packages/core/src/renderer/frames/root-frame/init-root-frame.injectable.ts @@ -13,7 +13,6 @@ import loggerInjectable from "../../../common/logger.injectable"; import { delay } from "../../../common/utils"; import { broadcastMessage } from "../../../common/ipc"; import { bundledExtensionsLoaded } from "../../../common/ipc/extension-handling"; -import closeRendererLogFileInjectable from "../../logger/close-renderer-log-file.injectable"; const initRootFrameInjectable = getInjectable({ id: "init-root-frame", @@ -25,7 +24,6 @@ const initRootFrameInjectable = getInjectable({ const lensProtocolRouterRenderer = di.inject(lensProtocolRouterRendererInjectable); const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); const logger = di.inject(loggerInjectable); - const closeRendererLogFile = di.inject(closeRendererLogFileInjectable); return async (unmountRoot: () => void) => { catalogEntityRegistry.init(); @@ -61,7 +59,7 @@ const initRootFrameInjectable = getInjectable({ window.addEventListener("beforeunload", () => { logger.info("[ROOT-FRAME]: Unload app"); - closeRendererLogFile(); + unmountRoot(); }); }; diff --git a/packages/core/src/renderer/logger/close-renderer-log-file.injectable.ts b/packages/core/src/renderer/logger/close-renderer-log-file.injectable.ts deleted file mode 100644 index 8480589c39..0000000000 --- a/packages/core/src/renderer/logger/close-renderer-log-file.injectable.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import winstonLoggerInjectable from "../../common/winston-logger.injectable"; -import rendererFileLoggerTransportInjectable from "./file-transport.injectable"; - -const closeRendererLogFileInjectable = getInjectable({ - id: "close-renderer-log-file", - instantiate: (di) => { - const winstonLogger = di.inject(winstonLoggerInjectable); - const fileLoggingTransport = di.inject(rendererFileLoggerTransportInjectable); - - return () => { - fileLoggingTransport.close?.(); - winstonLogger.remove(fileLoggingTransport); - }; - }, -}); - -export default closeRendererLogFileInjectable; diff --git a/packages/core/src/renderer/logger/file-transport.injectable.ts b/packages/core/src/renderer/logger/file-transport.injectable.ts deleted file mode 100644 index 0cd8607e83..0000000000 --- a/packages/core/src/renderer/logger/file-transport.injectable.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { transports } from "winston"; -import directoryForLogsInjectable from "../../common/app-paths/directory-for-logs.injectable"; -import { loggerTransportInjectionToken } from "../../common/logger/transports"; -import windowLocationInjectable from "../../common/k8s-api/window-location.injectable"; -import currentlyInClusterFrameInjectable from "../routes/currently-in-cluster-frame.injectable"; -import { getClusterIdFromHost } from "../utils"; - -const rendererFileLoggerTransportInjectable = getInjectable({ - id: "renderer-file-logger-transport", - instantiate: (di) => { - let frameId: string; - - const currentlyInClusterFrame = di.inject( - currentlyInClusterFrameInjectable, - ); - - if (currentlyInClusterFrame) { - const { host } = di.inject(windowLocationInjectable); - const clusterId = getClusterIdFromHost(host); - - frameId = clusterId ? `cluster-${clusterId}` : "cluster"; - } else { - frameId = "main"; - } - - return new transports.File({ - handleExceptions: false, - level: "info", - filename: `lens-renderer-${frameId}.log`, - dirname: di.inject(directoryForLogsInjectable), - maxsize: 1024 * 1024, - maxFiles: 2, - tailable: true, - }); - }, - injectionToken: loggerTransportInjectionToken, -}); - -export default rendererFileLoggerTransportInjectable; diff --git a/packages/extension-api/package.json b/packages/extension-api/package.json index f3299651d9..73dbd613fe 100644 --- a/packages/extension-api/package.json +++ b/packages/extension-api/package.json @@ -2,7 +2,7 @@ "name": "@k8slens/extensions", "productName": "OpenLens extensions", "description": "OpenLens - Open Source Kubernetes IDE: extensions", - "version": "6.4.0-beta.17", + "version": "6.4.0", "copyright": "© 2022 OpenLens Authors", "license": "MIT", "main": "dist/extension-api.js", @@ -26,7 +26,7 @@ "prepare:dev": "yarn run build" }, "dependencies": { - "@k8slens/core": "^6.4.0-beta.17" + "@k8slens/core": "^6.4.0" }, "devDependencies": { "@types/node": "^16.18.6", diff --git a/packages/open-lens/package.json b/packages/open-lens/package.json index a23f4f311b..4e94356311 100644 --- a/packages/open-lens/package.json +++ b/packages/open-lens/package.json @@ -4,7 +4,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "6.4.0-beta.17", + "version": "6.4.0", "repository": { "type": "git", "url": "git+https://github.com/lensapp/lens.git" @@ -192,7 +192,7 @@ } }, "dependencies": { - "@k8slens/core": "^6.4.0-beta.17", + "@k8slens/core": "^6.4.0", "@k8slens/ensure-binaries": "^6.4.0-beta.16", "@k8slens/generate-tray-icons": "^6.4.0-beta.16", "@ogre-tools/fp": "^12.0.1", diff --git a/packages/release-tool/package.json b/packages/release-tool/package.json index b1dbab050a..77d2b9b5e0 100644 --- a/packages/release-tool/package.json +++ b/packages/release-tool/package.json @@ -1,6 +1,6 @@ { "name": "@k8slens/release-tool", - "version": "6.4.0-beta.17", + "version": "6.4.0", "description": "Release tool for lens monorepo", "main": "dist/index.mjs", "license": "MIT", diff --git a/packages/release-tool/src/index.ts b/packages/release-tool/src/index.ts index 7a4ce49a27..8c1e465ffd 100755 --- a/packages/release-tool/src/index.ts +++ b/packages/release-tool/src/index.ts @@ -5,7 +5,7 @@ */ import assert from "assert"; import chalk from "chalk"; -import child_process from "child_process"; +import child_process, { spawn } from "child_process"; import { readFile } from "fs/promises"; import inquirer from "inquirer"; import { createInterface, ReadLine } from "readline"; @@ -16,9 +16,21 @@ type SemVer = semver.SemVer; const { SemVer } = semver; const exec = promisify(child_process.exec); -const spawn = promisify(child_process.spawn); const execFile = promisify(child_process.execFile); +async function pipeExecFile(file: string, args: string[], opts?: { stdin: string }) { + const p = execFile(file, args); + + p.child.stdout?.pipe(process.stdout); + p.child.stderr?.pipe(process.stderr); + + if (opts) { + p.child.stdin?.end(opts.stdin); + } + + await p; +} + interface GithubPrData { author: { login: string; @@ -65,9 +77,35 @@ async function fetchAllGitTags(): Promise { .map(line => line.trim()); } -async function bumpPackageVersions(): Promise { - await spawn("npm", ["run", "bump-version"], { - stdio: "inherit", +function bumpPackageVersions() { + const bumpPackages = spawn("npm", ["run", "bump-version"], { + stdio: "inherit" + }); + const cleaners: (() => void)[] = [ + () => bumpPackages.stdout?.unpipe(), + () => bumpPackages.stderr?.unpipe(), + ]; + const cleanup = () => cleaners.forEach(clean => clean()); + + return new Promise((resolve, reject) => { + const onExit = (code: number | null) => { + cleanup(); + if (code) { + reject(new Error(`"npm run bump-version" failed with code ${code}`)); + } else { + resolve(); + } + }; + const onError = (error: Error) => { + cleanup(); + reject(error); + }; + + bumpPackages.once("error", onError); + cleaners.push(() => bumpPackages.off("error", onError)); + + bumpPackages.once("exit", onExit); + cleaners.push(() => bumpPackages.off("exit", onExit)); }); } @@ -110,20 +148,12 @@ function formatSemverForMilestone(version: SemVer): string { async function createReleaseBranchAndCommit(prBase: string, version: SemVer, prBody: string): Promise { const prBranch = `release/v${version.format()}`; - await spawn("git", ["checkout", "-b", prBranch], { - stdio: "inherit", - }); - await spawn("git", ["add", "lerna.json", "packages/*/package.json"], { - stdio: "inherit", - }); - await spawn("git", ["commit", "-sm", `"Release ${version.format()}"`], { - stdio: "inherit", - }); - await spawn("git", ["push", "--set-upstream", "origin", prBranch], { - stdio: "inherit", - }); + await pipeExecFile("git", ["checkout", "-b", prBranch]); + await pipeExecFile("git", ["add", "lerna.json", "packages/*/package.json"]); + await pipeExecFile("git", ["commit", "-sm", `Release ${version.format()}`]); + await pipeExecFile("git", ["push", "--set-upstream", "origin", prBranch]); - await spawn("gh", [ + await pipeExecFile("gh", [ "pr", "create", "--base", prBase, @@ -131,9 +161,9 @@ async function createReleaseBranchAndCommit(prBase: string, version: SemVer, prB "--label", "skip-changelog", "--label", "release", "--milestone", formatSemverForMilestone(version), - "--body-file", prBody, + "--body-file", "-", ], { - stdio: "inherit" + stdin: prBody, }); } @@ -153,7 +183,7 @@ function sortExtendedGithubPrData(left: ExtendedGithubPrData, right: ExtendedGit } async function getRelevantPRs(milestone: string, previousReleasedVersion: string): Promise { - console.log("retreiving previous 500 PRs..."); + console.log("retrieving previous 200 PRs..."); const getMergedPrsArgs = [ "gh", @@ -167,14 +197,14 @@ async function getRelevantPRs(milestone: string, previousReleasedVersion: string const mergedPrs = JSON.parse((await exec(getMergedPrsArgs.join(" "), { encoding: "utf-8" })).stdout) as GithubPrData[]; const milestoneRelevantPrs = mergedPrs.filter(pr => pr.milestone?.title === milestone); - const relaventPrsQuery = await Promise.all( + const relevantPrsQuery = await Promise.all( milestoneRelevantPrs.map(async pr => ({ pr, stdout: (await exec(`git tag v${previousReleasedVersion} --no-contains ${pr.mergeCommit.oid}`)).stdout, })), ); - return relaventPrsQuery + return relevantPrsQuery .filter(query => query.stdout) .map(query => query.pr) .filter(pr => pr.labels.every(label => label.name !== "skip-changelog")) @@ -189,40 +219,11 @@ function formatPrEntry(pr: ExtendedGithubPrData) { const isEnhancementPr = (pr: ExtendedGithubPrData) => pr.labels.some(label => label.name === "enhancement"); const isBugfixPr = (pr: ExtendedGithubPrData) => pr.labels.some(label => label.name === "bug"); -const cherrypickCommitWith = (rl: ReadLine) => async (commit: string) => { +const cherryPickCommitWith = (rl: ReadLine) => async (commit: string) => { try { - const cherryPick = child_process.spawn("git", ["cherry-pick", commit]); - - cherryPick.stdout.pipe(process.stdout); - cherryPick.stderr.pipe(process.stderr); - - await new Promise((resolve, reject) => { - const cleaners: (() => void)[] = []; - const cleanup = () => cleaners.forEach(cleaner => cleaner()); - - const onExit = (code: number | null) => { - if (code) { - reject(new Error(`git cherry-pick failed with exit code ${code}`)); - cleanup(); - } - - resolve(); - cleanup(); - }; - - cherryPick.once("exit", onExit); - cleaners.push(() => cherryPick.off("exit", onExit)); - - const onError = (error: Error) => { - cleanup(); - reject(error); - }; - - cherryPick.once("error", onError); - cleaners.push(() => cherryPick.off("error", onError)); - }); + await pipeExecFile("git", ["cherry-pick", commit]); } catch { - console.error(chalk.bold("Please resolve conflicts in a seperate terminal and then press enter here...")); + console.error(chalk.bold("Please resolve conflicts in a separate terminal and then press enter here...")); await new Promise(resolve => rl.once("line", () => resolve())); } }; @@ -249,7 +250,7 @@ async function pickWhichPRsToUse(prs: ExtendedGithubPrData[]): Promise 0) { - maintenencePrLines.unshift("## 🧰 Maintenance", ""); - maintenencePrLines.push(""); + if (maintenancePrLines.length > 0) { + maintenancePrLines.unshift("## 🧰 Maintenance", ""); + maintenancePrLines.push(""); } return [ @@ -281,22 +282,22 @@ function formatChangelog(previousReleasedVersion: string, prs: ExtendedGithubPrD "", ...enhancementPrLines, ...bugPrLines, - ...maintenencePrLines, + ...maintenancePrLines, ].join("\n"); } -async function cherrypickCommits(prs: ExtendedGithubPrData[]): Promise { +async function cherryPickCommits(prs: ExtendedGithubPrData[]): Promise { const rl = createInterface(process.stdin); - const cherrypickCommit = cherrypickCommitWith(rl); + const cherryPickCommit = cherryPickCommitWith(rl); for (const pr of prs) { - await cherrypickCommit(pr.mergeCommit.oid); + await cherryPickCommit(pr.mergeCommit.oid); } rl.close(); } -async function pickRelaventPrs(prs: ExtendedGithubPrData[], isMasterBranch: boolean): Promise { +async function pickRelevantPrs(prs: ExtendedGithubPrData[], isMasterBranch: boolean): Promise { if (isMasterBranch) { return prs; } @@ -307,7 +308,7 @@ async function pickRelaventPrs(prs: ExtendedGithubPrData[], isMasterBranch: bool selectedPrs = await pickWhichPRsToUse(prs); } while (selectedPrs.length === 0 && (console.warn("[WARNING]: must pick at least once commit"), true)); - await cherrypickCommits(selectedPrs); + await cherryPickCommits(selectedPrs); return selectedPrs; } @@ -326,8 +327,8 @@ async function createRelease(): Promise { } const prMilestone = formatSemverForMilestone(await getCurrentVersionOfSubPackage("core")); - const relaventPrs = await getRelevantPRs(prMilestone, previousReleasedVersion); - const selectedPrs = await pickRelaventPrs(relaventPrs, isMasterBranch); + const relevantPrs = await getRelevantPRs(prMilestone, previousReleasedVersion); + const selectedPrs = await pickRelevantPrs(relevantPrs, isMasterBranch); const prBody = formatChangelog(previousReleasedVersion, selectedPrs); if (!isMasterBranch) {