mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 23:24:30 +00:00
refactor(models): reuse validated config snapshot loader
This commit is contained in:
@@ -10,7 +10,6 @@ import { normalizeProviderId } from "../../agents/model-selection.js";
|
|||||||
import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js";
|
import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js";
|
||||||
import { formatCliCommand } from "../../cli/command-format.js";
|
import { formatCliCommand } from "../../cli/command-format.js";
|
||||||
import { parseDurationMs } from "../../cli/parse-duration.js";
|
import { parseDurationMs } from "../../cli/parse-duration.js";
|
||||||
import { readConfigFileSnapshot } from "../../config/config.js";
|
|
||||||
import { logConfigUpdated } from "../../config/logging.js";
|
import { logConfigUpdated } from "../../config/logging.js";
|
||||||
import { resolvePluginProviders } from "../../plugins/providers.js";
|
import { resolvePluginProviders } from "../../plugins/providers.js";
|
||||||
import type { ProviderAuthResult, ProviderPlugin } from "../../plugins/types.js";
|
import type { ProviderAuthResult, ProviderPlugin } from "../../plugins/types.js";
|
||||||
@@ -28,7 +27,7 @@ import {
|
|||||||
pickAuthMethod,
|
pickAuthMethod,
|
||||||
resolveProviderMatch,
|
resolveProviderMatch,
|
||||||
} from "../provider-auth-helpers.js";
|
} from "../provider-auth-helpers.js";
|
||||||
import { updateConfig } from "./shared.js";
|
import { loadValidConfigOrThrow, updateConfig } from "./shared.js";
|
||||||
|
|
||||||
const confirm = (params: Parameters<typeof clackConfirm>[0]) =>
|
const confirm = (params: Parameters<typeof clackConfirm>[0]) =>
|
||||||
clackConfirm({
|
clackConfirm({
|
||||||
@@ -278,13 +277,7 @@ export async function modelsAuthLoginCommand(opts: LoginOptions, runtime: Runtim
|
|||||||
throw new Error("models auth login requires an interactive TTY.");
|
throw new Error("models auth login requires an interactive TTY.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const snapshot = await readConfigFileSnapshot();
|
const config = await loadValidConfigOrThrow();
|
||||||
if (!snapshot.valid) {
|
|
||||||
const issues = snapshot.issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n");
|
|
||||||
throw new Error(`Invalid config at ${snapshot.path}\n${issues}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = snapshot.config;
|
|
||||||
const defaultAgentId = resolveDefaultAgentId(config);
|
const defaultAgentId = resolveDefaultAgentId(config);
|
||||||
const agentDir = resolveAgentDir(config, defaultAgentId);
|
const agentDir = resolveAgentDir(config, defaultAgentId);
|
||||||
const workspaceDir =
|
const workspaceDir =
|
||||||
|
|||||||
63
src/commands/models/shared.test.ts
Normal file
63
src/commands/models/shared.test.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import type { OpenClawConfig } from "../../config/config.js";
|
||||||
|
|
||||||
|
const mocks = vi.hoisted(() => ({
|
||||||
|
readConfigFileSnapshot: vi.fn(),
|
||||||
|
writeConfigFile: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../config/config.js", () => ({
|
||||||
|
readConfigFileSnapshot: (...args: unknown[]) => mocks.readConfigFileSnapshot(...args),
|
||||||
|
writeConfigFile: (...args: unknown[]) => mocks.writeConfigFile(...args),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { loadValidConfigOrThrow, updateConfig } from "./shared.js";
|
||||||
|
|
||||||
|
describe("models/shared", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.readConfigFileSnapshot.mockReset();
|
||||||
|
mocks.writeConfigFile.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns config when snapshot is valid", async () => {
|
||||||
|
const cfg = { providers: {} } as unknown as OpenClawConfig;
|
||||||
|
mocks.readConfigFileSnapshot.mockResolvedValue({
|
||||||
|
valid: true,
|
||||||
|
config: cfg,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(loadValidConfigOrThrow()).resolves.toBe(cfg);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws formatted issues when snapshot is invalid", async () => {
|
||||||
|
mocks.readConfigFileSnapshot.mockResolvedValue({
|
||||||
|
valid: false,
|
||||||
|
path: "/tmp/openclaw.json",
|
||||||
|
issues: [{ path: "providers.openai.apiKey", message: "Required" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(loadValidConfigOrThrow()).rejects.toThrowError(
|
||||||
|
"Invalid config at /tmp/openclaw.json\n- providers.openai.apiKey: Required",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateConfig writes mutated config", async () => {
|
||||||
|
const cfg = { update: { channel: "stable" } } as unknown as OpenClawConfig;
|
||||||
|
mocks.readConfigFileSnapshot.mockResolvedValue({
|
||||||
|
valid: true,
|
||||||
|
config: cfg,
|
||||||
|
});
|
||||||
|
mocks.writeConfigFile.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await updateConfig((current) => ({
|
||||||
|
...current,
|
||||||
|
update: { channel: "beta" },
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(mocks.writeConfigFile).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
update: { channel: "beta" },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -59,15 +59,20 @@ export const isLocalBaseUrl = (baseUrl: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function updateConfig(
|
export async function loadValidConfigOrThrow(): Promise<OpenClawConfig> {
|
||||||
mutator: (cfg: OpenClawConfig) => OpenClawConfig,
|
|
||||||
): Promise<OpenClawConfig> {
|
|
||||||
const snapshot = await readConfigFileSnapshot();
|
const snapshot = await readConfigFileSnapshot();
|
||||||
if (!snapshot.valid) {
|
if (!snapshot.valid) {
|
||||||
const issues = snapshot.issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n");
|
const issues = snapshot.issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n");
|
||||||
throw new Error(`Invalid config at ${snapshot.path}\n${issues}`);
|
throw new Error(`Invalid config at ${snapshot.path}\n${issues}`);
|
||||||
}
|
}
|
||||||
const next = mutator(snapshot.config);
|
return snapshot.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateConfig(
|
||||||
|
mutator: (cfg: OpenClawConfig) => OpenClawConfig,
|
||||||
|
): Promise<OpenClawConfig> {
|
||||||
|
const config = await loadValidConfigOrThrow();
|
||||||
|
const next = mutator(config);
|
||||||
await writeConfigFile(next);
|
await writeConfigFile(next);
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user