mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 06:21:36 +00:00
fix(cli): refresh gateway service env during update (#21071)
* changelog: add security deepMerge prototype-pollution fix entry * update: refresh gateway service env during update restart * test(cli): fix daemon install mock assertion * test(cli): guard update restart false path
This commit is contained in:
@@ -15,6 +15,7 @@ const resolveGlobalManager = vi.fn();
|
|||||||
const serviceLoaded = vi.fn();
|
const serviceLoaded = vi.fn();
|
||||||
const prepareRestartScript = vi.fn();
|
const prepareRestartScript = vi.fn();
|
||||||
const runRestartScript = vi.fn();
|
const runRestartScript = vi.fn();
|
||||||
|
const mockedRunDaemonInstall = vi.fn();
|
||||||
|
|
||||||
vi.mock("@clack/prompts", () => ({
|
vi.mock("@clack/prompts", () => ({
|
||||||
confirm,
|
confirm,
|
||||||
@@ -93,6 +94,7 @@ vi.mock("../commands/doctor.js", () => ({
|
|||||||
}));
|
}));
|
||||||
// Mock the daemon-cli module
|
// Mock the daemon-cli module
|
||||||
vi.mock("./daemon-cli.js", () => ({
|
vi.mock("./daemon-cli.js", () => ({
|
||||||
|
runDaemonInstall: mockedRunDaemonInstall,
|
||||||
runDaemonRestart: vi.fn(),
|
runDaemonRestart: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -111,7 +113,7 @@ const { readConfigFileSnapshot, writeConfigFile } = await import("../config/conf
|
|||||||
const { checkUpdateStatus, fetchNpmTagVersion, resolveNpmChannelTag } =
|
const { checkUpdateStatus, fetchNpmTagVersion, resolveNpmChannelTag } =
|
||||||
await import("../infra/update-check.js");
|
await import("../infra/update-check.js");
|
||||||
const { runCommandWithTimeout } = await import("../process/exec.js");
|
const { runCommandWithTimeout } = await import("../process/exec.js");
|
||||||
const { runDaemonRestart } = await import("./daemon-cli.js");
|
const { runDaemonRestart, runDaemonInstall } = await import("./daemon-cli.js");
|
||||||
const { doctorCommand } = await import("../commands/doctor.js");
|
const { doctorCommand } = await import("../commands/doctor.js");
|
||||||
const { defaultRuntime } = await import("../runtime.js");
|
const { defaultRuntime } = await import("../runtime.js");
|
||||||
const { updateCommand, registerUpdateCli, updateStatusCommand, updateWizardCommand } =
|
const { updateCommand, registerUpdateCli, updateStatusCommand, updateWizardCommand } =
|
||||||
@@ -219,6 +221,7 @@ describe("update-cli", () => {
|
|||||||
vi.mocked(resolveNpmChannelTag).mockReset();
|
vi.mocked(resolveNpmChannelTag).mockReset();
|
||||||
vi.mocked(runCommandWithTimeout).mockReset();
|
vi.mocked(runCommandWithTimeout).mockReset();
|
||||||
vi.mocked(runDaemonRestart).mockReset();
|
vi.mocked(runDaemonRestart).mockReset();
|
||||||
|
vi.mocked(mockedRunDaemonInstall).mockReset();
|
||||||
vi.mocked(doctorCommand).mockReset();
|
vi.mocked(doctorCommand).mockReset();
|
||||||
vi.mocked(defaultRuntime.log).mockReset();
|
vi.mocked(defaultRuntime.log).mockReset();
|
||||||
vi.mocked(defaultRuntime.error).mockReset();
|
vi.mocked(defaultRuntime.error).mockReset();
|
||||||
@@ -278,6 +281,7 @@ describe("update-cli", () => {
|
|||||||
serviceLoaded.mockResolvedValue(false);
|
serviceLoaded.mockResolvedValue(false);
|
||||||
prepareRestartScript.mockResolvedValue("/tmp/openclaw-restart-test.sh");
|
prepareRestartScript.mockResolvedValue("/tmp/openclaw-restart-test.sh");
|
||||||
runRestartScript.mockResolvedValue(undefined);
|
runRestartScript.mockResolvedValue(undefined);
|
||||||
|
runDaemonInstall.mockResolvedValue(undefined);
|
||||||
setTty(false);
|
setTty(false);
|
||||||
setStdoutTty(false);
|
setStdoutTty(false);
|
||||||
});
|
});
|
||||||
@@ -460,6 +464,61 @@ describe("update-cli", () => {
|
|||||||
expect(runDaemonRestart).toHaveBeenCalled();
|
expect(runDaemonRestart).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("updateCommand refreshes gateway service env when service is already installed", async () => {
|
||||||
|
const mockResult: UpdateRunResult = {
|
||||||
|
status: "ok",
|
||||||
|
mode: "git",
|
||||||
|
steps: [],
|
||||||
|
durationMs: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(runGatewayUpdate).mockResolvedValue(mockResult);
|
||||||
|
vi.mocked(runDaemonInstall).mockResolvedValue(undefined);
|
||||||
|
serviceLoaded.mockResolvedValue(true);
|
||||||
|
|
||||||
|
await updateCommand({});
|
||||||
|
|
||||||
|
expect(runDaemonInstall).toHaveBeenCalledWith({
|
||||||
|
force: true,
|
||||||
|
json: undefined,
|
||||||
|
});
|
||||||
|
expect(runDaemonRestart).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateCommand falls back to restart when env refresh install fails", async () => {
|
||||||
|
const mockResult: UpdateRunResult = {
|
||||||
|
status: "ok",
|
||||||
|
mode: "git",
|
||||||
|
steps: [],
|
||||||
|
durationMs: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(runGatewayUpdate).mockResolvedValue(mockResult);
|
||||||
|
vi.mocked(runDaemonInstall).mockRejectedValueOnce(new Error("refresh failed"));
|
||||||
|
prepareRestartScript.mockResolvedValue(null);
|
||||||
|
serviceLoaded.mockResolvedValue(true);
|
||||||
|
vi.mocked(runDaemonRestart).mockResolvedValue(true);
|
||||||
|
|
||||||
|
await updateCommand({});
|
||||||
|
|
||||||
|
expect(runDaemonInstall).toHaveBeenCalledWith({
|
||||||
|
force: true,
|
||||||
|
json: undefined,
|
||||||
|
});
|
||||||
|
expect(runDaemonRestart).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateCommand does not refresh service env when --no-restart is set", async () => {
|
||||||
|
vi.mocked(runGatewayUpdate).mockResolvedValue(makeOkUpdateResult());
|
||||||
|
serviceLoaded.mockResolvedValue(true);
|
||||||
|
|
||||||
|
await updateCommand({ restart: false });
|
||||||
|
|
||||||
|
expect(runDaemonInstall).not.toHaveBeenCalled();
|
||||||
|
expect(runRestartScript).not.toHaveBeenCalled();
|
||||||
|
expect(runDaemonRestart).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("updateCommand continues after doctor sub-step and clears update flag", async () => {
|
it("updateCommand continues after doctor sub-step and clears update flag", async () => {
|
||||||
const envSnapshot = captureEnv(["OPENCLAW_UPDATE_IN_PROGRESS"]);
|
const envSnapshot = captureEnv(["OPENCLAW_UPDATE_IN_PROGRESS"]);
|
||||||
const randomSpy = vi.spyOn(Math, "random").mockReturnValue(0);
|
const randomSpy = vi.spyOn(Math, "random").mockReturnValue(0);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import { pathExists } from "../../utils.js";
|
|||||||
import { replaceCliName, resolveCliName } from "../cli-name.js";
|
import { replaceCliName, resolveCliName } from "../cli-name.js";
|
||||||
import { formatCliCommand } from "../command-format.js";
|
import { formatCliCommand } from "../command-format.js";
|
||||||
import { installCompletion } from "../completion-cli.js";
|
import { installCompletion } from "../completion-cli.js";
|
||||||
import { runDaemonRestart } from "../daemon-cli.js";
|
import { runDaemonInstall, runDaemonRestart } from "../daemon-cli.js";
|
||||||
import { createUpdateProgress, printResult } from "./progress.js";
|
import { createUpdateProgress, printResult } from "./progress.js";
|
||||||
import { prepareRestartScript, runRestartScript } from "./restart-helper.js";
|
import { prepareRestartScript, runRestartScript } from "./restart-helper.js";
|
||||||
import {
|
import {
|
||||||
@@ -391,6 +391,7 @@ async function maybeRestartService(params: {
|
|||||||
shouldRestart: boolean;
|
shouldRestart: boolean;
|
||||||
result: UpdateRunResult;
|
result: UpdateRunResult;
|
||||||
opts: UpdateCommandOptions;
|
opts: UpdateCommandOptions;
|
||||||
|
refreshServiceEnv: boolean;
|
||||||
restartScriptPath?: string | null;
|
restartScriptPath?: string | null;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
if (params.shouldRestart) {
|
if (params.shouldRestart) {
|
||||||
@@ -402,11 +403,29 @@ async function maybeRestartService(params: {
|
|||||||
try {
|
try {
|
||||||
let restarted = false;
|
let restarted = false;
|
||||||
let restartInitiated = false;
|
let restartInitiated = false;
|
||||||
if (params.restartScriptPath) {
|
let serviceRefreshed = false;
|
||||||
|
if (params.refreshServiceEnv) {
|
||||||
|
try {
|
||||||
|
await runDaemonInstall({ force: true, json: params.opts.json });
|
||||||
|
serviceRefreshed = true;
|
||||||
|
restarted = true;
|
||||||
|
} catch (err) {
|
||||||
|
if (!params.opts.json) {
|
||||||
|
defaultRuntime.log(
|
||||||
|
theme.warn(
|
||||||
|
`Failed to refresh gateway service environment from updated install: ${String(err)}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!serviceRefreshed && params.restartScriptPath) {
|
||||||
await runRestartScript(params.restartScriptPath);
|
await runRestartScript(params.restartScriptPath);
|
||||||
restartInitiated = true;
|
restartInitiated = true;
|
||||||
} else {
|
} else {
|
||||||
restarted = await runDaemonRestart();
|
if (!serviceRefreshed) {
|
||||||
|
restarted = await runDaemonRestart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.opts.json && restarted) {
|
if (!params.opts.json && restarted) {
|
||||||
@@ -586,11 +605,13 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
|||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
|
|
||||||
let restartScriptPath: string | null = null;
|
let restartScriptPath: string | null = null;
|
||||||
|
let refreshGatewayServiceEnv = false;
|
||||||
if (shouldRestart) {
|
if (shouldRestart) {
|
||||||
try {
|
try {
|
||||||
const loaded = await resolveGatewayService().isLoaded({ env: process.env });
|
const loaded = await resolveGatewayService().isLoaded({ env: process.env });
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
restartScriptPath = await prepareRestartScript(process.env);
|
restartScriptPath = await prepareRestartScript(process.env);
|
||||||
|
refreshGatewayServiceEnv = true;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore errors during pre-check; fallback to standard restart
|
// Ignore errors during pre-check; fallback to standard restart
|
||||||
@@ -669,6 +690,7 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
|||||||
shouldRestart,
|
shouldRestart,
|
||||||
result,
|
result,
|
||||||
opts,
|
opts,
|
||||||
|
refreshServiceEnv: refreshGatewayServiceEnv,
|
||||||
restartScriptPath,
|
restartScriptPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user