1
0
mirror of https://github.com/lensapp/lens.git synced 2025-01-07 17:10:34 +03:00

Extensions loading (#795)

Signed-off-by: Roman <ixrock@gmail.com>

Co-authored-by: Alex Andreev <alex.andreev.email@gmail.com>
Co-authored-by: Lauri Nevala <lauri.nevala@gmail.com>
This commit is contained in:
Roman 2020-09-09 13:00:25 +03:00 committed by GitHub
parent 4250523fe4
commit f1b03990ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 564 additions and 66 deletions

6
.gitignore vendored
View File

@ -1,12 +1,14 @@
dist/
out/
node_modules/
.DS_Store
yarn-error.log
coverage/
tmp/
static/build/**
static/build
static/types
binaries/client/
binaries/server/
src/extensions/*/*.js
src/extensions/*/*.d.ts
locales/**/**.js
lens.log

View File

@ -2,7 +2,7 @@
"name": "kontena-lens",
"productName": "Lens",
"description": "Lens - The Kubernetes IDE",
"version": "3.6.0-beta.2",
"version": "3.6.0-rc.1",
"main": "static/build/main.js",
"copyright": "© 2020, Mirantis, Inc.",
"license": "MIT",
@ -11,14 +11,16 @@
"email": "info@k8slens.dev"
},
"scripts": {
"dev": "concurrently -k \"yarn dev-run -C\" \"yarn dev:main\" \"yarn dev:renderer\"",
"dev-run": "cross-env DEBUG=true nodemon --watch static/build/main.js --exec \"electron --inspect .\"",
"dev:main": "cross-env DEBUG=true yarn compile:main --watch",
"dev:renderer": "cross-env DEBUG=true yarn compile:renderer --watch",
"dev": "concurrently -k \"yarn dev-run -C\" yarn:dev:*",
"dev-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\"",
"dev:main": "yarn compile:main --watch",
"dev:renderer": "yarn compile:renderer --watch",
"dev:extensions": "tsc --project src/extensions/example-extension --watch",
"compile": "env NODE_ENV=production concurrently yarn:compile:*",
"compile:main": "webpack --config webpack.main.ts",
"compile:renderer": "webpack --config webpack.renderer.ts",
"compile:i18n": "lingui compile",
"compile:extension-api.d.ts": "tsc --project src/extensions",
"build:linux": "yarn compile && electron-builder --linux --dir -c.productName=Lens",
"build:mac": "yarn compile && electron-builder --mac --dir -c.productName=Lens",
"build:win": "yarn compile && electron-builder --win --dir -c.productName=Lens",
@ -69,7 +71,8 @@
},
"moduleNameMapper": {
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.ts"
}
},
"modulePathIgnorePatterns": ["<rootDir>/dist"]
},
"build": {
"afterSign": "build/notarize.js",
@ -89,6 +92,11 @@
"to": "static/",
"filter": "!**/main.js"
},
{
"from": "src/extensions/",
"to": "./extensions/",
"filter": "**/*.js*"
},
"LICENSE"
],
"linux": {
@ -167,6 +175,7 @@
"@types/lodash": "^4.14.155",
"@types/marked": "^0.7.4",
"@types/mock-fs": "^4.10.0",
"@types/module-alias": "^2.0.0",
"@types/node": "^12.12.45",
"@types/proper-lockfile": "^4.1.1",
"@types/react-beautiful-dnd": "^13.0.0",
@ -193,6 +202,7 @@
"mobx": "^5.15.5",
"mobx-observable-history": "^1.0.3",
"mock-fs": "^4.12.0",
"module-alias": "^2.2.2",
"node-machine-id": "^1.1.12",
"node-pty": "^0.9.0",
"openid-client": "^3.15.2",
@ -269,7 +279,6 @@
"circular-dependency-plugin": "^5.2.0",
"color": "^3.1.2",
"concurrently": "^5.2.0",
"cross-env": "^7.0.2",
"css-element-queries": "^1.2.3",
"css-loader": "^3.5.3",
"dompurify": "^2.0.11",

View File

@ -1,4 +1,4 @@
import { WorkspaceId, workspaceStore } from "./workspace-store";
import type { WorkspaceId } from "./workspace-store";
import path from "path";
import { app, ipcRenderer, remote } from "electron";
import { unlink } from "fs-extra";
@ -13,7 +13,6 @@ import { saveToAppFiles } from "./utils/saveToAppFiles";
import { KubeConfig } from "@kubernetes/client-node";
import _ from "lodash";
import move from "array-move";
import { is } from "immer/dist/internal";
export interface ClusterIconUpload {
clusterId: string;
@ -64,7 +63,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
static embedCustomKubeConfig(clusterId: ClusterId, kubeConfig: KubeConfig | string): string {
const filePath = ClusterStore.getCustomKubeConfigPath(clusterId);
const fileContents = typeof kubeConfig == "string" ? kubeConfig : dumpConfigYaml(kubeConfig);
saveToAppFiles(filePath, fileContents, { mode: 0o600});
saveToAppFiles(filePath, fileContents, { mode: 0o600 });
return filePath;
}

View File

@ -2,6 +2,7 @@
import path from "path";
import packageInfo from "../../package.json"
import { defineGlobal } from "./utils/defineGlobal";
import { addAlias } from "module-alias";
export const isMac = process.platform === "darwin"
export const isWindows = process.platform === "win32"
@ -21,6 +22,13 @@ export const rendererDir = path.join(contextDir, "src/renderer");
export const htmlTemplate = path.resolve(rendererDir, "template.html");
export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");
// Extensions
export const extensionsLibName = `${appName}-extensions.api`
export const extensionsDir = path.join(contextDir, "src/extensions");
// Special dynamic module aliases
addAlias("@lens/extensions", path.resolve(buildDir, `${extensionsLibName}.js`)); // fixme: provide path in prod
// Special runtime paths
defineGlobal("__static", {
get() {

View File

@ -0,0 +1,3 @@
# Lens Example Extension
*TODO*: add more info

View File

@ -0,0 +1,17 @@
import { LensExtension, Icon, LensRuntimeRendererEnv } from "@lens/extensions"; // fixme: map to generated types from "extension-api.d.ts"
// todo: register custom icon in cluster-menu
// todo: register custom view by clicking the item
export default class ExampleExtension extends LensExtension {
async enable(runtime: /*LensRuntimeRendererEnv*/ any): Promise<any> {
try {
super.enable(runtime);
runtime.logger.info('EXAMPLE EXTENSION: ENABLE() override');
} catch (err){
console.error(err)
}
}
}
// <Icon material="camera" onClick={() => console.log("done")}/>

View File

@ -0,0 +1,11 @@
{
"name": "extension-example",
"version": "1.0.0",
"description": "Example extension",
"main": "example-extension.ts",
"lens": {
"metadata": {}
},
"dependencies": {
}
}

View File

@ -0,0 +1,13 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": ".",
"module": "CommonJS",
"sourceMap": false,
"declaration": false
},
"include": [
"../../../types",
"./example-extension.ts"
]
}

View File

@ -0,0 +1,22 @@
// Lens-extensions api developer's kit
export type { LensRuntimeRendererEnv } from "./lens-runtime";
// APIs
export * from "./extension"
// Common UI components
export * from "../renderer/components/icon"
export * from "../renderer/components/badge"
export * from "../renderer/components/tooltip"
export * from "../renderer/components/button"
export * from "../renderer/components/input"
export * from "../renderer/components/select"
export * from "../renderer/components/checkbox"
export * from "../renderer/components/radio"
export * from "../renderer/components/slider"
export * from "../renderer/components/spinner"
export * from "../renderer/components/tabs"
export * from "../renderer/components/line-progress"
// Utils
export * from "../main/logger";

View File

@ -0,0 +1,174 @@
import type { LensRuntimeRendererEnv } from "./lens-runtime";
import path from "path";
import fs from "fs-extra";
import { action, observable, reaction, toJS, } from "mobx";
import { BaseStore } from "../common/base-store";
import { ExtensionId, ExtensionManifest, ExtensionVersion, LensExtension } from "./extension";
import { isDevelopment, isProduction, isTestEnv } from "../common/vars";
import logger from "../main/logger";
export interface ExtensionStoreModel {
version: ExtensionVersion;
extensions: Record<ExtensionId, ExtensionModel>
}
export interface ExtensionModel {
id?: ExtensionId; // available in lens-extension instance
version: ExtensionVersion;
name: string;
manifestPath: string;
description?: string;
enabled?: boolean;
updateUrl?: string;
}
export interface InstalledExtension<T extends ExtensionModel = any> {
manifestPath: string;
manifest: ExtensionManifest;
extensionModule: {
[name: string]: any;
default: new (model: ExtensionModel, manifest?: ExtensionManifest) => LensExtension
}
}
export class ExtensionStore extends BaseStore<ExtensionStoreModel> {
private constructor() {
super({
configName: "lens-extension-store",
syncEnabled: false,
});
}
@observable version: ExtensionVersion = "0.0.0";
@observable extensions = observable.map<ExtensionId, LensExtension>();
@observable removed = observable.map<ExtensionId, LensExtension>();
@observable installed = observable.map<ExtensionId, InstalledExtension>([], { deep: false });
get folderPath(): string {
if (isDevelopment) {
return path.resolve(__static, "../src/extensions");
}
return path.resolve(__static, "../extensions"); //todo figure out prod
}
async load() {
await this.loadExtensions();
return super.load();
}
autoEnableOnLoad(getLensRuntimeEnv: () => LensRuntimeRendererEnv, { delay = 0 } = {}) {
logger.info('[EXTENSIONS-STORE]: auto-activation loaded extensions: ON');
return reaction(() => this.installed.toJS(), installedExtensions => {
installedExtensions.forEach(({ extensionModule, manifest, manifestPath }) => {
let instance = this.getById(manifestPath);
if (!instance) {
const LensExtensionClass = extensionModule.default;
instance = new LensExtensionClass({ ...manifest, manifestPath, id: manifestPath }, manifest);
instance.enable(getLensRuntimeEnv());
this.extensions.set(manifestPath, instance); // save
}
})
}, {
fireImmediately: true,
delay: delay,
})
}
getExtensionByManifest(manifestPath: string): InstalledExtension {
let manifestJson: ExtensionManifest;
let mainJs: string;
try {
manifestJson = __non_webpack_require__(manifestPath); // "__non_webpack_require__" converts to native node's require()-call
mainJs = path.resolve(path.dirname(manifestPath), manifestJson.main);
mainJs = mainJs.replace(/\.ts$/i, ".js"); // todo: compile *.ts on the fly?
const extensionModule = __non_webpack_require__(mainJs);
return {
manifestPath: manifestPath,
manifest: manifestJson,
extensionModule: extensionModule,
}
} catch (err) {
console.error(`[EXTENSION-STORE]: can't load extension at ${manifestPath}: ${err}`, { manifestJson, mainJs });
}
}
@action
async loadExtensions() {
const extensions = await this.loadFromFolder(this.folderPath);
const extManifestMap = new Map(extensions.map(ext => [ext.manifestPath, ext]));
this.installed.replace(extManifestMap);
}
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
const paths = await fs.readdir(folderPath);
const manifestsLoading = paths.map(fileName => {
const absPath = path.resolve(folderPath, fileName);
const manifestPath = path.resolve(absPath, "package.json");
return fs.access(manifestPath, fs.constants.F_OK)
.then(() => this.getExtensionByManifest(manifestPath))
.catch(() => null)
});
let extensions = await Promise.all(manifestsLoading);
extensions = extensions.filter(v => !!v); // filter out files and invalid folders (without manifest.json)
console.info(`[EXTENSION-STORE]: ${extensions.length} extensions loaded`, { folderPath, extensions });
return extensions;
}
getById(id: ExtensionId): LensExtension {
return this.extensions.get(id);
}
async removeById(id: ExtensionId) {
const extension = this.getById(id);
if (extension) {
await extension.uninstall();
this.extensions.delete(id);
}
}
@action
protected fromStore({ extensions, version }: ExtensionStoreModel) {
if (version) {
this.version = version;
}
if (extensions) {
const currentExtensions = new Map(Object.entries(extensions));
this.extensions.forEach(extension => {
if (!currentExtensions.has(extension.id)) {
this.removed.set(extension.id, extension);
}
})
currentExtensions.forEach(model => {
const extensionId = model.id || model.manifestPath;
const manifest = this.installed.get(extensionId);
if (!manifest) {
console.error(`[EXTENSION-STORE]: can't load extension manifest at ${model.manifestPath}`, { model })
return;
}
const extensionInstance = this.getById(extensionId)
if (!extensionInstance) {
try {
const { manifest: manifestJson, extensionModule } = manifest;
const LensExtensionClass = extensionModule.default;
this.extensions.set(model.id, new LensExtensionClass(model, manifestJson));
} catch (err) {
console.error(`[EXTENSION-STORE]: init extension failed: ${err}`, { model, manifest })
}
} else {
extensionInstance.importModel(model);
}
})
}
}
toJSON(): ExtensionStoreModel {
return toJS({
version: this.version,
extensions: this.extensions.toJSON(),
}, {
recurseEverything: true,
})
}
}
export const extensionStore = ExtensionStore.getInstance<ExtensionStore>()

View File

@ -0,0 +1,89 @@
import type { ExtensionModel } from "./extension-store";
import type { LensRuntimeRendererEnv } from "./lens-runtime";
import { readJsonSync } from "fs-extra";
import { action, observable, toJS } from "mobx";
import extensionManifest from "./example-extension/package.json"
import logger from "../main/logger";
export type ExtensionId = string; // instance-id or abs path to "%lens-extension/manifest.json"
export type ExtensionVersion = string | number;
export type ExtensionManifest = typeof extensionManifest & ExtensionModel;
export class LensExtension implements ExtensionModel {
public id: ExtensionId;
public updateUrl: string;
@observable name = "";
@observable description = "";
@observable version: ExtensionVersion = "0.0.0";
@observable manifest: ExtensionManifest;
@observable manifestPath: string;
@observable isEnabled = false;
@observable.ref runtime: Partial<LensRuntimeRendererEnv> = {};
constructor(model: ExtensionModel, manifest: ExtensionManifest) {
this.importModel(model, manifest);
}
@action
async importModel({ enabled, manifestPath, ...model }: ExtensionModel, manifest?: ExtensionManifest) {
try {
this.manifest = manifest || await readJsonSync(manifestPath, { throws: true })
this.manifestPath = manifestPath;
Object.assign(this, model);
} catch (err) {
logger.error(`[EXTENSION]: cannot read manifest at ${manifestPath}`, { ...model, err: String(err) })
this.disable();
}
}
async enable(runtime: LensRuntimeRendererEnv) {
this.isEnabled = true;
this.runtime = runtime;
console.log(`[EXTENSION]: enabled ${this.name}@${this.version}`, this.getMeta());
}
async disable() {
this.isEnabled = false;
this.runtime = {};
console.log(`[EXTENSION]: disabled ${this.name}@${this.version}`, this.getMeta());
}
// todo
async install(downloadUrl?: string) {
return;
}
// todo
async uninstall() {
return;
}
async hasNewVersion(): Promise<Partial<ExtensionModel>> {
return;
}
getMeta() {
return toJS({
id: this.id,
manifest: this.manifest,
manifestPath: this.manifestPath,
enabled: this.isEnabled,
runtime: this.runtime,
}, {
recurseEverything: true
})
}
toJSON(): ExtensionModel {
return {
id: this.id,
name: this.name,
version: this.version,
description: this.description,
manifestPath: this.manifestPath,
enabled: this.isEnabled,
updateUrl: this.updateUrl,
}
}
}

View File

@ -0,0 +1,19 @@
// Lens runtime for injecting to extension on activation
import { apiManager } from "../renderer/api/api-manager";
import logger from "../main/logger";
import { dynamicPages } from "../renderer/components/cluster-manager/register-page";
export interface LensRuntimeRendererEnv {
apiManager: typeof apiManager;
logger: typeof logger;
dynamicPages: typeof dynamicPages
}
// todo: expose more public runtime apis: stores, managers, etc.
export function getLensRuntime(): LensRuntimeRendererEnv {
return {
apiManager,
logger,
dynamicPages,
}
}

View File

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "AMD",
"declaration": true,
"emitDeclarationOnly": true,
"outFile": "./../../static/types/extension-api.d.ts"
},
"include": [
"../../types",
"./extension-api.ts"
]
}

View File

@ -6,6 +6,7 @@ import { addClusterURL } from "../renderer/components/+add-cluster/add-cluster.r
import { preferencesURL } from "../renderer/components/+preferences/preferences.route";
import { whatsNewURL } from "../renderer/components/+whats-new/whats-new.route";
import { clusterSettingsURL } from "../renderer/components/+cluster-settings/cluster-settings.route";
import { extensionsURL } from "../renderer/components/+extensions/extensions.route";
import logger from "./logger";
export function initMenu(windowManager: WindowManager) {
@ -67,11 +68,18 @@ export function buildMenu(windowManager: WindowManager) {
{ type: 'separator' },
{
label: 'Preferences',
accelerator: 'Cmd+,',
accelerator: 'CmdOrCtrl+,',
click() {
navigate(preferencesURL())
}
},
{
label: 'Extensions',
accelerator: 'CmdOrCtrl+E',
click() {
navigate(extensionsURL())
}
},
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },

View File

@ -4,6 +4,7 @@ import { render } from "react-dom";
import { isMac } from "../common/vars";
import { userStore } from "../common/user-store";
import { workspaceStore } from "../common/workspace-store";
import { extensionStore } from "../extensions/extension-store";
import { clusterStore, getHostedClusterId } from "../common/cluster-store";
import { i18nStore } from "./i18n";
import { themeStore } from "./theme.store";
@ -23,6 +24,7 @@ export async function bootstrap(App: AppComponent) {
userStore.load(),
workspaceStore.load(),
clusterStore.load(),
extensionStore.load(),
i18nStore.init(),
themeStore.init(),
]);

View File

@ -0,0 +1,11 @@
import { RouteProps } from "react-router";
import { buildURL } from "../../navigation";
export const extensionsRoute: RouteProps = {
path: "/extensions"
}
export interface IExtensionsRouteParams {
}
export const extensionsURL = buildURL<IExtensionsRouteParams>(extensionsRoute.path);

View File

@ -0,0 +1,4 @@
.Extensions {
$spacing: $padding * 2;
padding: $spacing;
}

View File

@ -0,0 +1,31 @@
import "./extensions.scss"
import React from "react";
import { observer } from "mobx-react";
import { extensionStore } from "../../../extensions/extension-store";
import { WizardLayout } from "../layout/wizard-layout";
import { Icon } from "../icon";
@observer
export class Extensions extends React.Component {
// todo: add input-select to customize extensions loading folder(s)
renderInfoPanel() {
return (
<div className="info-panel flex gaps align-center">
<Icon material="info"/>
<p>Extensions available to install</p>
</div>
);
}
render() {
const { installed: installedExtensions } = extensionStore;
return (
<WizardLayout className="Extensions" infoPanel={this.renderInfoPanel()}>
<h2>Extensions</h2>
<pre>
{JSON.stringify(installedExtensions.toJSON(), null, 2)}
</pre>
</WizardLayout>
);
}
}

