diff --git a/Makefile b/Makefile index a3436115e9..1a21ef0e02 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,7 @@ $(extension_node_modules): node_modules $(extension_dists): src/extensions/npm/extensions/dist $(extension_node_modules) cd $(@:/dist=) && ../../node_modules/.bin/npm run build + rm -rf ./node_modules/$(shell basename $(@:/dist=)) .PHONY: clean-old-extensions clean-old-extensions: @@ -76,6 +77,7 @@ clean-old-extensions: .PHONY: build-extensions build-extensions: node_modules clean-old-extensions $(extension_dists) + yarn install --check-files --frozen-lockfile --network-timeout=100000 .PHONY: test-extensions test-extensions: $(extension_node_modules) diff --git a/extensions/kube-object-event-status/package.json b/extensions/kube-object-event-status/package.json index 934ebbbdb8..4e65471429 100644 --- a/extensions/kube-object-event-status/package.json +++ b/extensions/kube-object-event-status/package.json @@ -8,7 +8,7 @@ "styles": [] }, "scripts": { - "build": "npx webpack && npm pack", + "build": "npx webpack", "dev": "npx webpack -- --watch", "test": "echo NO TESTS" }, diff --git a/extensions/metrics-cluster-feature/package.json b/extensions/metrics-cluster-feature/package.json index 03ad0df7a5..3be7b58d58 100644 --- a/extensions/metrics-cluster-feature/package.json +++ b/extensions/metrics-cluster-feature/package.json @@ -8,7 +8,7 @@ "styles": [] }, "scripts": { - "build": "npx webpack && npm pack", + "build": "npx webpack", "dev": "npx webpack -- --watch", "test": "npx jest --passWithNoTests --env=jsdom src $@", "clean": "rm -rf dist/ && rm *.tgz" diff --git a/extensions/node-menu/package.json b/extensions/node-menu/package.json index 3b56dc6e62..df2d490ee0 100644 --- a/extensions/node-menu/package.json +++ b/extensions/node-menu/package.json @@ -8,7 +8,7 @@ "styles": [] }, "scripts": { - "build": "npx webpack && npm pack", + "build": "npx webpack", "dev": "npx webpack -- --watch", "test": "npx jest --passWithNoTests --env=jsdom src $@" }, diff --git a/extensions/pod-menu/package.json b/extensions/pod-menu/package.json index ba3f107f80..3b44d3c44d 100644 --- a/extensions/pod-menu/package.json +++ b/extensions/pod-menu/package.json @@ -8,7 +8,7 @@ "styles": [] }, "scripts": { - "build": "npx webpack && npm pack", + "build": "npx webpack", "dev": "npx webpack -- --watch", "test": "npx jest --passWithNoTests --env=jsdom src $@" }, diff --git a/package.json b/package.json index e49acd1a2f..efd3e8cc06 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,13 @@ "bundledHelmVersion": "3.7.2", "sentryDsn": "", "contentSecurityPolicy": "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:", - "welcomeRoute": "/welcome" + "welcomeRoute": "/welcome", + "extensions": [ + "kube-object-event-status", + "metrics-cluster-feature", + "node-menu", + "pod-menu" + ] }, "engines": { "node": ">=16 <17" @@ -98,15 +104,6 @@ ], "afterSign": "build/notarize.js", "extraResources": [ - { - "from": "extensions/", - "to": "./extensions/", - "filter": [ - "**/*.tgz", - "**/package.json", - "!**/node_modules" - ] - }, { "from": "templates/", "to": "./templates/", @@ -236,9 +233,14 @@ "joi": "^17.7.0", "js-yaml": "^4.1.0", "jsdom": "^16.7.0", + "kube-object-event-status": "file:./extensions/kube-object-event-status", + "lens-metrics-cluster-feature": "file:./extensions/metrics-cluster-feature", + "lens-node-menu": "file:./extensions/node-menu", + "lens-pod-menu": "file:./extensions/pod-menu", "lodash": "^4.17.15", "marked": "^4.2.3", "md5-file": "^5.0.0", + "metrics-cluster-feature": "file:./extensions/metrics-cluster-feature", "mobx": "^6.7.0", "mobx-observable-history": "^2.0.3", "mobx-react": "^7.6.0", @@ -249,10 +251,12 @@ "monaco-editor": "^0.29.1", "monaco-editor-webpack-plugin": "^5.0.0", "node-fetch": "^3.3.0", + "node-menu": "file:./extensions/node-menu", "node-pty": "0.10.1", "npm": "^8.19.3", "p-limit": "^3.1.0", "path-to-regexp": "^6.2.0", + "pod-menu": "file:./extensions/pod-menu", "proper-lockfile": "^4.1.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/src/common/vars/application-information.global-override-for-injectable.ts b/src/common/vars/application-information.global-override-for-injectable.ts index 232a189ce1..83b9559d73 100644 --- a/src/common/vars/application-information.global-override-for-injectable.ts +++ b/src/common/vars/application-information.global-override-for-injectable.ts @@ -18,6 +18,7 @@ export default getGlobalOverride(applicationInformationInjectable, () => ({ sentryDsn: "", contentSecurityPolicy: "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:", welcomeRoute: "/welcome", + extensions: [], }, copyright: "some-copyright-information", description: "some-descriptive-text", diff --git a/src/extensions/extension-discovery/extension-discovery.injectable.ts b/src/extensions/extension-discovery/extension-discovery.injectable.ts index 549f9c9de7..ad146e2fd7 100644 --- a/src/extensions/extension-discovery/extension-discovery.injectable.ts +++ b/src/extensions/extension-discovery/extension-discovery.injectable.ts @@ -10,7 +10,6 @@ import extensionsStoreInjectable from "../extensions-store/extensions-store.inje 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 installExtensionsInjectable from "../extension-installer/install-extensions/install-extensions.injectable"; import readJsonFileInjectable from "../../common/fs/read-json-file.injectable"; import loggerInjectable from "../../common/logger.injectable"; import pathExistsInjectable from "../../common/fs/path-exists.injectable"; @@ -28,6 +27,7 @@ import getRelativePathInjectable from "../../common/path/get-relative-path.injec import joinPathsInjectable from "../../common/path/join-paths.injectable"; import removePathInjectable from "../../common/fs/remove-path.injectable"; import homeDirectoryPathInjectable from "../../common/os/home-directory-path.injectable"; +import applicationInformationInjectable from "../../common/vars/application-information.injectable"; import lensResourcesDirInjectable from "../../common/vars/lens-resources-dir.injectable"; const extensionDiscoveryInjectable = getInjectable({ @@ -39,7 +39,6 @@ const extensionDiscoveryInjectable = getInjectable({ extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable), isCompatibleExtension: di.inject(isCompatibleExtensionInjectable), installExtension: di.inject(installExtensionInjectable), - installExtensions: di.inject(installExtensionsInjectable), extensionPackageRootDirectory: di.inject(extensionPackageRootDirectoryInjectable), resourcesDirectory: di.inject(lensResourcesDirInjectable), readJsonFile: di.inject(readJsonFileInjectable), @@ -59,6 +58,7 @@ const extensionDiscoveryInjectable = getInjectable({ getRelativePath: di.inject(getRelativePathInjectable), joinPaths: di.inject(joinPathsInjectable), homeDirectoryPath: di.inject(homeDirectoryPathInjectable), + applicationInformation: di.inject(applicationInformationInjectable), }), }); diff --git a/src/extensions/extension-discovery/extension-discovery.ts b/src/extensions/extension-discovery/extension-discovery.ts index a23cebd9c2..511bfba8a9 100644 --- a/src/extensions/extension-discovery/extension-discovery.ts +++ b/src/extensions/extension-discovery/extension-discovery.ts @@ -12,7 +12,6 @@ import type { ExtensionsStore } from "../extensions-store/extensions-store"; import type { ExtensionLoader } from "../extension-loader"; import type { LensExtensionId, LensExtensionManifest } from "../lens-extension"; import type { ExtensionInstallationStateStore } from "../extension-installation-state-store/extension-installation-state-store"; -import type { PackageJson } from "type-fest"; import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling"; import { requestInitialExtensionDiscovery } from "../../renderer/ipc"; import type { ReadJson } from "../../common/fs/read-json-file.injectable"; @@ -20,7 +19,6 @@ import type { Logger } from "../../common/logger"; import type { PathExists } from "../../common/fs/path-exists.injectable"; import type { Watch } from "../../common/fs/watch/watch.injectable"; import type { Stats } from "fs"; -import { constants } from "fs"; import type { LStat } from "../../common/fs/lstat.injectable"; import type { ReadDirectory } from "../../common/fs/read-directory.injectable"; import type { EnsureDirectory } from "../../common/fs/ensure-dir.injectable"; @@ -32,6 +30,7 @@ import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable" import type { GetRelativePath } from "../../common/path/get-relative-path.injectable"; import type { RemovePath } from "../../common/fs/remove-path.injectable"; import type TypedEventEmitter from "typed-emitter"; +import type { ApplicationInformation } from "../../common/vars/application-information.injectable"; interface Dependencies { readonly extensionLoader: ExtensionLoader; @@ -43,9 +42,9 @@ interface Dependencies { readonly isProduction: boolean; readonly fileSystemSeparator: string; readonly homeDirectoryPath: string; + readonly applicationInformation: ApplicationInformation; isCompatibleExtension: (manifest: LensExtensionManifest) => boolean; installExtension: (name: string) => Promise; - installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise; readJsonFile: ReadJson; pathExists: PathExists; removePath: RemovePath; @@ -137,14 +136,6 @@ export class ExtensionDiscovery { return this.dependencies.joinPaths(this.dependencies.extensionPackageRootDirectory, manifestFilename); } - get inTreeTargetPath(): string { - return this.dependencies.joinPaths(this.dependencies.extensionPackageRootDirectory, "extensions"); - } - - get inTreeFolderPath(): string { - return this.dependencies.joinPaths(this.dependencies.resourcesDirectory, "extensions"); - } - get nodeModulesPath(): string { return this.dependencies.joinPaths(this.dependencies.extensionPackageRootDirectory, "node_modules"); } @@ -329,26 +320,6 @@ export class ExtensionDiscovery { ); await this.dependencies.removePath(this.dependencies.joinPaths(this.dependencies.extensionPackageRootDirectory, "package-lock.json")); - - const canWriteToInTreeFolder = await this.dependencies.accessPath(this.inTreeFolderPath, constants.W_OK); - - if (canWriteToInTreeFolder) { - // Set bundled folder path to static/extensions - this.bundledFolderPath = this.inTreeFolderPath; - } else { - // Remove e.g. /Users//Library/Application Support/LensDev/extensions - await this.dependencies.removePath(this.inTreeTargetPath); - - // Create folder e.g. /Users//Library/Application Support/LensDev/extensions - await this.dependencies.ensureDirectory(this.inTreeTargetPath); - - // Copy static/extensions to e.g. /Users//Library/Application Support/LensDev/extensions - await this.dependencies.copy(this.inTreeFolderPath, this.inTreeTargetPath); - - // Set bundled folder path to e.g. /Users//Library/Application Support/LensDev/extensions - this.bundledFolderPath = this.inTreeTargetPath; - } - await this.dependencies.ensureDirectory(this.nodeModulesPath); await this.dependencies.ensureDirectory(this.localFolderPath); @@ -382,7 +353,7 @@ export class ExtensionDiscovery { protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise { try { const manifest = await this.dependencies.readJsonFile(manifestPath) as unknown as LensExtensionManifest; - const id = this.getInstalledManifestPath(manifest.name); + const id = isBundled ? manifestPath : this.getInstalledManifestPath(manifest.name); const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled }); const extensionDir = this.dependencies.getDirnameOfPath(manifestPath); const npmPackage = this.dependencies.joinPaths(extensionDir, `${manifest.name}-${manifest.version}.tgz`); @@ -417,39 +388,24 @@ export class ExtensionDiscovery { const userExtensions = await this.loadFromFolder(this.localFolderPath, bundledExtensions.map((extension) => extension.manifest.name)); const extensions = bundledExtensions.concat(userExtensions); - await this.installBundledPackages(this.packageJsonPath, extensions); - return this.extensions = new Map(extensions.map(extension => [extension.id, extension])); } - /** - * Write package.json to file system and install dependencies. - */ - installBundledPackages(packageJsonPath: string, extensions: InstalledExtension[]): Promise { - const dependencies = Object.fromEntries( - extensions.filter(extension => extension.isBundled).map(extension => [extension.manifest.name, extension.absolutePath]), - ); - const optionalDependencies = Object.fromEntries( - extensions.filter(extension => !extension.isBundled).map(extension => [extension.manifest.name, extension.absolutePath]), - ); - - return this.dependencies.installExtensions(packageJsonPath, { dependencies, optionalDependencies }); - } - async loadBundledExtensions(): Promise { const extensions: InstalledExtension[] = []; - const folderPath = this.bundledFolderPath; - const paths = await this.dependencies.readDirectory(folderPath); + const extensionNames = this.dependencies.applicationInformation.config.extensions || []; - for (const fileName of paths) { - const absPath = this.dependencies.joinPaths(folderPath, fileName); + for (const dirName of extensionNames) { + const absPath = this.dependencies.joinPaths(__dirname, "..", "..", "node_modules", dirName); const extension = await this.loadExtensionFromFolder(absPath, { isBundled: true }); - if (extension) { - extensions.push(extension); + if (!extension) { + throw new Error(`Couldn't load bundled extension: ${dirName}`); } + + extensions.push(extension); } - this.dependencies.logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions }); + this.dependencies.logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { extensions }); return extensions; } diff --git a/src/extensions/extension-installer/extension-installer.ts b/src/extensions/extension-installer/extension-installer.ts index de97c71f00..941f8700d1 100644 --- a/src/extensions/extension-installer/extension-installer.ts +++ b/src/extensions/extension-installer/extension-installer.ts @@ -5,10 +5,7 @@ import AwaitLock from "await-lock"; import child_process from "child_process"; -import fs from "fs-extra"; -import path from "path"; import logger from "../../main/logger"; -import type { PackageJson } from "type-fest"; const logModule = "[EXTENSION-INSTALLER]"; @@ -38,27 +35,6 @@ export class ExtensionInstaller { return __non_webpack_require__.resolve("npm"); } - /** - * Write package.json to the file system and execute npm install for it. - */ - installPackages = async (packageJsonPath: string, packagesJson: PackageJson): Promise => { - // Mutual exclusion to install packages in sequence - await this.installLock.acquireAsync(); - - try { - // Write the package.json which will be installed in .installDependencies() - await fs.writeFile(path.join(packageJsonPath), JSON.stringify(packagesJson, null, 2), { - mode: 0o600, - }); - - logger.info(`${logModule} installing dependencies at ${this.dependencies.extensionPackageRootDirectory}`); - await this.npm(...baseNpmInstallArgs); - logger.info(`${logModule} dependencies installed at ${this.dependencies.extensionPackageRootDirectory}`); - } finally { - this.installLock.release(); - } - }; - /** * Install single package using npm */ diff --git a/src/extensions/extension-installer/install-extensions/install-extensions.injectable.ts b/src/extensions/extension-installer/install-extensions/install-extensions.injectable.ts deleted file mode 100644 index 7387326e15..0000000000 --- a/src/extensions/extension-installer/install-extensions/install-extensions.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 installExtensionsInjectable = getInjectable({ - id: "install-extensions", - instantiate: (di) => di.inject(extensionInstallerInjectable).installPackages, -}); - -export default installExtensionsInjectable; diff --git a/yarn.lock b/yarn.lock index 352b3af866..032aae3cc1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8394,6 +8394,9 @@ klona@^2.0.4, klona@^2.0.5: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== +"kube-object-event-status@file:./extensions/kube-object-event-status": + version "6.1.1" + kuler@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" @@ -8411,6 +8414,15 @@ lazy-val@^1.0.4, lazy-val@^1.0.5: resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d" integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== +"lens-metrics-cluster-feature@file:./extensions/metrics-cluster-feature": + version "6.1.0" + +"lens-node-menu@file:./extensions/node-menu": + version "6.1.0" + +"lens-pod-menu@file:./extensions/pod-menu": + version "6.1.0" + less@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/less/-/less-4.1.2.tgz#6099ee584999750c2624b65f80145f8674e4b4b0" @@ -8976,6 +8988,9 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= +"metrics-cluster-feature@file:./extensions/metrics-cluster-feature": + version "6.1.0" + micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -9422,6 +9437,9 @@ node-loader@^2.0.0: dependencies: loader-utils "^2.0.0" +"node-menu@file:./extensions/node-menu": + version "6.1.0" + node-pty@0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.1.tgz#cd05d03a2710315ec40221232ec04186f6ac2c6d" @@ -10227,6 +10245,9 @@ plist@^3.0.1, plist@^3.0.4: base64-js "^1.5.1" xmlbuilder "^9.0.7" +"pod-menu@file:./extensions/pod-menu": + version "6.1.0" + 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"