openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
118 lines (117 loc) • 4.85 kB
JavaScript
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 };