View File

@ -0,0 +1,2 @@
export * from "./extensions.route"
export * from "./extensions"

View File

@ -4,7 +4,6 @@
position: relative;
border-radius: $radius;
padding: $radius;
margin-bottom: $padding * 2;
user-select: none;
cursor: pointer;

View File

@ -11,6 +11,7 @@ import { Workspaces, workspacesRoute } from "../+workspaces";
import { AddCluster, addClusterRoute } from "../+add-cluster";
import { ClusterView } from "./cluster-view";
import { ClusterSettings, clusterSettingsRoute } from "../+cluster-settings";
import { Extensions, extensionsRoute } from "../+extensions";
import { clusterViewRoute, clusterViewURL, getMatchedCluster, getMatchedClusterId } from "./cluster-view.route";
import { clusterStore } from "../../../common/cluster-store";
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
@ -60,6 +61,7 @@ export class ClusterManager extends React.Component {
<Route component={AddCluster} {...addClusterRoute}/>
<Route component={ClusterView} {...clusterViewRoute}/>
<Route component={ClusterSettings} {...clusterSettingsRoute}/>
<Route component={Extensions} {...extensionsRoute}/>
<Redirect exact to={this.startUrl}/>
</Switch>
</main>

View File

@ -16,10 +16,10 @@
.clusters {
@include hidden-scrollbar;
padding: 0 $spacing; // extra spacing for cluster-icon's badge
margin-bottom: $spacing;
margin-bottom: $margin;
> :last-child {
margin-bottom: $margin;
.ClusterIcon {
margin-bottom: $margin * 1.5;
}
&:empty {
@ -67,4 +67,14 @@
pointer-events: none;
}
}
> .dynamic-pages {
&:not(:empty) {
padding-top: $spacing;
}
.Icon {
--size: 40px;
}
}
}

View File

@ -21,8 +21,7 @@ import { ConfirmDialog } from "../confirm-dialog";
import { clusterIpc } from "../../../common/cluster-ipc";
import { clusterViewURL, getMatchedClusterId } from "./cluster-view.route";
import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd";
// fixme: allow to rearrange clusters with drag&drop
import { dynamicPages } from "./register-page";
interface Props {
className?: IClassName;
@ -149,6 +148,12 @@ export class ClustersMenu extends React.Component<Props> {
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>} />
)}
</div>
<div className="dynamic-pages">
{Array.from(dynamicPages.all).map(([path, { MenuIcon }]) => {
if (!MenuIcon) return;
return <MenuIcon onClick={() => navigate(path)}/>
})}
</div>
</div>
);
}

