import { createCommand } from "@lib/utils"; import { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, MessageFlags, ButtonBuilder, ButtonStyle, ActionRowBuilder, ComponentType } from "discord.js"; import { createErrorEmbed, createSuccessEmbed, createWarningEmbed, createInfoEmbed } from "@lib/embeds"; export const update = createCommand({ data: new SlashCommandBuilder() .setName("update") .setDescription("Check for updates and restart the bot") .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), execute: async (interaction) => { await interaction.deferReply({ flags: MessageFlags.Ephemeral }); const { exec } = await import("child_process"); const { promisify } = await import("util"); const { writeFile, appendFile } = await import("fs/promises"); const execAsync = promisify(exec); try { // Get current branch const { stdout: branchName } = await execAsync("git rev-parse --abbrev-ref HEAD"); const branch = branchName.trim(); await interaction.editReply({ embeds: [createInfoEmbed("Fetching latest changes...", "Checking for Updates")] }); // Fetch remote await execAsync("git fetch --all"); // Check for potential changes const { stdout: logOutput } = await execAsync(`git log HEAD..origin/${branch} --oneline`); if (!logOutput.trim()) { await interaction.editReply({ embeds: [createSuccessEmbed("The bot is already up to date.", "No Updates Found")] }); return; } // Prepare confirmation UI const confirmButton = new ButtonBuilder() .setCustomId("confirm_update") .setLabel("Update & Restart") .setStyle(ButtonStyle.Success); const cancelButton = new ButtonBuilder() .setCustomId("cancel_update") .setLabel("Cancel") .setStyle(ButtonStyle.Secondary); const row = new ActionRowBuilder() .addComponents(confirmButton, cancelButton); const updateEmbed = createInfoEmbed( `The following changes are available:\n\`\`\`\n${logOutput.substring(0, 1000)}${logOutput.length > 1000 ? "\n...and more" : ""}\n\`\`\`\n**Do you want to update and restart?**`, "Updates Available" ); const response = await interaction.editReply({ embeds: [updateEmbed], components: [row] }); try { const confirmation = await response.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, componentType: ComponentType.Button, time: 30000 // 30 seconds timeout }); if (confirmation.customId === "confirm_update") { await confirmation.update({ embeds: [createWarningEmbed("Applying updates and restarting...", "Update In Progress")], components: [] }); const { stdout } = await execAsync(`git reset --hard origin/${branch}`); // Run DB Migrations await confirmation.editReply({ embeds: [createWarningEmbed("Updating database schema...", "Running Migrations")], components: [] }); let migrationOutput = ""; try { const { stdout: dbOut } = await execAsync("bun run db:push:local"); migrationOutput = dbOut; } catch (dbErr: any) { migrationOutput = `Migration Failed: ${dbErr.message}`; console.error("Migration Error:", dbErr); // We continue with restart even if migration fails? // Probably safer to try, or should we abort? // For now, let's log it and proceed, as code might depend on it but maybe it was a no-op partial fail. } await interaction.followUp({ flags: MessageFlags.Ephemeral, embeds: [createSuccessEmbed(`Git Reset Output:\n\`\`\`\n${stdout}\n\`\`\`\nDB Migration Output:\n\`\`\`\n${migrationOutput}\n\`\`\`\nRestarting process...`, "Update Successful")] }); // Write context for post-restart notification await writeFile(".restart_context.json", JSON.stringify({ channelId: interaction.channelId, userId: interaction.user.id, timestamp: Date.now() })); // Trigger restart await appendFile("src/index.ts", " "); } else { await confirmation.update({ embeds: [createInfoEmbed("Update cancelled.", "Cancelled")], components: [] }); } } catch (e) { // Timeout await interaction.editReply({ embeds: [createWarningEmbed("Update confirmation timed out.", "Timed Out")], components: [] }); } } catch (error) { console.error(error); await interaction.editReply({ embeds: [createErrorEmbed(`Failed to check for updates:\n\`\`\`\n${error instanceof Error ? error.message : String(error)}\n\`\`\``, "Update Check Failed")] }); } } });