import * as vscode from "vscode"; import * as fs from "fs"; import * as os from "os"; import * as path from "path"; import fetch from "node-fetch"; // ================= LOGGING SETUP ================= export const updaterOutput = vscode.window.createOutputChannel("Updater"); export function logDebug(...args: any[]) { const message = args .map(a => (typeof a === "object" ? JSON.stringify(a, null, 2) : a)) .join(" "); updaterOutput.appendLine(message); console.log(message); // optional, still shows in DevTools updaterOutput.show(true); } // ================= CONFIG ================= const CONFIG = { apiBase: "https://git.cstmgames.dev/api/v1", repo: "CSTMGames/FiveM_Snippets", extensionId: "kj4lxc.cstmgames-fivem-snippets", checkIntervalMs: 1000 * 60 * 60 * 6, // 6 hours allowPrerelease: false, authToken: "", // optional: "your_gitea_token" }; // ================= UPDATER ================= export async function runUpdater(context: vscode.ExtensionContext) { // Run once on startup (silent) await checkForUpdates(context, true); // Schedule periodic checks setInterval(() => { checkForUpdates(context, true); }, CONFIG.checkIntervalMs); } export async function manualUpdateCheck(context: vscode.ExtensionContext) { await checkForUpdates(context, false); } async function checkForUpdates( context: vscode.ExtensionContext, silent: boolean ) { try { // Read user settings dynamically const autoInstall = vscode.workspace .getConfiguration("cstmgames.fivem_snippets") .get("autoUpdate", false); const now = Date.now(); const lastCheck = context.globalState.get("lastUpdateCheck") || 0; // Prevent too frequent checks if (now - lastCheck < CONFIG.checkIntervalMs && silent) return; context.globalState.update("lastUpdateCheck", now); // Build Gitea releases URL const url = `${CONFIG.apiBase}/repos/${CONFIG.repo}/releases`; // Build headers safely const headers: Record = {}; if (CONFIG.authToken && CONFIG.authToken.length > 0) { headers["Authorization"] = `token ${CONFIG.authToken}`; } logDebug(`[Updater] Checking Gitea releases URL: ${url}`); logDebug(`[Updater] Headers:`, headers); const res = await fetch(url, { headers }); logDebug(`[Updater] HTTP status: ${res.status}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const dataRaw = await res.json() as unknown; let data: any[] = []; if (Array.isArray(dataRaw)) { data = dataRaw; } else { logDebug("[Updater] Unexpected API response:", dataRaw); } logDebug(`[Updater] Releases data:`, data); if (!Array.isArray(data) || data.length === 0) { console.warn("[Updater] No releases found."); return; } // Take the latest release (first in array) const latestRelease = data[0]; const latestVersion = latestRelease.tag_name.replace(/^v/, ""); const ext = vscode.extensions.getExtension(CONFIG.extensionId); if (!ext) { console.warn(`[Updater] Extension ${CONFIG.extensionId} not found`); return; } const currentVersion = ext.packageJSON.version; logDebug(`[Updater] Current version: ${currentVersion}, Latest version: ${latestVersion}`); if (latestVersion === currentVersion) { if (!silent) vscode.window.showInformationMessage("Extension is up to date."); return; } const asset = latestRelease.assets?.find((a: any) => a.name.endsWith(".vsix")); if (!asset) { console.warn("[Updater] No VSIX asset found in latest release"); return; } logDebug(`[Updater] Latest VSIX URL: ${asset.browser_download_url}`); if (autoInstall) { await installVsix(asset.browser_download_url); return; } const choice = await vscode.window.showInformationMessage( `Update available: ${currentVersion} → ${latestVersion}`, "Install", "Later" ); if (choice === "Install") { await installVsix(asset.browser_download_url); } } catch (err) { console.error("[Updater] Error checking updates:", err); if (!silent) { vscode.window.showErrorMessage(`Failed to check for updates: ${err}`); } } } // ================= INSTALL FUNCTION ================= async function installVsix(url: string) { const tmpFile = path.join(os.tmpdir(), "update.vsix"); try { logDebug("[Updater] Downloading VSIX from:", url); // Fetch the VSIX const res = await fetch(url); if (!res.ok) throw new Error(`HTTP ${res.status}`); const buffer = Buffer.from(await res.arrayBuffer()); // Save to temp file fs.writeFileSync(tmpFile, buffer); logDebug("[Updater] VSIX saved to temp file:", tmpFile); // Install extension from local file await vscode.commands.executeCommand( "workbench.extensions.installExtension", vscode.Uri.file(tmpFile) ); logDebug("[Updater] Installed VSIX from temp file"); // Ask user to reload const reload = await vscode.window.showInformationMessage( "Extension updated successfully.", "Reload Now" ); if (reload === "Reload Now") { await vscode.commands.executeCommand("workbench.action.reloadWindow"); } } catch (err) { logDebug("[Updater] Install failed:", err); vscode.window.showErrorMessage("Failed to install update."); } finally { // Clean up temp file try { if (fs.existsSync(tmpFile)) { fs.unlinkSync(tmpFile); logDebug("[Updater] Deleted temp VSIX file:", tmpFile); } } catch (cleanupErr) { logDebug("[Updater] Failed to delete temp VSIX file:", cleanupErr); } } }