UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

118 lines (117 loc) 4.85 kB
import { n as PACKAGE_LIFECYCLE_MARKER_CONTRACT_RELATIVE_PATH, r as PACKAGE_LIFECYCLE_PENDING_RELATIVE_PATH, t as LEGACY_PACKAGE_INSTALL_GUARD_RELATIVE_PATH } from "./package-lifecycle-marker-DhfhzqrA.js"; import path from "node:path"; import fs from "node:fs/promises"; import { spawnSync } from "node:child_process"; //#region src/infra/package-lifecycle.ts const PACKAGE_LIFECYCLE_LOCK_RELATIVE_PATH = ".openclaw-lifecycle-lock"; const DEFAULT_PACKAGE_LIFECYCLE_SCRIPT_TIMEOUT_MS = 12e5; const PACKAGE_LIFECYCLE_LOCK_POLL_MS = 100; const PACKAGE_LIFECYCLE_LOCK_RECOVERY_GRACE_MS = 12e5; const PACKAGE_LIFECYCLE_SCRIPTS = [{ name: "preinstall", relativePath: path.join("scripts", "preinstall-package-manager-warning.mjs") }, { name: "postinstall", relativePath: path.join("scripts", "postinstall-bundled-plugins.mjs") }]; function resolveLifecycleBudgetMs(scriptTimeoutMs) { return scriptTimeoutMs * PACKAGE_LIFECYCLE_SCRIPTS.length; } function hasErrorCode(error, code) { return error instanceof Error && "code" in error && error.code === code; } async function pathExists(filePath) { try { await fs.access(filePath); return true; } catch (error) { if (hasErrorCode(error, "ENOENT")) return false; throw error; } } function resolveLifecyclePaths(packageRoot) { return { pending: path.join(packageRoot, PACKAGE_LIFECYCLE_PENDING_RELATIVE_PATH), legacyGuard: path.join(packageRoot, LEGACY_PACKAGE_INSTALL_GUARD_RELATIVE_PATH), lock: path.join(packageRoot, PACKAGE_LIFECYCLE_LOCK_RELATIVE_PATH) }; } async function isPackageLifecyclePending(paths) { return await pathExists(paths.pending) || await pathExists(paths.legacyGuard); } async function ensurePendingMarker(markerPath) { try { await fs.writeFile(markerPath, "pending\n", { flag: "wx", mode: 420 }); } catch (error) { if (!hasErrorCode(error, "EEXIST")) throw error; } } async function acquireLifecycleLock(paths, scriptTimeoutMs) { let waitDeadline = Date.now() + resolveLifecycleBudgetMs(scriptTimeoutMs) + PACKAGE_LIFECYCLE_LOCK_RECOVERY_GRACE_MS; while (await isPackageLifecyclePending(paths)) try { await fs.mkdir(paths.lock); const ownerDeadline = new Date(Date.now() + resolveLifecycleBudgetMs(scriptTimeoutMs)); try { await fs.utimes(paths.lock, ownerDeadline, ownerDeadline); } catch (error) { await fs.rmdir(paths.lock).catch(() => void 0); throw error; } return async () => await fs.rmdir(paths.lock).catch(() => void 0); } catch (error) { if (!hasErrorCode(error, "EEXIST")) throw error; const stat = await fs.stat(paths.lock).catch(() => null); if (stat) { const staleAt = stat.mtimeMs + PACKAGE_LIFECYCLE_LOCK_RECOVERY_GRACE_MS; waitDeadline = Math.max(waitDeadline, staleAt); if (Date.now() >= staleAt) { await fs.rmdir(paths.lock).catch(() => void 0); continue; } } if (Date.now() >= waitDeadline) throw new Error("timed out waiting for another OpenClaw package lifecycle", { cause: error }); await new Promise((resolve) => { setTimeout(resolve, PACKAGE_LIFECYCLE_LOCK_POLL_MS); }); } return null; } function runPackageLifecycleScript(packageRoot, script, timeoutMs) { const scriptPath = path.join(packageRoot, script.relativePath); const result = spawnSync(process.execPath, [scriptPath], { cwd: packageRoot, env: process.env, stdio: "inherit", timeout: timeoutMs }); if (result.error) throw result.error; if (result.status !== 0) throw new Error(`OpenClaw package ${script.name} failed${result.signal ? ` with ${result.signal}` : ` with exit code ${result.status ?? "unknown"}`}`); } async function completePendingPackageLifecycle(params) { const packageRoot = path.resolve(params.packageRoot); const scriptTimeoutMs = params.timeoutMs ?? DEFAULT_PACKAGE_LIFECYCLE_SCRIPT_TIMEOUT_MS; const paths = resolveLifecyclePaths(packageRoot); if (!await isPackageLifecyclePending(paths)) return false; const releaseLock = await acquireLifecycleLock(paths, scriptTimeoutMs); if (!releaseLock) return false; try { if (!await isPackageLifecyclePending(paths)) return false; const finalizeLegacyMarker = !await pathExists(path.join(packageRoot, PACKAGE_LIFECYCLE_MARKER_CONTRACT_RELATIVE_PATH)); await ensurePendingMarker(paths.pending); const runScript = params.runScript ?? ((script) => runPackageLifecycleScript(packageRoot, script, scriptTimeoutMs)); for (const script of PACKAGE_LIFECYCLE_SCRIPTS) await runScript(script); if (finalizeLegacyMarker) await fs.rm(paths.pending, { force: true }); if (await isPackageLifecyclePending(paths)) throw new Error("OpenClaw package postinstall did not complete its lifecycle marker"); return true; } catch (error) { await ensurePendingMarker(paths.pending).catch(() => void 0); throw error; } finally { await releaseLock(); } } //#endregion export { completePendingPackageLifecycle as t };