codex-proxy / src /self-update.ts
icebear0828
feat: GitHub Star badge + check-for-updates in dashboard header
06a5304
raw
history blame
4.66 kB
/**
* Proxy self-update — checks for new commits on GitHub and applies via git pull.
* Only works in CLI mode (where .git exists). Docker/Electron show manual instructions.
*/
import { execFile, execFileSync } from "child_process";
import { existsSync, readFileSync } from "fs";
import { resolve } from "path";
import { promisify } from "util";
import { isEmbedded } from "./paths.js";
const execFileAsync = promisify(execFile);
export interface ProxyInfo {
version: string;
commit: string | null;
}
export interface ProxySelfUpdateResult {
commitsBehind: number;
currentCommit: string | null;
latestCommit: string | null;
}
let _proxyUpdateInProgress = false;
let _gitAvailable: boolean | null = null;
/** Read proxy version from package.json + current git commit hash. */
export function getProxyInfo(): ProxyInfo {
let version = "unknown";
try {
const pkg = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf-8"));
version = pkg.version ?? "unknown";
} catch { /* ignore */ }
let commit: string | null = null;
if (canSelfUpdate()) {
try {
const out = execFileSync("git", ["rev-parse", "--short", "HEAD"], {
cwd: process.cwd(),
encoding: "utf-8",
timeout: 5000,
});
commit = out.trim() || null;
} catch { /* ignore */ }
}
return { version, commit };
}
/** Whether this environment supports git-based self-update. */
export function canSelfUpdate(): boolean {
if (isEmbedded()) return false;
if (_gitAvailable !== null) return _gitAvailable;
// Check .git directory exists
if (!existsSync(resolve(process.cwd(), ".git"))) {
_gitAvailable = false;
return false;
}
// Check git command is available
try {
execFileSync("git", ["--version"], {
cwd: process.cwd(),
timeout: 5000,
stdio: "ignore",
});
_gitAvailable = true;
} catch {
_gitAvailable = false;
}
return _gitAvailable;
}
/** Whether a proxy self-update is currently in progress. */
export function isProxyUpdateInProgress(): boolean {
return _proxyUpdateInProgress;
}
/** Fetch latest from origin and check how many commits behind. */
export async function checkProxySelfUpdate(): Promise<ProxySelfUpdateResult> {
const cwd = process.cwd();
// Get current commit
let currentCommit: string | null = null;
try {
const { stdout } = await execFileAsync("git", ["rev-parse", "--short", "HEAD"], { cwd, timeout: 5000 });
currentCommit = stdout.trim() || null;
} catch { /* ignore */ }
// Fetch latest
try {
await execFileAsync("git", ["fetch", "origin", "master", "--quiet"], { cwd, timeout: 30000 });
} catch (err) {
console.warn("[SelfUpdate] git fetch failed:", err instanceof Error ? err.message : err);
return { commitsBehind: 0, currentCommit, latestCommit: currentCommit };
}
// Count commits behind
let commitsBehind = 0;
let latestCommit: string | null = null;
try {
const { stdout: countOut } = await execFileAsync(
"git", ["rev-list", "HEAD..origin/master", "--count"], { cwd, timeout: 5000 },
);
commitsBehind = parseInt(countOut.trim(), 10) || 0;
const { stdout: latestOut } = await execFileAsync(
"git", ["rev-parse", "--short", "origin/master"], { cwd, timeout: 5000 },
);
latestCommit = latestOut.trim() || null;
} catch { /* ignore */ }
return { commitsBehind, currentCommit, latestCommit };
}
/**
* Apply proxy self-update: git pull + npm install + npm run build.
* Runs in background. Returns immediately; check isProxyUpdateInProgress() for status.
*/
export async function applyProxySelfUpdate(): Promise<{ started: boolean; error?: string }> {
if (_proxyUpdateInProgress) {
return { started: false, error: "Update already in progress" };
}
_proxyUpdateInProgress = true;
const cwd = process.cwd();
try {
console.log("[SelfUpdate] Pulling latest code...");
await execFileAsync("git", ["pull", "origin", "master"], { cwd, timeout: 60000 });
console.log("[SelfUpdate] Installing dependencies...");
await execFileAsync("npm", ["install"], { cwd, timeout: 120000, shell: true });
console.log("[SelfUpdate] Building...");
await execFileAsync("npm", ["run", "build"], { cwd, timeout: 120000, shell: true });
console.log("[SelfUpdate] Update complete. Server restart required.");
_proxyUpdateInProgress = false;
return { started: true };
} catch (err) {
_proxyUpdateInProgress = false;
const msg = err instanceof Error ? err.message : String(err);
console.error("[SelfUpdate] Update failed:", msg);
return { started: false, error: msg };
}
}