View File

@ -0,0 +1,28 @@
// Dynamic pages
import React from "react";
import { observable } from "mobx";
import type { IconProps } from "../icon";
export interface PageComponents {
Main: React.ComponentType<any>;
MenuIcon: React.ComponentType<IconProps>;
}
export class PagesStore {
all = observable.map<string, PageComponents>();
getComponents(path: string): PageComponents | null {
return this.all.get(path);
}
register(path: string, components: PageComponents) {
this.all.set(path, components);
}
unregister(path: string) {
this.all.delete(path);
}
}
export const dynamicPages = new PagesStore();

View File

@ -11,9 +11,15 @@ import { ErrorBoundary } from "./components/error-boundary";
import { WhatsNew, whatsNewRoute } from "./components/+whats-new";
import { Notifications } from "./components/notifications";
import { ConfirmDialog } from "./components/confirm-dialog";
import { extensionStore } from "../extensions/extension-store";
import { getLensRuntime } from "../extensions/lens-runtime";
@observer
export class LensApp extends React.Component {
componentDidMount() {
extensionStore.autoEnableOnLoad(getLensRuntime);
}
render() {
return (
<I18nProvider i18n={_i18n}>

View File

@ -2,7 +2,24 @@
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
## 3.6.0-beta.2 (current version)
## 3.6.0-rc.1 (current version)
- Allow user to configure directory where Kubectl binaries are downloaded
- Allow user to configure path to Kubectl binary, instead of using bundled Kubectl
- Log application logs also to log file
- Restrict file permissions to only the user for pasted kubeconfigs
- Close Preferences and Cluster Setting on Esc keypress
- Update Kubectl versions used with Lens
- Update Helm binary version
- Fix: Update CRD api to use preferred version and implement v1 differences
- Fix: Allow to drag and drop cluster icons
- Fix: Wider version select box for Helm chart installation
- Fix: Reload only active dashboard view, not the whole app window
- Fix cluster icon margins
- Fix: Reconnect non-accessible clusters on reconnect
- Fix: Bundle Kubectl and Helm binaries
- Fix: Remove double copyright
## 3.6.0-beta.2
- Fix: too narrow sidebar without clusters
- Fix app crash when iterating Events without 'kind' property defined
- Detect non-functional bundled kubectl

View File

@ -1,7 +1,6 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "./out",
"jsx": "react",
"target": "ES2017",
"module": "ESNext",

1
types/mocks.d.ts vendored
View File

@ -3,6 +3,7 @@ declare module "mac-ca"
declare module "win-ca"
declare module "@hapi/call"
declare module "@hapi/subtext"
declare module "@lens/extensions" // fixme: provide generated types from "extension-api.ts"
// Global path to static assets
declare const __static: string;

View File

@ -1,34 +0,0 @@
import path from "path";
import webpack, { LibraryTarget } from "webpack";
import { isDevelopment, buildDir } from "./src/common/vars";
export const library = "dll"
export const libraryTarget: LibraryTarget = "commonjs2"
export const manifestPath = path.resolve(buildDir, `${library}.manifest.json`);
export const packages = [
"react", "react-dom",
"ace-builds", "xterm",
"moment",
];
export default function (): webpack.Configuration {
return {
context: path.dirname(manifestPath),
mode: isDevelopment ? "development" : "production",
cache: isDevelopment,
entry: {
[library]: packages,
},
output: {
library,
libraryTarget,
},
plugins: [
new webpack.DllPlugin({
name: library,
path: manifestPath,
})
],
}
}

View File

@ -1,4 +1,4 @@
import { appName, htmlTemplate, isDevelopment, isProduction, buildDir, rendererDir, sassCommonVars, publicPath } from "./src/common/vars";
import { appName, buildDir, extensionsDir, extensionsLibName, htmlTemplate, isDevelopment, isProduction, publicPath, rendererDir, sassCommonVars } from "./src/common/vars";
import path from "path";
import webpack from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
@ -6,12 +6,32 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
import TerserPlugin from "terser-webpack-plugin";
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin"
export default function (): webpack.Configuration {
console.info('WEBPACK:renderer', require("./src/common/vars"))
export default [
webpackLensRenderer,
webpackExtensionsApi,
]
// todo: use common chunks/externals for "react", "react-dom", etc.
export function webpackExtensionsApi(): webpack.Configuration {
const config = webpackLensRenderer({ showVars: false });
config.name = "extensions-api"
config.entry = {
[extensionsLibName]: path.resolve(extensionsDir, "extension-api.ts")
};
config.output.libraryTarget = "commonjs2"
delete config.devtool;
return config;
}
export function webpackLensRenderer({ showVars = true } = {}): webpack.Configuration {
if (showVars) {
console.info('WEBPACK:renderer', require("./src/common/vars"));
}
return {
context: __dirname,
target: "electron-renderer",
devtool: "source-map", // todo: optimize in dev-mode with webpack.SourceMapDevToolPlugin
name: "lens-app",
mode: isProduction ? "production" : "development",
cache: isDevelopment,
entry: {
@ -23,6 +43,11 @@ export default function (): webpack.Configuration {
filename: '[name].js',
chunkFilename: 'chunks/[name].js',
},
stats: {
warningsFilter: [
/Critical dependency: the request of a dependency is an expression/
]
},
resolve: {
extensions: [
'.js', '.jsx', '.json',

View File

@ -1966,6 +1966,11 @@
dependencies:
"@types/node" "*"
"@types/module-alias@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/module-alias/-/module-alias-2.0.0.tgz#882668f8b8cdbda44812c3b592c590909e18849e"
integrity sha512-e3sW4oEH0qS1QxSfX7PT6xIi5qk/YSMsrB9Lq8EtkhQBZB+bKyfkP+jpLJRySanvBhAQPSv2PEBe81M8Iy/7yg==
"@types/node@*":
version "14.0.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.11.tgz#61d4886e2424da73b7b25547f59fdcb534c165a3"
@ -4086,13 +4091,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
cross-env@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9"
integrity sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==
dependencies:
cross-spawn "^7.0.1"
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@ -4112,7 +4110,7 @@ cross-spawn@^3.0.0:
lru-cache "^4.0.1"
which "^1.2.9"
cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2:
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@ -8181,6 +8179,11 @@ mock-fs@^4.12.0:
resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.12.0.tgz#a5d50b12d2d75e5bec9dac3b67ffe3c41d31ade4"
integrity sha512-/P/HtrlvBxY4o/PzXY9cCNBrdylDNxg7gnrv2sMNxj+UJ2m8jSpl0/A6fuJeNAWr99ZvGWH8XCbE0vmnM5KupQ==
module-alias@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==
moment@^2.10.2, moment@^2.26.0:
version "2.26.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"