UNPKG

browse

Version:

Unified Browserbase CLI for browser automation and cloud APIs.

154 lines (153 loc) 4.93 kB
import { spawn } from "node:child_process"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; import { fileURLToPath } from "node:url"; import semver from "semver"; const CLI_PACKAGE_NAME = "browse"; const DEFAULT_NPM_REGISTRY_BASE_URL = "https://registry.npmjs.org/"; const UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1000; const UPDATE_TIMEOUT_MS = 1500; export async function maybeAutoUpdateCli(currentVersion, env = process.env, options = {}) { if (env.BROWSE_DISABLE_UPDATE_CHECK === "1" || env.BB_DISABLE_UPDATE_CHECK === "1") { return; } const cachePath = resolveUpdateCheckPath(env, options); if (!cachePath) { return; } const cache = await readFreshUpdateCheckCache(cachePath); if (cache) { if (isVersionNewer(currentVersion, cache.version)) { writeUpdateNotice(currentVersion, cache.version); } return; } spawnBackgroundUpdateCheck(env, cachePath); } export async function refreshUpdateCheckCache(env = process.env, options = {}) { const cachePath = resolveUpdateCheckPath(env, options); if (!cachePath) { return; } const latestVersion = await fetchLatestCliVersion(); if (!latestVersion) { return; } await writeUpdateCheckCache(cachePath, { checkedAt: new Date().toISOString(), version: latestVersion, }); } async function readFreshUpdateCheckCache(cachePath) { const cache = await readUpdateCheckCache(cachePath); if (!cache) { return null; } const checkedAtMs = Date.parse(cache.checkedAt); if (!Number.isFinite(checkedAtMs)) { return null; } if (Date.now() - checkedAtMs >= UPDATE_CACHE_TTL_MS) { return null; } return cache; } async function readUpdateCheckCache(cachePath) { try { const contents = await readFile(cachePath, "utf8"); const parsed = JSON.parse(contents); if (typeof parsed.version !== "string" || parsed.version.length === 0) { return null; } if (typeof parsed.checkedAt !== "string" || parsed.checkedAt.length === 0) { return null; } return { checkedAt: parsed.checkedAt, version: parsed.version, }; } catch { return null; } } async function writeUpdateCheckCache(cachePath, cache) { try { await mkdir(dirname(cachePath), { recursive: true }); await writeFile(cachePath, `${JSON.stringify(cache)}\n`, "utf8"); } catch { // Best-effort cache writes should never affect CLI behavior. } } function resolveUpdateCheckPath(env, options = {}) { const configured = options.cacheFile ?? env.BROWSE_UPDATE_CHECK_FILE ?? env.BB_UPDATE_CHECK_FILE; return configured && configured.length > 0 ? configured : null; } function spawnBackgroundUpdateCheck(env, cachePath) { try { const workerPath = resolveUpdateCheckWorkerPath(); const childEnv = { ...env, BROWSE_UPDATE_CHECK_FILE: cachePath, }; const child = spawn(process.execPath, [workerPath], { detached: true, env: childEnv, stdio: "ignore", windowsHide: true, }); child.unref(); } catch { // Background update checks are best-effort only. } } function resolveUpdateCheckWorkerPath() { const extension = import.meta.url.endsWith(".ts") ? "ts" : "js"; return fileURLToPath(new URL(`../update-check-worker.${extension}`, import.meta.url)); } function writeUpdateNotice(currentVersion, latestVersion) { process.stderr.write([ `Update available: ${currentVersion} -> ${latestVersion}.`, "Run:", ` npm i -g ${CLI_PACKAGE_NAME}@latest`, "", ].join("\n")); } async function fetchLatestCliVersion() { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), UPDATE_TIMEOUT_MS); try { const latestUrl = new URL(`${encodeURIComponent(CLI_PACKAGE_NAME)}/latest`, DEFAULT_NPM_REGISTRY_BASE_URL); const response = await fetch(latestUrl, { headers: { accept: "application/json", }, signal: controller.signal, }); if (!response.ok) { return null; } const payload = (await response.json()); if (typeof payload.version !== "string" || payload.version.length === 0) { return null; } return payload.version; } catch { return null; } finally { clearTimeout(timeout); } } function isVersionNewer(currentVersion, latestVersion) { if (!semver.valid(currentVersion) || !semver.valid(latestVersion)) { return false; } return semver.gt(latestVersion, currentVersion); }