UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

299 lines (298 loc) 12.3 kB
import { w as parseStrictPositiveInteger } from "./number-coercion-CLj0HTDM.js"; import { t as resolveOpenClawPackageRoot } from "./openclaw-root-CfYY-fyD.js"; import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js"; import { r as defaultRuntime } from "./runtime-CF2WjnNZ.js"; import { o as resolveRequiredHomeDir } from "./home-dir-BPhrG-aM.js"; import { t as hasErrnoCode } from "./errno-CkbDOfLk.js"; import { l as pathExists } from "./utils-P__uGsPB.js"; import "./errors-Db3Ymjlb.js"; import { r as theme } from "./theme-vjDs9tao.js"; import { n as readPackageName } from "./package-json-BU3dNR1B.js"; import { o as parseSemver } from "./runtime-guard-2dUH2iVI.js"; import { r as runCommandWithTimeout } from "./exec-BIE-3oLG.js"; import { a as runStep, d as detectGlobalInstallManagerForRoot, l as createGlobalInstallEnv, o as canResolveRegistryVersionForPackageTarget, u as detectGlobalInstallManagerByPresence } from "./update-runner-command-Cn6wUQJP.js"; import { r as isJsonOutputModeActive } from "./json-output-mode-D0F07HLf.js"; import { n as COMPLETION_SKIP_PLUGIN_COMMANDS_ENV } from "./completion-runtime-CjrvYENP.js"; import { t as normalizePackageTagInput } from "./package-tag-C-hHwRJg.js"; import { r as fetchNpmTagVersion } from "./update-check-CshgmMpJ.js"; import path from "node:path"; import fs from "node:fs/promises"; import { spawnSync } from "node:child_process"; import os from "node:os"; //#region src/cli/update-cli/shared.ts var UpdatePreMutationError = class extends Error { constructor(reason, message) { super(message); this.reason = reason; this.name = "UpdatePreMutationError"; } }; const INVALID_TIMEOUT_ERROR = "--timeout must be a positive integer (seconds)"; const MAX_SAFE_TIMEOUT_SECONDS = Math.floor(Number.MAX_SAFE_INTEGER / 1e3); /** Parse the shared timeout contract without exiting an owning operation. */ function parseUpdateTimeoutMs(timeout) { if (timeout === void 0) return; const trimmed = timeout.trim(); const seconds = parseStrictPositiveInteger(trimmed); if (seconds === void 0 || seconds > MAX_SAFE_TIMEOUT_SECONDS) throw new Error(INVALID_TIMEOUT_ERROR); return seconds * 1e3; } /** Parse a CLI timeout in seconds, exiting through the runtime on invalid input. */ function parseTimeoutMsOrExit(timeout) { try { return parseUpdateTimeoutMs(timeout); } catch (error) { if (isJsonOutputModeActive(process.argv)) throw error; defaultRuntime.error(INVALID_TIMEOUT_ERROR); defaultRuntime.exit(1); return null; } } const UPSTREAM_REPOSITORY_URL = "https://github.com/openclaw/openclaw.git"; const GIT_CLONE_BLOB_FILTER = "--filter=blob:none"; const DEFAULT_PACKAGE_NAME = "openclaw"; const CORE_PACKAGE_NAMES = /* @__PURE__ */ new Set([DEFAULT_PACKAGE_NAME]); /** Normalize a CLI tag/version/spec into the npm target form accepted by update flows. */ function normalizeTag(value) { return normalizePackageTagInput(value, ["openclaw", DEFAULT_PACKAGE_NAME]); } function normalizeVersionTag(tag) { const trimmed = tag.trim(); if (!trimmed) return null; const cleaned = trimmed.startsWith("v") ? trimmed.slice(1) : trimmed; return parseSemver(cleaned) ? cleaned : null; } /** Resolve an npm dist-tag or explicit version into a concrete package version. */ async function resolveTargetVersion(tag, timeoutMs, options = {}) { if (!canResolveRegistryVersionForPackageTarget(tag)) return null; const direct = normalizeVersionTag(tag); if (direct) return direct; return (await fetchNpmTagVersion({ tag, timeoutMs, spec: options.spec, command: options.command, cwd: options.cwd, env: options.env })).version ?? null; } /** Return true when `root` is a local git checkout directory. */ async function isGitCheckout(root) { try { await fs.stat(path.join(root, ".git")); return true; } catch { return false; } } async function isCorePackage(root) { const name = await readPackageName(root); return Boolean(name && CORE_PACKAGE_NAMES.has(name)); } /** Return true only for existing directories with no entries. */ async function isEmptyDir(targetPath) { try { return (await fs.readdir(targetPath)).length === 0; } catch { return false; } } /** Resolve the checkout path used by source-based self-update. */ function resolveGitInstallDir() { const override = process.env.OPENCLAW_GIT_DIR?.trim(); if (override) return path.resolve(override); return resolveDefaultGitDir(); } function resolveDefaultGitDir() { const home = resolveRequiredHomeDir(process.env, os.homedir); if (home.startsWith("/")) return path.posix.join(home, "openclaw"); return path.join(home, "openclaw"); } /** Prefer the current Node executable, falling back to `node` when run through another shim. */ function resolveNodeRunner() { const base = normalizeLowercaseStringOrEmpty(path.basename(process.execPath)); if (base === "node" || base === "node.exe") return process.execPath; return "node"; } function tryResolveInvocationCwd() { try { return process.cwd(); } catch { return; } } /** Locate the installed OpenClaw package root that should receive update operations. */ async function resolveUpdateRoot() { return (process.argv[1] ? await resolveOpenClawPackageRoot({ cwd: path.dirname(path.resolve(process.argv[1])) }) : null) ?? await resolveOpenClawPackageRoot({ moduleUrl: import.meta.url, cwd: process.cwd() }) ?? process.cwd(); } /** Run one update subprocess and report bounded stdout/stderr tails to progress listeners. */ async function runUpdateStep(params) { return await runStep({ ...params, cwd: params.cwd ?? process.cwd(), runCommand: runCommandWithTimeout, stepIndex: 0, totalSteps: 0 }); } async function cloneGitCheckoutTransactionally(params) { const parentDir = path.dirname(params.dir); await fs.mkdir(parentDir, { recursive: true }); const canonicalParentDir = await fs.realpath(parentDir); const preserveDir = await pathExists(params.dir) && await isEmptyDir(params.dir); const targetDir = preserveDir ? await fs.realpath(params.dir) : path.join(canonicalParentDir, path.basename(params.dir)); const stagingParent = preserveDir ? targetDir : canonicalParentDir; const stagingDir = await fs.mkdtemp(path.join(stagingParent, ".openclaw-clone-")); let cleanupStaging = true; try { const result = await runUpdateStep({ name: "git clone", argv: [ "git", "clone", GIT_CLONE_BLOB_FILTER, UPSTREAM_REPOSITORY_URL, stagingDir ], env: params.env, timeoutMs: params.timeoutMs, progress: params.progress }); if (result.exitCode !== 0) return { checkoutDir: targetDir, step: result }; if (!preserveDir) try { await fs.lstat(targetDir); } catch (error) { if (!hasErrnoCode(error, "ENOENT")) throw error; await fs.rename(stagingDir, targetDir); return { checkoutDir: targetDir, step: result }; } if (!preserveDir) throw new Error(`OPENCLAW_GIT_DIR appeared while cloning: ${params.dir}. The existing path was left unchanged; move it or choose another OPENCLAW_GIT_DIR, then retry.`); const expectedEntries = preserveDir ? [path.basename(stagingDir)] : []; if ((await fs.readdir(targetDir)).toSorted().join("\0") !== expectedEntries.toSorted().join("\0")) throw new Error(`OPENCLAW_GIT_DIR appeared while cloning: ${params.dir}. The existing path was left unchanged; move it or choose another OPENCLAW_GIT_DIR, then retry.`); const entries = (await fs.readdir(stagingDir)).toSorted((a, b) => a === ".git" ? 1 : b === ".git" ? -1 : 0); const moved = []; let publishError; try { for (const entry of entries) { await fs.rename(path.join(stagingDir, entry), path.join(targetDir, entry)); moved.push(entry); } } catch (error) { publishError = { value: error }; } if (publishError) { const rollbackErrors = []; for (const entry of moved.toReversed()) try { await fs.rename(path.join(targetDir, entry), path.join(stagingDir, entry)); } catch (rollbackError) { rollbackErrors.push(rollbackError); } if (rollbackErrors.length > 0) { cleanupStaging = false; throw new AggregateError([publishError.value, ...rollbackErrors], `Could not publish or fully roll back the cloned checkout at ${targetDir}; recovery files remain at ${stagingDir}`); } throw publishError.value; } return { checkoutDir: targetDir, step: result }; } finally { if (cleanupStaging) await fs.rm(stagingDir, { recursive: true, force: true }); } } /** Ensure the configured source-update directory exists and points at an OpenClaw checkout. */ async function ensureGitCheckout(params) { const gitEnv = params.env ?? await createGlobalInstallEnv(); if (!await pathExists(params.dir)) return await cloneGitCheckoutTransactionally({ dir: params.dir, env: gitEnv, timeoutMs: params.timeoutMs, progress: params.progress }); if (!await isGitCheckout(params.dir)) { if (!await isEmptyDir(params.dir)) throw new UpdatePreMutationError("invalid-git-directory", `OPENCLAW_GIT_DIR points at a non-git directory: ${params.dir}. Set OPENCLAW_GIT_DIR to an empty folder or an openclaw checkout.`); return await cloneGitCheckoutTransactionally({ dir: params.dir, env: gitEnv, timeoutMs: params.timeoutMs, progress: params.progress }); } if (!await isCorePackage(params.dir)) throw new UpdatePreMutationError("invalid-git-directory", `OPENCLAW_GIT_DIR does not look like a core checkout: ${params.dir}.`); return { checkoutDir: await fs.realpath(params.dir), step: null }; } /** Detect the package manager that owns a global/package OpenClaw install. */ async function resolveGlobalManager(params) { const runCommand = createGlobalCommandRunner(); if (params.installKind === "package") { const detected = await detectGlobalInstallManagerForRoot(runCommand, params.root, params.timeoutMs); if (!detected) throw new Error("Update refused: package manager owner is unknown; no changes were made. Run this OpenClaw install through its active npm, pnpm, or Bun global shim, or reinstall it with that package manager, then retry."); return detected; } return await detectGlobalInstallManagerByPresence(runCommand, params.timeoutMs) ?? "npm"; } const COMPLETION_CACHE_WRITE_TIMEOUT_MS = 3e4; const COMPLETION_CACHE_MANUAL_REFRESH_HINT = "Shell tab-completion may be stale; refresh manually with: openclaw completion --write-state"; /** Best-effort refresh of shell completion state after a successful update. */ async function tryWriteCompletionCache(root, jsonMode) { const binPath = path.join(root, "openclaw.mjs"); if (!await pathExists(binPath)) return "skipped"; const result = spawnSync(resolveNodeRunner(), [ binPath, "completion", "--write-state" ], { cwd: root, env: { ...process.env, [COMPLETION_SKIP_PLUGIN_COMMANDS_ENV]: "1" }, encoding: "utf-8", timeout: COMPLETION_CACHE_WRITE_TIMEOUT_MS }); if (result.error) { if (!jsonMode) { const reason = result.error.code === "ETIMEDOUT" ? `timed out after ${COMPLETION_CACHE_WRITE_TIMEOUT_MS / 1e3}s` : String(result.error); defaultRuntime.log(theme.warn(`Completion cache update failed: ${reason}. ${COMPLETION_CACHE_MANUAL_REFRESH_HINT}`)); } return "failed"; } if (result.status !== 0) { if (!jsonMode) { const stderr = (result.stderr ?? "").trim(); const detail = stderr ? ` (${stderr})` : ""; defaultRuntime.log(theme.warn(`Completion cache update failed${detail}. ${COMPLETION_CACHE_MANUAL_REFRESH_HINT}`)); } return "failed"; } return "completed"; } /** Adapter used by global-install detection helpers to execute bounded subprocess probes. */ function createGlobalCommandRunner() { return async (argv, options) => { const res = await runCommandWithTimeout(argv, options); return { stdout: res.stdout, stderr: res.stderr, code: res.code }; }; } //#endregion export { tryWriteCompletionCache as _, isEmptyDir as a, parseTimeoutMsOrExit as c, resolveGlobalManager as d, resolveNodeRunner as f, tryResolveInvocationCwd as g, runUpdateStep as h, ensureGitCheckout as i, parseUpdateTimeoutMs as l, resolveUpdateRoot as m, UpdatePreMutationError as n, isGitCheckout as o, resolveTargetVersion as p, createGlobalCommandRunner as r, normalizeTag as s, DEFAULT_PACKAGE_NAME as t, resolveGitInstallDir as u };