97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
import { UpdateFailure, updatePiAndExtensions, type UpdateReporter, type UpdateResult } from "./updater.ts";
|
|
|
|
const STATUS_KEY = "pi-update";
|
|
|
|
function conciseError(error: unknown): string {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
const lines = message.split(/\r?\n/).filter(Boolean);
|
|
return lines.slice(Math.max(0, lines.length - 12)).join("\n");
|
|
}
|
|
|
|
function notify(ctx: ExtensionContext, message: string, level: "info" | "warning" | "error" = "info"): void {
|
|
if (ctx.hasUI) {
|
|
ctx.ui.notify(message, level);
|
|
} else if (level === "error") {
|
|
console.error(message);
|
|
} else if (level === "warning") {
|
|
console.warn(message);
|
|
} else {
|
|
console.log(message);
|
|
}
|
|
}
|
|
|
|
function makeReporter(ctx: ExtensionContext): UpdateReporter {
|
|
return {
|
|
info(message) {
|
|
ctx.ui.setStatus(STATUS_KEY, message);
|
|
notify(ctx, message, "info");
|
|
},
|
|
retry(message) {
|
|
ctx.ui.setStatus(STATUS_KEY, message);
|
|
notify(ctx, message, "warning");
|
|
},
|
|
};
|
|
}
|
|
|
|
function formatResult(result: UpdateResult): string {
|
|
const versionChange =
|
|
result.beforeVersion === result.afterVersion
|
|
? `Pi remains at v${result.afterVersion}`
|
|
: `Pi updated: v${result.beforeVersion} → v${result.afterVersion}`;
|
|
const extensionStatus = result.extensionsChecked
|
|
? "Configured Pi extension packages were checked successfully."
|
|
: "Extension package checking did not complete.";
|
|
return `${versionChange}\nInstall method: ${result.method}\n${extensionStatus}\nRestart Pi to load updated code.`;
|
|
}
|
|
|
|
export default function updateExtension(pi: ExtensionAPI): void {
|
|
let activeUpdate: Promise<UpdateResult> | undefined;
|
|
let updateFlagHandled = false;
|
|
|
|
pi.registerFlag("update", {
|
|
description: "Update Pi and all configured extension packages, then exit",
|
|
type: "boolean",
|
|
default: false,
|
|
});
|
|
|
|
async function runUpdate(ctx: ExtensionContext): Promise<UpdateResult> {
|
|
if (!activeUpdate) {
|
|
ctx.ui.setStatus(STATUS_KEY, "Preparing Pi update");
|
|
activeUpdate = updatePiAndExtensions(pi, makeReporter(ctx), {
|
|
projectTrusted: ctx.isProjectTrusted(),
|
|
}).finally(() => {
|
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
activeUpdate = undefined;
|
|
});
|
|
}
|
|
return activeUpdate;
|
|
}
|
|
|
|
async function handleUpdate(ctx: ExtensionContext, exitAfterUpdate: boolean): Promise<void> {
|
|
try {
|
|
const result = await runUpdate(ctx);
|
|
notify(ctx, formatResult(result), "info");
|
|
if (exitAfterUpdate || result.requiresShutdown) ctx.shutdown();
|
|
} catch (error) {
|
|
notify(ctx, `Update failed\n${conciseError(error)}`, "error");
|
|
if (exitAfterUpdate) process.exitCode = 1;
|
|
if (exitAfterUpdate || (error instanceof UpdateFailure && error.requiresShutdown)) ctx.shutdown();
|
|
}
|
|
}
|
|
|
|
pi.registerCommand("update", {
|
|
description: "Update Pi and all configured extension packages",
|
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
await ctx.waitForIdle();
|
|
await handleUpdate(ctx, false);
|
|
},
|
|
});
|
|
|
|
pi.on("session_start", async (_event, ctx) => {
|
|
if (updateFlagHandled || pi.getFlag("update") !== true) return;
|
|
updateFlagHandled = true;
|
|
await handleUpdate(ctx, true);
|
|
});
|
|
}
|