import type { TemplateResult } from "lit"; import type { HtmlTemplateTag, PiWebComponentStatus, PiWebPlugin, PiWebStatusResponse, PluginRuntimeState, WorkspacePanelTerminal } from "@jmfederico/pi-web/plugin-api"; import { additionalCommands, fallbackDockerStatus, formatVersion, installationLabel, messageCount, recommendedCommand, shouldShowUpdatesPanel, statusFor, type UpdatesRuntimeHint } from "./updatesLogic.js"; function runCommandInTerminal(terminal: WorkspacePanelTerminal, label: string, command: string): void { void terminal.runCommand({ title: label, command, open: true, metadata: { "pi.plugin": "updates" }, }).catch((error: unknown) => { console.error(`Updates plugin failed to run "${label}"`, error); }); } function renderComponent(html: HtmlTemplateTag, component: PiWebComponentStatus): TemplateResult { const status = !component.available ? "unavailable" : component.stale ? "restart needed" : "current"; return html`
${component.label} ${status} running ${formatVersion(component.runtimeVersion)} · installed ${formatVersion(component.installedVersion)} ${installationLabel(component.installation)}${component.installation?.path === undefined ? "" : ` · ${component.installation.path}`}
`; } function renderCommandActions(html: HtmlTemplateTag, terminal: WorkspacePanelTerminal | undefined, label: string, command: string): TemplateResult { return html` ${terminal === undefined ? null : html``} `; } function renderCommand(html: HtmlTemplateTag, terminal: WorkspacePanelTerminal | undefined, label: string, command: string): TemplateResult { return html`
${label} ${command} ${renderCommandActions(html, terminal, label, command)}
`; } function updatesRuntimeHintFromModuleUrl(moduleUrl: string): UpdatesRuntimeHint { try { const dockerMode = new URL(moduleUrl).searchParams.get("piWebDockerMode"); return dockerMode === "runtime" || dockerMode === "dev" ? { dockerMode } : {}; } catch { return {}; } } const runtimeHint = updatesRuntimeHintFromModuleUrl(import.meta.url); function renderCommands(html: HtmlTemplateTag, terminal: WorkspacePanelTerminal | undefined, status: PiWebStatusResponse): TemplateResult | undefined { const recommended = recommendedCommand(status); const additional = additionalCommands(status, recommended); if (recommended === undefined && additional.length === 0) return undefined; return html` ${recommended === undefined ? null : html` `} ${additional.length === 0 ? null : html`
${recommended === undefined ? "Suggested commands" : "Additional commands (optional)"} ${recommended === undefined ? null : html`

Only needed for finer control, such as restarting a single service.

`} ${additional.map((entry) => renderCommand(html, terminal, entry.label, entry.command))}
`} `; } function renderUpdatesPanel(html: HtmlTemplateTag, terminal: WorkspacePanelTerminal | undefined, state: PluginRuntimeState | undefined): TemplateResult { const status = statusFor(state) ?? fallbackDockerStatus(runtimeHint); if (status === undefined) { return html`
Updates

Checking PI WEB update status…

`; } const messages = status.messages; return html`
Updates${messages.length > 0 ? html`${String(messages.length)}` : null}
${messages.length === 0 ? html`

No PI WEB update or restart messages.

` : messages.map((message) => html`
${message.title}${message.severity}

${message.body}

${message.command === undefined ? null : html`
${message.command} ${renderCommandActions(html, terminal, message.title, message.command)}
`}
`)}
Installed services ${renderComponent(html, status.components.web)} ${renderComponent(html, status.components.sessiond)}
${renderCommands(html, terminal, status)}
Generated ${status.generatedAt} ${status.release.latestVersion === undefined ? null : html`Latest npm release ${status.release.latestVersion}`} ${status.release.skipped === true ? html`Remote version check skipped.` : null} ${status.release.error === undefined ? null : html`Remote version check failed: ${status.release.error}`}
`; } const plugin: PiWebPlugin = { apiVersion: 1, name: "Updates", activate: ({ html, svg }) => ({ contributions: { workspacePanels: [ { id: "workspace.updates", title: "Updates", icon: svg` `, order: 100, visible: (context) => shouldShowUpdatesPanel(context.state, runtimeHint), badge: (context) => { const count = messageCount(context.state); return count > 0 ? count : undefined; }, render: (context) => renderUpdatesPanel(html, context.terminal, context.state), }, ], }, }), }; export default plugin;