FiveM_Snippets/src/updater.ts

196 lines
6.1 KiB
TypeScript

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<boolean>("autoUpdate", false);
const now = Date.now();
const lastCheck = context.globalState.get<number>("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<string, string> = {};
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);
}
}
}