mirror of https://github.com/openclaw/openclaw.git
feat: surface backup guidance for destructive flows
This commit is contained in:
parent
dca7085058
commit
941ed14385
|
|
@ -0,0 +1,69 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createNonExitingRuntime } from "../runtime.js";
|
||||
|
||||
const resolveCleanupPlanFromDisk = vi.fn();
|
||||
const removePath = vi.fn();
|
||||
const listAgentSessionDirs = vi.fn();
|
||||
const removeStateAndLinkedPaths = vi.fn();
|
||||
const removeWorkspaceDirs = vi.fn();
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
isNixMode: false,
|
||||
}));
|
||||
|
||||
vi.mock("./cleanup-plan.js", () => ({
|
||||
resolveCleanupPlanFromDisk,
|
||||
}));
|
||||
|
||||
vi.mock("./cleanup-utils.js", () => ({
|
||||
removePath,
|
||||
listAgentSessionDirs,
|
||||
removeStateAndLinkedPaths,
|
||||
removeWorkspaceDirs,
|
||||
}));
|
||||
|
||||
const { resetCommand } = await import("./reset.js");
|
||||
|
||||
describe("resetCommand", () => {
|
||||
const runtime = createNonExitingRuntime();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
resolveCleanupPlanFromDisk.mockReturnValue({
|
||||
stateDir: "/tmp/.openclaw",
|
||||
configPath: "/tmp/.openclaw/openclaw.json",
|
||||
oauthDir: "/tmp/.openclaw/credentials",
|
||||
configInsideState: true,
|
||||
oauthInsideState: true,
|
||||
workspaceDirs: ["/tmp/.openclaw/workspace"],
|
||||
});
|
||||
removePath.mockResolvedValue({ ok: true });
|
||||
listAgentSessionDirs.mockResolvedValue(["/tmp/.openclaw/agents/main/sessions"]);
|
||||
removeStateAndLinkedPaths.mockResolvedValue(undefined);
|
||||
removeWorkspaceDirs.mockResolvedValue(undefined);
|
||||
vi.spyOn(runtime, "log").mockImplementation(() => {});
|
||||
vi.spyOn(runtime, "error").mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it("recommends creating a backup before state-destructive reset scopes", async () => {
|
||||
await resetCommand(runtime, {
|
||||
scope: "config+creds+sessions",
|
||||
yes: true,
|
||||
nonInteractive: true,
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(runtime.log).toHaveBeenCalledWith(expect.stringContaining("openclaw backup create"));
|
||||
});
|
||||
|
||||
it("does not recommend backup for config-only reset", async () => {
|
||||
await resetCommand(runtime, {
|
||||
scope: "config",
|
||||
yes: true,
|
||||
nonInteractive: true,
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(runtime.log).not.toHaveBeenCalledWith(expect.stringContaining("openclaw backup create"));
|
||||
});
|
||||
});
|
||||
|
|
@ -44,6 +44,10 @@ async function stopGatewayIfRunning(runtime: RuntimeEnv) {
|
|||
}
|
||||
}
|
||||
|
||||
function logBackupRecommendation(runtime: RuntimeEnv) {
|
||||
runtime.log(`Recommended first: ${formatCliCommand("openclaw backup create")}`);
|
||||
}
|
||||
|
||||
export async function resetCommand(runtime: RuntimeEnv, opts: ResetOptions) {
|
||||
const interactive = !opts.nonInteractive;
|
||||
if (!interactive && !opts.yes) {
|
||||
|
|
@ -110,6 +114,7 @@ export async function resetCommand(runtime: RuntimeEnv, opts: ResetOptions) {
|
|||
resolveCleanupPlanFromDisk();
|
||||
|
||||
if (scope !== "config") {
|
||||
logBackupRecommendation(runtime);
|
||||
if (dryRun) {
|
||||
runtime.log("[dry-run] stop gateway service");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createNonExitingRuntime } from "../runtime.js";
|
||||
|
||||
const resolveCleanupPlanFromDisk = vi.fn();
|
||||
const removePath = vi.fn();
|
||||
const removeStateAndLinkedPaths = vi.fn();
|
||||
const removeWorkspaceDirs = vi.fn();
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
isNixMode: false,
|
||||
}));
|
||||
|
||||
vi.mock("./cleanup-plan.js", () => ({
|
||||
resolveCleanupPlanFromDisk,
|
||||
}));
|
||||
|
||||
vi.mock("./cleanup-utils.js", () => ({
|
||||
removePath,
|
||||
removeStateAndLinkedPaths,
|
||||
removeWorkspaceDirs,
|
||||
}));
|
||||
|
||||
const { uninstallCommand } = await import("./uninstall.js");
|
||||
|
||||
describe("uninstallCommand", () => {
|
||||
const runtime = createNonExitingRuntime();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
resolveCleanupPlanFromDisk.mockReturnValue({
|
||||
stateDir: "/tmp/.openclaw",
|
||||
configPath: "/tmp/.openclaw/openclaw.json",
|
||||
oauthDir: "/tmp/.openclaw/credentials",
|
||||
configInsideState: true,
|
||||
oauthInsideState: true,
|
||||
workspaceDirs: ["/tmp/.openclaw/workspace"],
|
||||
});
|
||||
removePath.mockResolvedValue({ ok: true });
|
||||
removeStateAndLinkedPaths.mockResolvedValue(undefined);
|
||||
removeWorkspaceDirs.mockResolvedValue(undefined);
|
||||
vi.spyOn(runtime, "log").mockImplementation(() => {});
|
||||
vi.spyOn(runtime, "error").mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it("recommends creating a backup before removing state or workspaces", async () => {
|
||||
await uninstallCommand(runtime, {
|
||||
state: true,
|
||||
yes: true,
|
||||
nonInteractive: true,
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(runtime.log).toHaveBeenCalledWith(expect.stringContaining("openclaw backup create"));
|
||||
});
|
||||
|
||||
it("does not recommend backup for service-only uninstall", async () => {
|
||||
await uninstallCommand(runtime, {
|
||||
service: true,
|
||||
yes: true,
|
||||
nonInteractive: true,
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(runtime.log).not.toHaveBeenCalledWith(expect.stringContaining("openclaw backup create"));
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import path from "node:path";
|
||||
import { cancel, confirm, isCancel, multiselect } from "@clack/prompts";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { isNixMode } from "../config/config.js";
|
||||
import { resolveGatewayService } from "../daemon/service.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
|
|
@ -92,6 +93,10 @@ async function removeMacApp(runtime: RuntimeEnv, dryRun?: boolean) {
|
|||
});
|
||||
}
|
||||
|
||||
function logBackupRecommendation(runtime: RuntimeEnv) {
|
||||
runtime.log(`Recommended first: ${formatCliCommand("openclaw backup create")}`);
|
||||
}
|
||||
|
||||
export async function uninstallCommand(runtime: RuntimeEnv, opts: UninstallOptions) {
|
||||
const { scopes, hadExplicit } = buildScopeSelection(opts);
|
||||
const interactive = !opts.nonInteractive;
|
||||
|
|
@ -155,6 +160,10 @@ export async function uninstallCommand(runtime: RuntimeEnv, opts: UninstallOptio
|
|||
const { stateDir, configPath, oauthDir, configInsideState, oauthInsideState, workspaceDirs } =
|
||||
resolveCleanupPlanFromDisk();
|
||||
|
||||
if (scopes.has("state") || scopes.has("workspace")) {
|
||||
logBackupRecommendation(runtime);
|
||||
}
|
||||
|
||||
if (scopes.has("service")) {
|
||||
if (dryRun) {
|
||||
runtime.log("[dry-run] remove gateway service");
|
||||
|
|
|
|||
Loading…
Reference in New Issue