From f8882f89d4de189fb1203978a2ea16cb050da59d Mon Sep 17 00:00:00 2001 From: Sergey Pronin Date: Thu, 3 Sep 2026 00:02:22 +0300 Subject: [PATCH] show prerequisites in the plugin hub Signed-off-by: Sergey Pronin --- src/components/Drawer.ts | 60 +++++++++++++++++++++++++++++++++++++++- src/styles.ts | 40 +++++++++++++++++++++++++++ src/types.ts | 14 ++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/components/Drawer.ts b/src/components/Drawer.ts index 6bdaecd..2b6e94a 100644 --- a/src/components/Drawer.ts +++ b/src/components/Drawer.ts @@ -2,7 +2,7 @@ import { h, React } from '../runtime'; import { styles } from '../styles'; import { defaultChannelVersion, helmInstallCommand } from '../catalog'; import { IconImg, resolveIconSrc } from '../icons'; -import type { CatalogEntry } from '../types'; +import type { CatalogEntry, Prerequisite } from '../types'; function humanizeKey(key: string): string { return key @@ -53,6 +53,62 @@ function renderCapabilityValue(key: string, value: unknown): any { ); } +function prerequisiteHelmCommand(pre: Prerequisite): string { + const helm = pre.helm!; + const release = pre.name; + const lines = [`helm install ${release} ${helm.oci} \\`]; + if (helm.version) lines.push(` --version ${helm.version} \\`); + lines.push(` -n ${helm.namespace}${helm.createNamespace ? ' --create-namespace' : ''}`); + for (const [k, v] of Object.entries(helm.defaultValues ?? {})) { + lines[lines.length - 1] += ' \\'; + lines.push(` --set ${k}=${v}`); + } + return lines.join('\n'); +} + +function renderPrerequisite(pre: Prerequisite, key: number): any { + return h( + 'div', + { key, style: styles.prereqCard }, + h( + 'div', + { style: styles.prereqHead }, + h('span', { style: styles.prereqName }, pre.name), + pre.installUrl + ? h( + 'a', + { + href: pre.installUrl, + target: '_blank', + rel: 'noopener noreferrer', + style: styles.prereqLink, + }, + 'Docs ↗', + ) + : null, + ), + pre.description ? h('div', { style: styles.prereqDesc }, pre.description) : null, + pre.helm + ? h( + 'details', + null, + h('summary', { style: styles.prereqSummary }, 'Install command'), + h('pre', { style: { ...styles.codeBlock, marginTop: '0.5rem' } }, prerequisiteHelmCommand(pre)), + ) + : null, + ); +} + +function renderPrerequisites(prerequisites: Prerequisite[] | undefined): any { + if (!prerequisites || !prerequisites.length) return null; + return h( + 'div', + { style: styles.section }, + h('h3', { style: styles.sectionTitle }, 'Prerequisites'), + h('div', { style: styles.prereqList }, ...prerequisites.map(renderPrerequisite)), + ); +} + function renderCapabilities(caps: Record): any { const entries = Object.entries(caps); if (!entries.length) return null; @@ -247,6 +303,8 @@ export function Drawer(props: { entry: CatalogEntry; pluginName: string; onClose ) : null, + isGated ? null : renderPrerequisites(entry.install?.prerequisites), + h( 'div', { style: styles.section }, diff --git a/src/styles.ts b/src/styles.ts index 900e7eb..76764f5 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -289,4 +289,44 @@ export const styles = { whiteSpace: 'pre' as const, overflowX: 'auto' as const, }, + prereqList: { + display: 'flex', + flexDirection: 'column' as const, + gap: '0.5rem', + } as const, + prereqCard: { + border: '1px solid #e5e7eb', + borderRadius: 8, + padding: '0.625rem 0.75rem', + background: '#f9fafb', + } as const, + prereqHead: { + display: 'flex', + alignItems: 'baseline', + justifyContent: 'space-between', + gap: '0.5rem', + } as const, + prereqName: { + fontWeight: 600, + color: '#111827', + fontSize: '0.875rem', + } as const, + prereqDesc: { + color: '#374151', + fontSize: '0.8125rem', + marginTop: '0.2rem', + } as const, + prereqLink: { + fontSize: '0.75rem', + color: '#2563eb', + textDecoration: 'none', + whiteSpace: 'nowrap' as const, + } as const, + prereqSummary: { + cursor: 'pointer', + fontSize: '0.75rem', + color: '#2563eb', + marginTop: '0.5rem', + userSelect: 'none' as const, + } as const, }; diff --git a/src/types.ts b/src/types.ts index 5040cc6..766037f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,6 +2,19 @@ export type ExtensionType = 'plugin' | 'provider' | string; +export interface Prerequisite { + name: string; + description?: string; + installUrl?: string; + helm?: { + oci: string; + version?: string; + namespace: string; + createNamespace?: boolean; + defaultValues?: Record; + }; +} + export interface CatalogEntry { name: string; type: ExtensionType; @@ -24,6 +37,7 @@ export interface CatalogEntry { }; install?: { helm?: { namespace?: string; releaseName?: string }; + prerequisites?: Prerequisite[]; }; plugin?: { contributes?: { backend?: boolean; ui?: boolean; cli?: boolean };