fvttt
Version:
FVTTT is a CLI tool for tunneling Foundry VTT application through Cloudflare.
112 lines (96 loc) • 3.58 kB
text/typescript
import * as p from "@clack/prompts";
import { bin, install } from "cloudflared";
import fs from "node:fs";
import { execSync } from "node:child_process";
export const CLOUDFLARED_VERSION = "2026.5.2";
export const GITHUB_CLOUDFLARED_URL =
"https://api.github.com/repos/cloudflare/cloudflared/releases/latest";
export const TUNNEL_TIMEOUT = 30000;
/**
* Fetches the latest cloudflared version from GitHub
*/
export async function getLatestVersion(): Promise<string | null> {
try {
const response = await fetch(GITHUB_CLOUDFLARED_URL);
if (!response.ok) {
throw new Error(`GitHub responded with status: ${response.status}`);
}
const data = (await response.json()) as { tag_name: string };
return data.tag_name;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
p.log.error(`Error fetching latest Cloudflared version: ${errorMessage}`);
return null;
}
}
/**
* Compares two semantic version strings
* @returns 1 if v1 > v2, -1 if v1 < v2, 0 if equal
*/
export function compareVersion(v1: string, v2: string): number {
const parts1 = v1.split(".").map(Number);
const parts2 = v2.split(".").map(Number);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const p1 = parts1[i] || 0;
const p2 = parts2[i] || 0;
if (p1 > p2) return 1;
if (p1 < p2) return -1;
}
return 0;
}
/**
* Parses the version number from cloudflared version output
*/
export function parseVersion(versionOutput: string): string | null {
const versionMatch = versionOutput.match(/version (\d+\.\d+\.\d+)/);
return versionMatch?.[1] ?? null;
}
/**
* Checks if cloudflared is installed and up-to-date, installs or updates if necessary
*/
export async function installCloudflared(): Promise<void> {
const targetVersion = (await getLatestVersion()) || CLOUDFLARED_VERSION;
let needsInstall = false;
if (!fs.existsSync(bin)) {
p.log.info("Cloudflared not found. Installing...");
needsInstall = true;
} else {
try {
const cloudflaredVersionOutput = execSync(`"${bin}" --version`)
.toString()
.trim();
p.log.info(`Current ${cloudflaredVersionOutput}`);
const currentVersion = parseVersion(cloudflaredVersionOutput);
if (!currentVersion) {
p.log.info("Unable to get current version. Will attempt to update.");
needsInstall = true;
} else if (compareVersion(targetVersion, currentVersion) > 0) {
p.log.info(
`Cloudflared version ${currentVersion} is older than ${targetVersion}. Updating...`
);
needsInstall = true;
} else {
p.log.info(`Using Cloudflared version ${currentVersion}`);
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
p.log.error(`Error checking Cloudflared version: ${errorMessage}`);
needsInstall = true;
}
}
if (needsInstall) {
const s = p.spinner();
s.start(`Installing Cloudflared ${targetVersion}...`);
try {
await install(bin, targetVersion);
s.stop(`Cloudflared ${targetVersion} installed successfully.`);
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
s.stop(`Failed to install Cloudflared: ${errorMessage}`);
p.log.error(`Error installing Cloudflared: ${errorMessage}`);
process.exit(1);
}
}
}