mirror of https://github.com/openclaw/openclaw.git
Guard updater service refresh against missing invocation cwd (#45486)
* Update: capture a stable cwd for service refresh env * Test: cover service refresh when cwd disappears
This commit is contained in:
parent
fac754041c
commit
65f92fd839
|
|
@ -668,6 +668,54 @@ describe("update-cli", () => {
|
||||||
expect(runDaemonInstall).not.toHaveBeenCalled();
|
expect(runDaemonInstall).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("updateCommand reuses the captured invocation cwd when process.cwd later fails", async () => {
|
||||||
|
const root = createCaseDir("openclaw-updated-root");
|
||||||
|
const entryPath = path.join(root, "dist", "entry.js");
|
||||||
|
pathExists.mockImplementation(async (candidate: string) => candidate === entryPath);
|
||||||
|
|
||||||
|
const originalCwd = process.cwd();
|
||||||
|
let restoreCwd: (() => void) | undefined;
|
||||||
|
vi.mocked(runGatewayUpdate).mockImplementation(async () => {
|
||||||
|
const cwdSpy = vi.spyOn(process, "cwd").mockImplementation(() => {
|
||||||
|
throw new Error("ENOENT: current working directory is gone");
|
||||||
|
});
|
||||||
|
restoreCwd = () => cwdSpy.mockRestore();
|
||||||
|
return {
|
||||||
|
status: "ok",
|
||||||
|
mode: "npm",
|
||||||
|
root,
|
||||||
|
steps: [],
|
||||||
|
durationMs: 100,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
serviceLoaded.mockResolvedValue(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await withEnvAsync(
|
||||||
|
{
|
||||||
|
OPENCLAW_STATE_DIR: "./state",
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
await updateCommand({});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
restoreCwd?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(runCommandWithTimeout).toHaveBeenCalledWith(
|
||||||
|
[expect.stringMatching(/node/), entryPath, "gateway", "install", "--force"],
|
||||||
|
expect.objectContaining({
|
||||||
|
cwd: root,
|
||||||
|
env: expect.objectContaining({
|
||||||
|
OPENCLAW_STATE_DIR: path.resolve(originalCwd, "./state"),
|
||||||
|
}),
|
||||||
|
timeoutMs: 60_000,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(runDaemonInstall).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("updateCommand falls back to restart when env refresh install fails", async () => {
|
it("updateCommand falls back to restart when env refresh install fails", async () => {
|
||||||
await runRestartFallbackScenario({ daemonInstall: "fail" });
|
await runRestartFallbackScenario({ daemonInstall: "fail" });
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -124,9 +124,17 @@ function formatCommandFailure(stdout: string, stderr: string): string {
|
||||||
return detail.split("\n").slice(-3).join("\n");
|
return detail.split("\n").slice(-3).join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tryResolveInvocationCwd(): string | undefined {
|
||||||
|
try {
|
||||||
|
return process.cwd();
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function resolveServiceRefreshEnv(
|
function resolveServiceRefreshEnv(
|
||||||
env: NodeJS.ProcessEnv,
|
env: NodeJS.ProcessEnv,
|
||||||
invocationCwd: string = process.cwd(),
|
invocationCwd?: string,
|
||||||
): NodeJS.ProcessEnv {
|
): NodeJS.ProcessEnv {
|
||||||
const resolvedEnv: NodeJS.ProcessEnv = { ...env };
|
const resolvedEnv: NodeJS.ProcessEnv = { ...env };
|
||||||
for (const key of SERVICE_REFRESH_PATH_ENV_KEYS) {
|
for (const key of SERVICE_REFRESH_PATH_ENV_KEYS) {
|
||||||
|
|
@ -138,6 +146,10 @@ function resolveServiceRefreshEnv(
|
||||||
resolvedEnv[key] = rawValue;
|
resolvedEnv[key] = rawValue;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!invocationCwd) {
|
||||||
|
resolvedEnv[key] = rawValue;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
resolvedEnv[key] = path.resolve(invocationCwd, rawValue);
|
resolvedEnv[key] = path.resolve(invocationCwd, rawValue);
|
||||||
}
|
}
|
||||||
return resolvedEnv;
|
return resolvedEnv;
|
||||||
|
|
@ -205,6 +217,7 @@ function printDryRunPreview(preview: UpdateDryRunPreview, jsonMode: boolean): vo
|
||||||
async function refreshGatewayServiceEnv(params: {
|
async function refreshGatewayServiceEnv(params: {
|
||||||
result: UpdateRunResult;
|
result: UpdateRunResult;
|
||||||
jsonMode: boolean;
|
jsonMode: boolean;
|
||||||
|
invocationCwd?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const args = ["gateway", "install", "--force"];
|
const args = ["gateway", "install", "--force"];
|
||||||
if (params.jsonMode) {
|
if (params.jsonMode) {
|
||||||
|
|
@ -217,7 +230,7 @@ async function refreshGatewayServiceEnv(params: {
|
||||||
}
|
}
|
||||||
const res = await runCommandWithTimeout([resolveNodeRunner(), candidate, ...args], {
|
const res = await runCommandWithTimeout([resolveNodeRunner(), candidate, ...args], {
|
||||||
cwd: params.result.root,
|
cwd: params.result.root,
|
||||||
env: resolveServiceRefreshEnv(process.env),
|
env: resolveServiceRefreshEnv(process.env, params.invocationCwd),
|
||||||
timeoutMs: SERVICE_REFRESH_TIMEOUT_MS,
|
timeoutMs: SERVICE_REFRESH_TIMEOUT_MS,
|
||||||
});
|
});
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
|
|
@ -547,6 +560,7 @@ async function maybeRestartService(params: {
|
||||||
refreshServiceEnv: boolean;
|
refreshServiceEnv: boolean;
|
||||||
gatewayPort: number;
|
gatewayPort: number;
|
||||||
restartScriptPath?: string | null;
|
restartScriptPath?: string | null;
|
||||||
|
invocationCwd?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
if (params.shouldRestart) {
|
if (params.shouldRestart) {
|
||||||
if (!params.opts.json) {
|
if (!params.opts.json) {
|
||||||
|
|
@ -562,6 +576,7 @@ async function maybeRestartService(params: {
|
||||||
await refreshGatewayServiceEnv({
|
await refreshGatewayServiceEnv({
|
||||||
result: params.result,
|
result: params.result,
|
||||||
jsonMode: Boolean(params.opts.json),
|
jsonMode: Boolean(params.opts.json),
|
||||||
|
invocationCwd: params.invocationCwd,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!params.opts.json) {
|
if (!params.opts.json) {
|
||||||
|
|
@ -667,6 +682,7 @@ async function maybeRestartService(params: {
|
||||||
|
|
||||||
export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
||||||
suppressDeprecations();
|
suppressDeprecations();
|
||||||
|
const invocationCwd = tryResolveInvocationCwd();
|
||||||
|
|
||||||
const timeoutMs = parseTimeoutMsOrExit(opts.timeout);
|
const timeoutMs = parseTimeoutMsOrExit(opts.timeout);
|
||||||
const shouldRestart = opts.restart !== false;
|
const shouldRestart = opts.restart !== false;
|
||||||
|
|
@ -949,6 +965,7 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
||||||
refreshServiceEnv: refreshGatewayServiceEnv,
|
refreshServiceEnv: refreshGatewayServiceEnv,
|
||||||
gatewayPort,
|
gatewayPort,
|
||||||
restartScriptPath,
|
restartScriptPath,
|
||||||
|
invocationCwd,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!opts.json) {
|
if (!opts.json) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue