forked from syntaxbullet/AuroraBot-discord
275 lines
8.5 KiB
TypeScript
275 lines
8.5 KiB
TypeScript
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
|
import { createInfoEmbed, createSuccessEmbed, createWarningEmbed, createErrorEmbed } from "@lib/embeds";
|
|
import type { UpdateInfo, UpdateCheckResult } from "./update.types";
|
|
|
|
// Constants for UI
|
|
const LOG_TRUNCATE_LENGTH = 800;
|
|
const OUTPUT_TRUNCATE_LENGTH = 400;
|
|
|
|
function truncate(text: string, maxLength: number): string {
|
|
if (!text) return "";
|
|
return text.length > maxLength ? `${text.substring(0, maxLength)}...` : text;
|
|
}
|
|
|
|
// ============ Pre-Update Embeds ============
|
|
|
|
export function getCheckingEmbed() {
|
|
return createInfoEmbed("🔍 Fetching latest changes from remote...", "Checking for Updates");
|
|
}
|
|
|
|
export function getNoUpdatesEmbed(currentCommit: string) {
|
|
return createSuccessEmbed(
|
|
`You're running the latest version.\n\n**Current:** \`${currentCommit}\``,
|
|
"✅ Already Up to Date"
|
|
);
|
|
}
|
|
|
|
export function getUpdatesAvailableMessage(
|
|
updateInfo: UpdateInfo,
|
|
requirements: UpdateCheckResult,
|
|
changeCategories: Record<string, number>,
|
|
force: boolean
|
|
) {
|
|
const { branch, currentCommit, latestCommit, commitCount, commits } = updateInfo;
|
|
const { needsRootInstall, needsWebInstall, needsMigrations } = requirements;
|
|
|
|
// Build commit list (max 5)
|
|
const commitList = commits
|
|
.slice(0, 5)
|
|
.map(c => `\`${c.hash}\` ${truncate(c.message, 50)}`)
|
|
.join("\n");
|
|
|
|
const moreCommits = commitCount > 5 ? `\n*...and ${commitCount - 5} more*` : "";
|
|
|
|
// Build change categories
|
|
const categoryList = Object.entries(changeCategories)
|
|
.map(([cat, count]) => `• ${cat}: ${count} file${count > 1 ? "s" : ""}`)
|
|
.join("\n");
|
|
|
|
// Build requirements list
|
|
const reqs: string[] = [];
|
|
if (needsRootInstall) reqs.push("📦 Install root dependencies");
|
|
if (needsWebInstall) reqs.push("🌐 Install web dependencies");
|
|
if (needsMigrations) reqs.push("🗃️ Run database migrations");
|
|
if (reqs.length === 0) reqs.push("⚡ Quick update (no extra steps)");
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setTitle("📥 Updates Available")
|
|
.setColor(force ? 0xFF6B6B : 0x5865F2)
|
|
.addFields(
|
|
{
|
|
name: "Version",
|
|
value: `\`${currentCommit}\` → \`${latestCommit}\``,
|
|
inline: true
|
|
},
|
|
{
|
|
name: "Branch",
|
|
value: `\`${branch}\``,
|
|
inline: true
|
|
},
|
|
{
|
|
name: "Commits",
|
|
value: `${commitCount} new commit${commitCount > 1 ? "s" : ""}`,
|
|
inline: true
|
|
},
|
|
{
|
|
name: "Recent Changes",
|
|
value: commitList + moreCommits || "No commits",
|
|
inline: false
|
|
},
|
|
{
|
|
name: "Files Changed",
|
|
value: categoryList || "Unknown",
|
|
inline: true
|
|
},
|
|
{
|
|
name: "Update Actions",
|
|
value: reqs.join("\n"),
|
|
inline: true
|
|
}
|
|
)
|
|
.setFooter({ text: force ? "⚠️ Force mode enabled" : "This will restart the bot" })
|
|
.setTimestamp();
|
|
|
|
const confirmButton = new ButtonBuilder()
|
|
.setCustomId("confirm_update")
|
|
.setLabel(force ? "Force Update" : "Update Now")
|
|
.setEmoji(force ? "⚠️" : "🚀")
|
|
.setStyle(force ? ButtonStyle.Danger : ButtonStyle.Success);
|
|
|
|
const cancelButton = new ButtonBuilder()
|
|
.setCustomId("cancel_update")
|
|
.setLabel("Cancel")
|
|
.setStyle(ButtonStyle.Secondary);
|
|
|
|
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(confirmButton, cancelButton);
|
|
|
|
return { embeds: [embed], components: [row] };
|
|
}
|
|
|
|
// ============ Update Progress Embeds ============
|
|
|
|
export function getPreparingEmbed() {
|
|
return createInfoEmbed(
|
|
"🔒 Saving rollback point...\n📥 Preparing to download updates...",
|
|
"⏳ Preparing Update"
|
|
);
|
|
}
|
|
|
|
export function getUpdatingEmbed(requirements: UpdateCheckResult) {
|
|
const steps: string[] = ["✅ Rollback point saved"];
|
|
|
|
steps.push("📥 Downloading updates...");
|
|
|
|
if (requirements.needsRootInstall || requirements.needsWebInstall) {
|
|
steps.push("📦 Dependencies will be installed after restart");
|
|
}
|
|
if (requirements.needsMigrations) {
|
|
steps.push("🗃️ Migrations will run after restart");
|
|
}
|
|
|
|
steps.push("\n🔄 **Restarting now...**");
|
|
|
|
return createWarningEmbed(steps.join("\n"), "🚀 Updating");
|
|
}
|
|
|
|
export function getCancelledEmbed() {
|
|
return createInfoEmbed("Update cancelled. No changes were made.", "❌ Cancelled");
|
|
}
|
|
|
|
export function getTimeoutEmbed() {
|
|
return createWarningEmbed(
|
|
"No response received within 30 seconds.\nRun `/update` again when ready.",
|
|
"⏰ Timed Out"
|
|
);
|
|
}
|
|
|
|
export function getErrorEmbed(error: unknown) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
return createErrorEmbed(
|
|
`The update could not be completed:\n\`\`\`\n${truncate(message, 500)}\n\`\`\``,
|
|
"❌ Update Failed"
|
|
);
|
|
}
|
|
|
|
// ============ Post-Restart Embeds ============
|
|
|
|
export interface PostRestartResult {
|
|
installSuccess: boolean;
|
|
installOutput: string;
|
|
migrationSuccess: boolean;
|
|
migrationOutput: string;
|
|
ranInstall: boolean;
|
|
ranMigrations: boolean;
|
|
previousCommit?: string;
|
|
newCommit?: string;
|
|
}
|
|
|
|
export function getPostRestartEmbed(result: PostRestartResult, hasRollback: boolean) {
|
|
const isSuccess = result.installSuccess && result.migrationSuccess;
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setTitle(isSuccess ? "✅ Update Complete" : "⚠️ Update Completed with Issues")
|
|
.setColor(isSuccess ? 0x57F287 : 0xFEE75C)
|
|
.setTimestamp();
|
|
|
|
// Version info
|
|
if (result.previousCommit && result.newCommit) {
|
|
embed.addFields({
|
|
name: "Version",
|
|
value: `\`${result.previousCommit}\` → \`${result.newCommit}\``,
|
|
inline: false
|
|
});
|
|
}
|
|
|
|
// Results summary
|
|
const results: string[] = [];
|
|
|
|
if (result.ranInstall) {
|
|
results.push(result.installSuccess
|
|
? "✅ Dependencies installed"
|
|
: "❌ Dependency installation failed"
|
|
);
|
|
}
|
|
|
|
if (result.ranMigrations) {
|
|
results.push(result.migrationSuccess
|
|
? "✅ Migrations applied"
|
|
: "❌ Migration failed"
|
|
);
|
|
}
|
|
|
|
if (results.length > 0) {
|
|
embed.addFields({
|
|
name: "Actions Performed",
|
|
value: results.join("\n"),
|
|
inline: false
|
|
});
|
|
}
|
|
|
|
// Output details (collapsed if too long)
|
|
if (result.installOutput && !result.installSuccess) {
|
|
embed.addFields({
|
|
name: "Install Output",
|
|
value: `\`\`\`\n${truncate(result.installOutput, OUTPUT_TRUNCATE_LENGTH)}\n\`\`\``,
|
|
inline: false
|
|
});
|
|
}
|
|
|
|
if (result.migrationOutput && !result.migrationSuccess) {
|
|
embed.addFields({
|
|
name: "Migration Output",
|
|
value: `\`\`\`\n${truncate(result.migrationOutput, OUTPUT_TRUNCATE_LENGTH)}\n\`\`\``,
|
|
inline: false
|
|
});
|
|
}
|
|
|
|
// Footer with rollback hint
|
|
if (!isSuccess && hasRollback) {
|
|
embed.setFooter({ text: "💡 Use /update rollback to revert if needed" });
|
|
}
|
|
|
|
// Build components
|
|
const components: ActionRowBuilder<ButtonBuilder>[] = [];
|
|
|
|
if (!isSuccess && hasRollback) {
|
|
const rollbackButton = new ButtonBuilder()
|
|
.setCustomId("rollback_update")
|
|
.setLabel("Rollback")
|
|
.setEmoji("↩️")
|
|
.setStyle(ButtonStyle.Danger);
|
|
|
|
components.push(new ActionRowBuilder<ButtonBuilder>().addComponents(rollbackButton));
|
|
}
|
|
|
|
return { embeds: [embed], components };
|
|
}
|
|
|
|
export function getInstallingDependenciesEmbed() {
|
|
return createInfoEmbed(
|
|
"📦 Installing dependencies for root and web projects...\nThis may take a moment.",
|
|
"⏳ Installing Dependencies"
|
|
);
|
|
}
|
|
|
|
export function getRunningMigrationsEmbed() {
|
|
return createInfoEmbed(
|
|
"🗃️ Applying database migrations...",
|
|
"⏳ Running Migrations"
|
|
);
|
|
}
|
|
|
|
export function getRollbackSuccessEmbed(commit: string) {
|
|
return createSuccessEmbed(
|
|
`Successfully rolled back to commit \`${commit}\`.\nThe bot will restart now.`,
|
|
"↩️ Rollback Complete"
|
|
);
|
|
}
|
|
|
|
export function getRollbackFailedEmbed(error: string) {
|
|
return createErrorEmbed(
|
|
`Could not rollback:\n\`\`\`\n${error}\n\`\`\``,
|
|
"❌ Rollback Failed"
|
|
);
|
|
}
|