mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 16:10:44 +00:00
* refactor: extract filesystem safety primitives * refactor: use fs-safe for file access helpers * refactor: reuse fs-safe for media reads * refactor: use fs-safe for image reads * refactor: reuse fs-safe in qqbot media opener * refactor: reuse fs-safe for local media checks * refactor: consume cleaner fs-safe api * refactor: align fs-safe json option names * fix: preserve fs-safe migration contracts * refactor: use fs-safe primitive subpaths * refactor: use grouped fs-safe subpaths * refactor: align fs-safe api usage * refactor: adapt private state store api * chore: refresh proof gate * refactor: follow fs-safe json api split * refactor: follow reduced fs-safe surface * build: default fs-safe python helper off * fix: preserve fs-safe plugin sdk aliases * refactor: consolidate fs-safe usage * refactor: unify fs-safe store usage * refactor: trim fs-safe temp workspace usage * refactor: hide low-level fs-safe primitives * build: use published fs-safe package * fix: preserve outbound recovery durability after rebase * chore: refresh pr checks
134 lines
4.1 KiB
TypeScript
134 lines
4.1 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { pathExists } from "../infra/fs-safe.js";
|
|
import { readPackageName, readPackageVersion } from "../infra/package-json.js";
|
|
import type { GatewayServiceCommandConfig } from "./service-types.js";
|
|
|
|
export type GatewayServiceLayoutSummary = {
|
|
execStart: string;
|
|
sourcePath?: string;
|
|
sourcePathReal?: string;
|
|
sourceScope?: "user" | "system";
|
|
entrypoint?: string;
|
|
entrypointReal?: string;
|
|
packageRoot?: string;
|
|
packageRootReal?: string;
|
|
packageVersion?: string;
|
|
entrypointSourceCheckout?: boolean;
|
|
};
|
|
|
|
function shellQuoteArg(value: string): string {
|
|
if (/^[A-Za-z0-9_./:@%+=,-]+$/u.test(value)) {
|
|
return value;
|
|
}
|
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
}
|
|
|
|
function formatExecStart(programArguments: readonly string[]): string {
|
|
return programArguments.map(shellQuoteArg).join(" ");
|
|
}
|
|
|
|
function resolveSystemdScopeFromServicePath(
|
|
sourcePath: string | undefined,
|
|
): "user" | "system" | undefined {
|
|
const normalized = sourcePath?.replaceAll("\\", "/") ?? "";
|
|
if (!normalized.endsWith(".service")) {
|
|
return undefined;
|
|
}
|
|
if (
|
|
normalized.startsWith("/etc/systemd/") ||
|
|
normalized.startsWith("/usr/lib/systemd/") ||
|
|
normalized.startsWith("/lib/systemd/")
|
|
) {
|
|
return "system";
|
|
}
|
|
return "user";
|
|
}
|
|
|
|
function findGatewayEntrypoint(programArguments: readonly string[]): string | undefined {
|
|
const gatewayIndex = programArguments.indexOf("gateway");
|
|
if (gatewayIndex <= 0) {
|
|
return undefined;
|
|
}
|
|
return programArguments[gatewayIndex - 1];
|
|
}
|
|
|
|
async function tryRealpath(value: string | undefined): Promise<string | undefined> {
|
|
if (!value) {
|
|
return undefined;
|
|
}
|
|
const resolved = path.resolve(value);
|
|
try {
|
|
return await fs.realpath(resolved);
|
|
} catch {
|
|
return resolved;
|
|
}
|
|
}
|
|
|
|
async function isSourceCheckoutRoot(candidate: string): Promise<boolean> {
|
|
const hasRepoMarker =
|
|
(await pathExists(path.join(candidate, ".git"))) ||
|
|
(await pathExists(path.join(candidate, "pnpm-workspace.yaml")));
|
|
if (!hasRepoMarker) {
|
|
return false;
|
|
}
|
|
return (
|
|
(await pathExists(path.join(candidate, "src"))) &&
|
|
(await pathExists(path.join(candidate, "extensions")))
|
|
);
|
|
}
|
|
|
|
async function resolveOpenClawPackageRoot(entrypoint: string): Promise<string | undefined> {
|
|
let current = path.dirname(path.resolve(entrypoint));
|
|
for (let depth = 0; depth < 8; depth += 1) {
|
|
const packageJson = path.join(current, "package.json");
|
|
if (await pathExists(packageJson)) {
|
|
const name = await readPackageName(current);
|
|
if (name === "openclaw") {
|
|
return current;
|
|
}
|
|
}
|
|
const next = path.dirname(current);
|
|
if (next === current) {
|
|
return undefined;
|
|
}
|
|
current = next;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export async function summarizeGatewayServiceLayout(
|
|
command: GatewayServiceCommandConfig | null,
|
|
): Promise<GatewayServiceLayoutSummary | undefined> {
|
|
if (!command) {
|
|
return undefined;
|
|
}
|
|
const sourcePath = command.sourcePath?.trim() || undefined;
|
|
const entrypoint = findGatewayEntrypoint(command.programArguments);
|
|
const [sourcePathReal, entrypointReal] = await Promise.all([
|
|
tryRealpath(sourcePath),
|
|
tryRealpath(entrypoint),
|
|
]);
|
|
const packageRoot = entrypointReal ? await resolveOpenClawPackageRoot(entrypointReal) : undefined;
|
|
const packageRootReal = await tryRealpath(packageRoot);
|
|
const packageVersion = packageRoot
|
|
? ((await readPackageVersion(packageRoot)) ?? undefined)
|
|
: undefined;
|
|
const entrypointSourceCheckout = packageRootReal
|
|
? await isSourceCheckoutRoot(packageRootReal)
|
|
: undefined;
|
|
|
|
return {
|
|
execStart: formatExecStart(command.programArguments),
|
|
...(sourcePath ? { sourcePath } : {}),
|
|
...(sourcePathReal ? { sourcePathReal } : {}),
|
|
...(sourcePath ? { sourceScope: resolveSystemdScopeFromServicePath(sourcePath) } : {}),
|
|
...(entrypoint ? { entrypoint } : {}),
|
|
...(entrypointReal ? { entrypointReal } : {}),
|
|
...(packageRoot ? { packageRoot } : {}),
|
|
...(packageRootReal ? { packageRootReal } : {}),
|
|
...(packageVersion ? { packageVersion } : {}),
|
|
...(entrypointSourceCheckout !== undefined ? { entrypointSourceCheckout } : {}),
|
|
};
|
|
}
|