import { execFileSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; const repository = "https://github.com/wezterm/wezterm"; const api = "https://api.github.com/repos/wezterm/wezterm"; const config = new URL("../../../../wezterm.lua", import.meta.url); export function parseVersion(version) { const match = /^(\d{8}-\d{6})-([0-9a-f]{7,40})$/.exec(version); if (!match) throw new Error(`Unrecognized WezTerm version: ${version}`); return { version, timestamp: match[1], hash: match[2] }; } export function readBaseline(source) { const lines = source.split(/\r?\n/).filter(line => /^\s*local supported_version\b/.test(line)); if (!lines.length) return null; const match = /^local supported_version = (?:nil|(['"])([^'"]+)\1)\s*(?:--.*)?$/.exec(lines[0]); if (lines.length !== 1 || !match) throw new Error("Expected one literal local supported_version declaration"); return match[2] ? parseVersion(match[2]) : null; } export function versionStatus(baseline, installed) { if (!baseline) return "unknown"; if (installed.timestamp < baseline.timestamp) return "older"; return installed.timestamp === baseline.timestamp ? "equal" : "newer"; } async function request(path) { const url = `${api}/${path}`; const response = await fetch(url, { headers: { Accept: "application/vnd.github+json", "User-Agent": "wezterm-modernize" }, signal: AbortSignal.timeout(30_000), }); if (!response.ok) throw new Error(`GitHub HTTP ${response.status}: ${url}`); return response.json(); } function checkSha(sha) { if (!/^[0-9a-f]{40}$/.test(sha ?? "")) throw new Error("GitHub returned an invalid commit SHA"); return sha; } async function resolve(ref) { const sha = checkSha((await request(`commits/${ref}`)).sha); const docs = `https://raw.githubusercontent.com/wezterm/wezterm/${sha}/docs/`; return { sha, url: `${repository}/commit/${sha}`, changelog: `${docs}changelog.md`, lua_docs: `${docs}config/lua/`, docs_tree: `${repository}/tree/${sha}/docs/config/lua`, }; } export async function compare(base, head) { checkSha(base); checkSha(head); const commits = []; const seen = new Set(); let total; let status; for (let page = 1; ; page++) { const data = await request(`compare/${base}...${head}?per_page=100&page=${page}`); if (!["ahead", "identical"].includes(data.status)) { throw new Error(`Cannot review ${base}...${head}: history is ${data.status}`); } total ??= data.total_commits; status ??= data.status; if (!Number.isSafeInteger(total) || total < 0 || data.total_commits !== total || data.status !== status || !Array.isArray(data.commits)) { throw new Error("Invalid or inconsistent GitHub comparison"); } for (const commit of data.commits) { const sha = checkSha(commit.sha); if (seen.has(sha) || typeof commit.commit?.message !== "string") { throw new Error("Invalid or repeated GitHub commit"); } seen.add(sha); commits.push({ sha, title: commit.commit.message.split("\n")[0], url: `${repository}/commit/${sha}` }); } if (commits.length === total) break; if (!data.commits.length || commits.length > total) throw new Error("Incomplete GitHub comparison"); } return { url: `${repository}/compare/${base}...${head}`, status, total_commits: total, commits }; } export async function inspect(source, installedVersion) { const baseline = readBaseline(source); const installed = parseVersion(installedVersion); const upstream = await resolve("main"); const installedCommit = await resolve(installed.hash); const baselineCommit = baseline ? await resolve(baseline.hash) : null; return { config: fileURLToPath(config), baseline: baseline ? { ...baseline, ...baselineCommit } : null, installed: { ...installed, ...installedCommit }, upstream: { ref: "main", ...upstream }, runtime_check: versionStatus(baseline, installed), review: baseline ? await compare(baselineCommit.sha, installedCommit.sha) : null, requires_upgrade: await compare(installedCommit.sha, upstream.sha), }; } if (import.meta.main) { try { const source = readFileSync(config, "utf8"); const output = execFileSync(process.platform === "win32" ? "wezterm.exe" : "wezterm", ["--version"], { encoding: "utf8", timeout: 10_000, windowsHide: true, }).trim(); if (!output.startsWith("wezterm ")) throw new Error("Unexpected wezterm --version output"); console.log(JSON.stringify(await inspect(source, output.slice(8)), null, 2)); } catch (error) { console.error(error.message); process.exitCode = 1; } }