Archived
Merge branch 'main' into feat/plugin-api-completeness
This commit is contained in:
@@ -1,10 +1,6 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import type { HtmlTemplateTag, PiWebComponentStatus, PiWebInstallationInfo, PiWebPlugin, PiWebStatusMessage, PiWebStatusResponse, PluginRuntimeState, WorkspacePanelTerminal } from "@jmfederico/pi-web/plugin-api";
|
||||
|
||||
interface CommandEntry {
|
||||
label: string;
|
||||
command: string;
|
||||
}
|
||||
import type { HtmlTemplateTag, PiWebComponentStatus, PiWebPlugin, PiWebStatusResponse, PluginRuntimeState, WorkspacePanelTerminal } from "@jmfederico/pi-web/plugin-api";
|
||||
import { additionalCommands, formatVersion, installationLabel, messageCount, recommendedCommand, shouldShowUpdatesPanel, statusFor } from "./updatesLogic.js";
|
||||
|
||||
function runCommandInTerminal(terminal: WorkspacePanelTerminal, label: string, command: string): void {
|
||||
void terminal.runCommand({
|
||||
@@ -17,74 +13,6 @@ function runCommandInTerminal(terminal: WorkspacePanelTerminal, label: string, c
|
||||
});
|
||||
}
|
||||
|
||||
// The single command users should run when they do not want to think: if an
|
||||
// update is available, `commands.update` already chains the update and a full
|
||||
// restart; otherwise, when anything is stale, a full restart is enough.
|
||||
function recommendedCommand(status: PiWebStatusResponse): CommandEntry | undefined {
|
||||
const { commands, release, components } = status;
|
||||
if (release.updateAvailable && typeof commands.update === "string" && commands.update !== "") {
|
||||
return { label: "Update & restart everything", command: commands.update };
|
||||
}
|
||||
const restartNeeded = components.web.stale || components.sessiond.stale || !components.sessiond.available;
|
||||
if (restartNeeded && typeof commands.restart === "string" && commands.restart !== "") {
|
||||
return { label: "Restart everything", command: commands.restart };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function additionalCommands(status: PiWebStatusResponse, recommended: CommandEntry | undefined): CommandEntry[] {
|
||||
return [
|
||||
["Update", status.commands.update],
|
||||
["Restart all", status.commands.restart],
|
||||
["Restart Web/UI", status.commands.restartWeb],
|
||||
["Restart session daemon", status.commands.restartSessiond],
|
||||
["Status", status.commands.status],
|
||||
]
|
||||
.filter((entry): entry is [string, string] => typeof entry[1] === "string" && entry[1] !== "")
|
||||
.filter(([, command]) => command !== recommended?.command)
|
||||
.map(([label, command]) => ({ label, command }));
|
||||
}
|
||||
|
||||
function messagesFor(state: PluginRuntimeState | undefined): PiWebStatusMessage[] {
|
||||
return state?.piWebStatus?.messages ?? [];
|
||||
}
|
||||
|
||||
function statusFor(state: PluginRuntimeState | undefined): PiWebStatusResponse | undefined {
|
||||
return state?.piWebStatus;
|
||||
}
|
||||
|
||||
function messageCount(state: PluginRuntimeState | undefined): number {
|
||||
return messagesFor(state).length;
|
||||
}
|
||||
|
||||
function isLocalOrUnknownInstallation(installation: PiWebInstallationInfo | undefined): boolean {
|
||||
return installation === undefined || installation.kind === "local" || installation.kind === "unknown";
|
||||
}
|
||||
|
||||
function shouldShowUpdatesPanel(state: PluginRuntimeState | undefined): boolean {
|
||||
const status = statusFor(state);
|
||||
if (messageCount(state) > 0) return true;
|
||||
if (status === undefined) return false;
|
||||
return isLocalOrUnknownInstallation(status.components.web.installation)
|
||||
|| isLocalOrUnknownInstallation(status.components.sessiond.installation);
|
||||
}
|
||||
|
||||
function formatVersion(version: string | undefined): string {
|
||||
return version === undefined || version === "" ? "unknown" : version;
|
||||
}
|
||||
|
||||
function installationLabel(installation: PiWebInstallationInfo | undefined): string {
|
||||
if (installation === undefined) return "installation unknown";
|
||||
if (installation.kind === "pi-package") {
|
||||
const scope = installation.scope === undefined ? "" : ` · ${installation.scope}`;
|
||||
const source = installation.source ?? "Pi package";
|
||||
return `${source}${scope}`;
|
||||
}
|
||||
if (installation.kind === "npm-global") return "global npm package";
|
||||
if (installation.kind === "local") return "local checkout";
|
||||
return "installation unknown";
|
||||
}
|
||||
|
||||
function renderComponent(html: HtmlTemplateTag, component: PiWebComponentStatus): TemplateResult {
|
||||
const status = !component.available
|
||||
? "unavailable"
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { PiWebComponentStatus, PiWebStatusMessage, PiWebStatusResponse, PluginRuntimeState } from "@jmfederico/pi-web/plugin-api";
|
||||
import { additionalCommands, formatVersion, installationLabel, messageCount, recommendedCommand, shouldShowUpdatesPanel } from "./updatesLogic";
|
||||
|
||||
function component(overrides: Partial<PiWebComponentStatus> = {}): PiWebComponentStatus {
|
||||
return {
|
||||
component: "web",
|
||||
label: "Web/UI",
|
||||
runtimeVersion: "1.202605.8",
|
||||
installedVersion: "1.202605.8",
|
||||
stale: false,
|
||||
available: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function status(overrides: Partial<PiWebStatusResponse> = {}): PiWebStatusResponse {
|
||||
return {
|
||||
packageName: "@jmfederico/pi-web",
|
||||
generatedAt: "2026-06-14T00:00:00.000Z",
|
||||
components: {
|
||||
web: component({ component: "web", label: "Web/UI" }),
|
||||
sessiond: component({ component: "sessiond", label: "Session daemon" }),
|
||||
},
|
||||
release: { packageName: "@jmfederico/pi-web", updateAvailable: false },
|
||||
commands: {},
|
||||
messages: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function stateWith(value: PiWebStatusResponse | undefined): PluginRuntimeState {
|
||||
return value === undefined ? {} : { piWebStatus: value };
|
||||
}
|
||||
|
||||
describe("recommendedCommand", () => {
|
||||
it("recommends update & restart when an update is available", () => {
|
||||
const result = recommendedCommand(status({
|
||||
release: { packageName: "@jmfederico/pi-web", updateAvailable: true },
|
||||
commands: { update: "pi-web update && pi-web restart", restart: "pi-web restart" },
|
||||
}));
|
||||
expect(result).toEqual({ label: "Update & restart everything", command: "pi-web update && pi-web restart" });
|
||||
});
|
||||
|
||||
it("falls through to restart when an update is available but the update command is empty", () => {
|
||||
const result = recommendedCommand(status({
|
||||
release: { packageName: "@jmfederico/pi-web", updateAvailable: true },
|
||||
components: {
|
||||
web: component({ stale: true }),
|
||||
sessiond: component({ component: "sessiond", label: "Session daemon" }),
|
||||
},
|
||||
commands: { update: "", restart: "pi-web restart" },
|
||||
}));
|
||||
expect(result).toEqual({ label: "Restart everything", command: "pi-web restart" });
|
||||
});
|
||||
|
||||
it("recommends restart when the web component is stale", () => {
|
||||
const result = recommendedCommand(status({
|
||||
components: {
|
||||
web: component({ stale: true }),
|
||||
sessiond: component({ component: "sessiond", label: "Session daemon" }),
|
||||
},
|
||||
commands: { restart: "pi-web restart" },
|
||||
}));
|
||||
expect(result).toEqual({ label: "Restart everything", command: "pi-web restart" });
|
||||
});
|
||||
|
||||
it("recommends restart when the session daemon is unavailable", () => {
|
||||
const result = recommendedCommand(status({
|
||||
components: {
|
||||
web: component(),
|
||||
sessiond: component({ component: "sessiond", label: "Session daemon", available: false }),
|
||||
},
|
||||
commands: { restart: "pi-web restart" },
|
||||
}));
|
||||
expect(result).toEqual({ label: "Restart everything", command: "pi-web restart" });
|
||||
});
|
||||
|
||||
it("returns nothing when everything is current and available", () => {
|
||||
expect(recommendedCommand(status({ commands: { restart: "pi-web restart" } }))).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not fabricate a restart command when one is not configured", () => {
|
||||
const result = recommendedCommand(status({
|
||||
components: {
|
||||
web: component({ stale: true }),
|
||||
sessiond: component({ component: "sessiond", label: "Session daemon" }),
|
||||
},
|
||||
commands: { restart: "" },
|
||||
}));
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("additionalCommands", () => {
|
||||
it("drops empty commands and the recommended command, preserving order", () => {
|
||||
const value = status({
|
||||
commands: {
|
||||
update: "pi-web update",
|
||||
restart: "pi-web restart",
|
||||
restartWeb: "",
|
||||
restartSessiond: "pi-web restart sessiond",
|
||||
status: "pi-web status",
|
||||
},
|
||||
});
|
||||
const result = additionalCommands(value, { label: "Restart everything", command: "pi-web restart" });
|
||||
expect(result).toEqual([
|
||||
{ label: "Update", command: "pi-web update" },
|
||||
{ label: "Restart session daemon", command: "pi-web restart sessiond" },
|
||||
{ label: "Status", command: "pi-web status" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps all commands when there is no recommended command", () => {
|
||||
const value = status({ commands: { update: "pi-web update", status: "pi-web status" } });
|
||||
expect(additionalCommands(value, undefined)).toEqual([
|
||||
{ label: "Update", command: "pi-web update" },
|
||||
{ label: "Status", command: "pi-web status" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldShowUpdatesPanel", () => {
|
||||
const messages: PiWebStatusMessage[] = [{ id: "x", severity: "warning", title: "t", body: "b" }];
|
||||
|
||||
it("shows the panel whenever there are messages, even on a managed install", () => {
|
||||
const value = status({
|
||||
messages,
|
||||
components: {
|
||||
web: component({ installation: { kind: "pi-package" } }),
|
||||
sessiond: component({ component: "sessiond", label: "Session daemon", installation: { kind: "pi-package" } }),
|
||||
},
|
||||
});
|
||||
expect(shouldShowUpdatesPanel(stateWith(value))).toBe(true);
|
||||
});
|
||||
|
||||
it("hides the panel when status is unavailable", () => {
|
||||
expect(shouldShowUpdatesPanel(stateWith(undefined))).toBe(false);
|
||||
expect(shouldShowUpdatesPanel(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it("shows the panel for local or unknown installs", () => {
|
||||
const local = status({
|
||||
components: {
|
||||
web: component({ installation: { kind: "local" } }),
|
||||
sessiond: component({ component: "sessiond", label: "Session daemon", installation: { kind: "pi-package" } }),
|
||||
},
|
||||
});
|
||||
expect(shouldShowUpdatesPanel(stateWith(local))).toBe(true);
|
||||
|
||||
const unknown = status({
|
||||
components: {
|
||||
web: component({ installation: { kind: "pi-package" } }),
|
||||
sessiond: component({ component: "sessiond", label: "Session daemon" }),
|
||||
},
|
||||
});
|
||||
expect(shouldShowUpdatesPanel(stateWith(unknown))).toBe(true);
|
||||
});
|
||||
|
||||
it("hides the panel for fully managed installs with no messages", () => {
|
||||
const value = status({
|
||||
components: {
|
||||
web: component({ installation: { kind: "pi-package" } }),
|
||||
sessiond: component({ component: "sessiond", label: "Session daemon", installation: { kind: "npm-global" } }),
|
||||
},
|
||||
});
|
||||
expect(shouldShowUpdatesPanel(stateWith(value))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("messageCount", () => {
|
||||
it("counts messages and tolerates missing status", () => {
|
||||
expect(messageCount(undefined)).toBe(0);
|
||||
expect(messageCount(stateWith(status()))).toBe(0);
|
||||
expect(messageCount(stateWith(status({ messages: [{ id: "a", severity: "info", title: "t", body: "b" }] })))).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatVersion", () => {
|
||||
it("renders unknown for missing or empty versions", () => {
|
||||
expect(formatVersion(undefined)).toBe("unknown");
|
||||
expect(formatVersion("")).toBe("unknown");
|
||||
expect(formatVersion("1.202605.8")).toBe("1.202605.8");
|
||||
});
|
||||
});
|
||||
|
||||
describe("installationLabel", () => {
|
||||
it("labels each installation kind", () => {
|
||||
expect(installationLabel(undefined)).toBe("installation unknown");
|
||||
expect(installationLabel({ kind: "unknown" })).toBe("installation unknown");
|
||||
expect(installationLabel({ kind: "npm-global" })).toBe("global npm package");
|
||||
expect(installationLabel({ kind: "local" })).toBe("local checkout");
|
||||
});
|
||||
|
||||
it("includes source and scope for pi-package installs", () => {
|
||||
expect(installationLabel({ kind: "pi-package", source: "npm:@jmfederico/pi-web", scope: "user" }))
|
||||
.toBe("npm:@jmfederico/pi-web · user");
|
||||
});
|
||||
|
||||
it("defaults the source and omits scope when absent", () => {
|
||||
expect(installationLabel({ kind: "pi-package" })).toBe("Pi package");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import type { PiWebInstallationInfo, PiWebStatusMessage, PiWebStatusResponse, PluginRuntimeState } from "@jmfederico/pi-web/plugin-api";
|
||||
|
||||
export interface CommandEntry {
|
||||
label: string;
|
||||
command: string;
|
||||
}
|
||||
|
||||
// The single command users should run when they do not want to think: if an
|
||||
// update is available, `commands.update` already chains the update and a full
|
||||
// restart; otherwise, when anything is stale, a full restart is enough.
|
||||
export function recommendedCommand(status: PiWebStatusResponse): CommandEntry | undefined {
|
||||
const { commands, release, components } = status;
|
||||
if (release.updateAvailable && typeof commands.update === "string" && commands.update !== "") {
|
||||
return { label: "Update & restart everything", command: commands.update };
|
||||
}
|
||||
const restartNeeded = components.web.stale || components.sessiond.stale || !components.sessiond.available;
|
||||
if (restartNeeded && typeof commands.restart === "string" && commands.restart !== "") {
|
||||
return { label: "Restart everything", command: commands.restart };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function additionalCommands(status: PiWebStatusResponse, recommended: CommandEntry | undefined): CommandEntry[] {
|
||||
return [
|
||||
["Update", status.commands.update],
|
||||
["Restart all", status.commands.restart],
|
||||
["Restart Web/UI", status.commands.restartWeb],
|
||||
["Restart session daemon", status.commands.restartSessiond],
|
||||
["Status", status.commands.status],
|
||||
]
|
||||
.filter((entry): entry is [string, string] => typeof entry[1] === "string" && entry[1] !== "")
|
||||
.filter(([, command]) => command !== recommended?.command)
|
||||
.map(([label, command]) => ({ label, command }));
|
||||
}
|
||||
|
||||
export function messagesFor(state: PluginRuntimeState | undefined): PiWebStatusMessage[] {
|
||||
return state?.piWebStatus?.messages ?? [];
|
||||
}
|
||||
|
||||
export function statusFor(state: PluginRuntimeState | undefined): PiWebStatusResponse | undefined {
|
||||
return state?.piWebStatus;
|
||||
}
|
||||
|
||||
export function messageCount(state: PluginRuntimeState | undefined): number {
|
||||
return messagesFor(state).length;
|
||||
}
|
||||
|
||||
export function isLocalOrUnknownInstallation(installation: PiWebInstallationInfo | undefined): boolean {
|
||||
return installation === undefined || installation.kind === "local" || installation.kind === "unknown";
|
||||
}
|
||||
|
||||
export function shouldShowUpdatesPanel(state: PluginRuntimeState | undefined): boolean {
|
||||
const status = statusFor(state);
|
||||
if (messageCount(state) > 0) return true;
|
||||
if (status === undefined) return false;
|
||||
return isLocalOrUnknownInstallation(status.components.web.installation)
|
||||
|| isLocalOrUnknownInstallation(status.components.sessiond.installation);
|
||||
}
|
||||
|
||||
export function formatVersion(version: string | undefined): string {
|
||||
return version === undefined || version === "" ? "unknown" : version;
|
||||
}
|
||||
|
||||
export function installationLabel(installation: PiWebInstallationInfo | undefined): string {
|
||||
if (installation === undefined) return "installation unknown";
|
||||
if (installation.kind === "pi-package") {
|
||||
const scope = installation.scope === undefined ? "" : ` · ${installation.scope}`;
|
||||
const source = installation.source ?? "Pi package";
|
||||
return `${source}${scope}`;
|
||||
}
|
||||
if (installation.kind === "npm-global") return "global npm package";
|
||||
if (installation.kind === "local") return "local checkout";
|
||||
return "installation unknown";
|
||||
}
|
||||
Reference in New Issue
Block a user