mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-29 08:13:34 +00:00
fix(cli): harden official plugin recovery (#93325)
* fix(cli): harden official plugin recovery * fix(config): preserve include write context * fix(config): reject external include mutations * fix(config): bind snapshots to config paths * fix(config): preserve write ownership * fix(cli): preflight plugin config mutations * chore(plugin-sdk): refresh api baseline * test(config): prove install env policy mutations * fix(cli): preflight plugin updates * fix(cli): preflight non-npm id migrations * chore(plugin-sdk): refresh api baseline * fix(cli): satisfy plugin recovery checks
This commit is contained in:
@@ -21,6 +21,10 @@ type ListMarketplacePluginsFn =
|
||||
(typeof import("../plugins/marketplace.js"))["listMarketplacePlugins"];
|
||||
type ResolveMarketplaceInstallShortcutFn =
|
||||
(typeof import("../plugins/marketplace.js"))["resolveMarketplaceInstallShortcut"];
|
||||
type UpdateNpmInstalledPluginsFn =
|
||||
(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"];
|
||||
type UpdateNpmInstalledHookPacksFn =
|
||||
(typeof import("../hooks/update.js"))["updateNpmInstalledHookPacks"];
|
||||
type PluginInstallRecordMap = Record<string, PluginInstallRecord>;
|
||||
|
||||
let mockInstalledPluginIndexInstallRecords: PluginInstallRecordMap = {};
|
||||
@@ -37,6 +41,7 @@ function invokeMock<TArgs extends unknown[], TResult>(mock: unknown, ...args: TA
|
||||
|
||||
export const loadConfig: Mock<LoadConfigFn> = vi.fn<LoadConfigFn>(() => ({}) as OpenClawConfig);
|
||||
export const readConfigFileSnapshot: AsyncUnknownMock = vi.fn();
|
||||
export const readConfigFileSnapshotForWrite: AsyncUnknownMock = vi.fn();
|
||||
export const writeConfigFile: AsyncUnknownMock = vi.fn(async () => undefined);
|
||||
export const replaceConfigFile: AsyncUnknownMock = vi.fn(
|
||||
async (params: { nextConfig: OpenClawConfig }) => await writeConfigFile(params.nextConfig),
|
||||
@@ -73,8 +78,8 @@ export const applyExclusiveSlotSelection: UnknownMock = vi.fn();
|
||||
export const planPluginUninstall: UnknownMock = vi.fn();
|
||||
export const applyPluginUninstallDirectoryRemoval: AsyncUnknownMock = vi.fn();
|
||||
const uninstallPlugin: AsyncUnknownMock = vi.fn();
|
||||
export const updateNpmInstalledPlugins: AsyncUnknownMock = vi.fn();
|
||||
export const updateNpmInstalledHookPacks: AsyncUnknownMock = vi.fn();
|
||||
export const updateNpmInstalledPlugins: Mock<UpdateNpmInstalledPluginsFn> = vi.fn();
|
||||
export const updateNpmInstalledHookPacks: Mock<UpdateNpmInstalledHookPacksFn> = vi.fn();
|
||||
export const promptYesNo: AsyncUnknownMock = vi.fn();
|
||||
export class PromptInputClosedError extends Error {
|
||||
constructor() {
|
||||
@@ -191,6 +196,16 @@ vi.mock("../config/config.js", () => ({
|
||||
readConfigFileSnapshot,
|
||||
...args,
|
||||
)) as (typeof import("../config/config.js"))["readConfigFileSnapshot"],
|
||||
readConfigFileSnapshotForWrite: ((
|
||||
...args: Parameters<(typeof import("../config/config.js"))["readConfigFileSnapshotForWrite"]>
|
||||
) =>
|
||||
invokeMock<
|
||||
Parameters<(typeof import("../config/config.js"))["readConfigFileSnapshotForWrite"]>,
|
||||
ReturnType<(typeof import("../config/config.js"))["readConfigFileSnapshotForWrite"]>
|
||||
>(
|
||||
readConfigFileSnapshotForWrite,
|
||||
...args,
|
||||
)) as (typeof import("../config/config.js"))["readConfigFileSnapshotForWrite"],
|
||||
writeConfigFile: ((config: OpenClawConfig) =>
|
||||
invokeMock<
|
||||
[OpenClawConfig],
|
||||
@@ -481,18 +496,22 @@ vi.mock("../plugins/uninstall.js", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../plugins/update.js", () => ({
|
||||
updateNpmInstalledPlugins: ((
|
||||
...args: Parameters<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]>
|
||||
) =>
|
||||
invokeMock<
|
||||
Parameters<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]>,
|
||||
ReturnType<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]>
|
||||
>(
|
||||
updateNpmInstalledPlugins,
|
||||
...args,
|
||||
)) as (typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"],
|
||||
}));
|
||||
vi.mock("../plugins/update.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../plugins/update.js")>();
|
||||
return {
|
||||
...actual,
|
||||
updateNpmInstalledPlugins: ((
|
||||
...args: Parameters<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]>
|
||||
) =>
|
||||
invokeMock<
|
||||
Parameters<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]>,
|
||||
ReturnType<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]>
|
||||
>(
|
||||
updateNpmInstalledPlugins,
|
||||
...args,
|
||||
)) as (typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"],
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../hooks/update.js", () => ({
|
||||
updateNpmInstalledHookPacks: ((
|
||||
@@ -679,6 +698,7 @@ export function resetPluginsCliTestState() {
|
||||
restoreRuntimeCaptureMocks();
|
||||
loadConfig.mockReset();
|
||||
readConfigFileSnapshot.mockReset();
|
||||
readConfigFileSnapshotForWrite.mockReset();
|
||||
writeConfigFile.mockReset();
|
||||
replaceConfigFile.mockReset();
|
||||
resolveStateDir.mockReset();
|
||||
@@ -737,6 +757,17 @@ export function resetPluginsCliTestState() {
|
||||
legacyIssues: [],
|
||||
};
|
||||
});
|
||||
readConfigFileSnapshotForWrite.mockImplementation(async () => {
|
||||
const snapshot = (await readConfigFileSnapshot()) as { path: string };
|
||||
return {
|
||||
snapshot,
|
||||
writeOptions: {
|
||||
assertConfigPathForWrite: () => {},
|
||||
expectedConfigPath: snapshot.path,
|
||||
ownedConfigPathForWrite: snapshot.path,
|
||||
},
|
||||
};
|
||||
});
|
||||
writeConfigFile.mockResolvedValue(undefined);
|
||||
replaceConfigFile.mockImplementation(
|
||||
(async (params: { nextConfig: OpenClawConfig }) =>
|
||||
|
||||
Reference in New Issue
Block a user