import { spawn } from "node:child_process"; import https from "node:https"; import readline from "node:readline"; import { compareVersions } from "./version"; function requestJson(url: string): Promise { return new Promise((resolve, reject) => { const req = https.get(url, { headers: { "User-Agent": "kandev-npx" } }, (res) => { if (res.statusCode !== 200) { return reject(new Error(`HTTP ${res.statusCode} fetching ${url}`)); } let body = ""; res.on("data", (chunk) => (body += chunk)); res.on("end", () => { try { resolve(JSON.parse(body) as T); } catch { reject(new Error(`Failed to parse JSON from ${url}`)); } }); }); req.setTimeout(5000, () => { req.destroy(new Error(`Request timed out fetching ${url}`)); }); req.on("error", reject); }); } async function getLatestNpmVersion(): Promise { const data = await requestJson<{ "dist-tags"?: { latest?: string } }>( "https://registry.npmjs.org/kandev", ); return data?.["dist-tags"]?.latest; } function promptYesNo(question: string, defaultYes = false): Promise { return new Promise((resolve) => { if (!process.stdin.isTTY) { resolve(false); return; } const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); const suffix = defaultYes ? "[Y/n]" : "[y/N]"; rl.question(`${question} ${suffix} `, (answer: string) => { rl.close(); const normalized = String(answer || "") .trim() .toLowerCase(); if (!normalized) { resolve(Boolean(defaultYes)); return; } resolve(normalized === "y" || normalized === "yes"); }); }); } export async function maybePromptForUpdate(currentVersion: string, args: string[]) { // Allow disabling update checks for CI or automation. if (process.env.KANDEV_SKIP_UPDATE === "1" || process.env.KANDEV_NO_UPDATE_PROMPT === "1") { return; } try { const latest = await getLatestNpmVersion(); if (!latest) return; if (compareVersions(latest, currentVersion) <= 0) return; const wantsUpdate = await promptYesNo( `Update available: ${currentVersion} -> ${latest}. Update now?`, false, ); if (!wantsUpdate) return; const env = { ...process.env, KANDEV_SKIP_UPDATE: "1" }; const child = spawn("npx", ["kandev@latest", ...args], { stdio: "inherit", env, }); child.on("exit", (code) => process.exit(code || 0)); await new Promise(() => {}); } catch { // ignore update errors } }