Skip to content

Commit 418791a

Browse files
authored
Fix: custom modes export import (#2810)
* Enhance export method in ContextProxy to filter out project custom modes, ensuring only global settings are included in the export. * Fix issue of customModes not being imported when importing settings. The problem was that the Custom modes are managed by the CustomModesManager, not by the context proxy. * * Fix tests * Update webviewMessageHandler to provide customModeManager to importSettings
1 parent 42c1f5f commit 418791a

File tree

4 files changed

+76
-4
lines changed

4 files changed

+76
-4
lines changed

src/core/config/ContextProxy.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ export class ContextProxy {
230230
public async export(): Promise<GlobalSettings | undefined> {
231231
try {
232232
const globalSettings = globalSettingsExportSchema.parse(this.getValues())
233+
234+
// Exports should only contain global settings, so this skips project custom modes (those exist in the .roomode folder)
235+
globalSettings.customModes = globalSettings.customModes?.filter((mode) => mode.source === "global")
236+
233237
return Object.fromEntries(Object.entries(globalSettings).filter(([_, value]) => value !== undefined))
234238
} catch (error) {
235239
if (error instanceof ZodError) {

src/core/config/__tests__/importExport.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ProviderName } from "../../../schemas"
99
import { importSettings, exportSettings } from "../importExport"
1010
import { ProviderSettingsManager } from "../ProviderSettingsManager"
1111
import { ContextProxy } from "../ContextProxy"
12+
import { CustomModesManager } from "../CustomModesManager"
1213

1314
// Mock VSCode modules
1415
jest.mock("vscode", () => ({
@@ -37,6 +38,7 @@ describe("importExport", () => {
3738
let mockProviderSettingsManager: jest.Mocked<ProviderSettingsManager>
3839
let mockContextProxy: jest.Mocked<ContextProxy>
3940
let mockExtensionContext: jest.Mocked<vscode.ExtensionContext>
41+
let mockCustomModesManager: jest.Mocked<CustomModesManager>
4042

4143
beforeEach(() => {
4244
// Reset all mocks
@@ -56,6 +58,11 @@ describe("importExport", () => {
5658
export: jest.fn().mockImplementation(() => Promise.resolve({})),
5759
} as unknown as jest.Mocked<ContextProxy>
5860

61+
// Setup customModesManager mock
62+
mockCustomModesManager = {
63+
updateCustomMode: jest.fn(),
64+
} as unknown as jest.Mocked<CustomModesManager>
65+
5966
const map = new Map<string, string>()
6067

6168
mockExtensionContext = {
@@ -74,6 +81,7 @@ describe("importExport", () => {
7481
const result = await importSettings({
7582
providerSettingsManager: mockProviderSettingsManager,
7683
contextProxy: mockContextProxy,
84+
customModesManager: mockCustomModesManager,
7785
})
7886

7987
expect(result).toEqual({ success: false })
@@ -138,6 +146,7 @@ describe("importExport", () => {
138146
const result = await importSettings({
139147
providerSettingsManager: mockProviderSettingsManager,
140148
contextProxy: mockContextProxy,
149+
customModesManager: mockCustomModesManager,
141150
})
142151

143152
expect(result.success).toBe(true)
@@ -181,6 +190,7 @@ describe("importExport", () => {
181190
const result = await importSettings({
182191
providerSettingsManager: mockProviderSettingsManager,
183192
contextProxy: mockContextProxy,
193+
customModesManager: mockCustomModesManager,
184194
})
185195

186196
expect(result).toEqual({ success: false })
@@ -202,6 +212,7 @@ describe("importExport", () => {
202212
const result = await importSettings({
203213
providerSettingsManager: mockProviderSettingsManager,
204214
contextProxy: mockContextProxy,
215+
customModesManager: mockCustomModesManager,
205216
})
206217

207218
expect(result).toEqual({ success: false })
@@ -220,6 +231,7 @@ describe("importExport", () => {
220231
const result = await importSettings({
221232
providerSettingsManager: mockProviderSettingsManager,
222233
contextProxy: mockContextProxy,
234+
customModesManager: mockCustomModesManager,
223235
})
224236

225237
expect(result).toEqual({ success: false })
@@ -252,6 +264,7 @@ describe("importExport", () => {
252264
const result = await importSettings({
253265
providerSettingsManager,
254266
contextProxy: mockContextProxy,
267+
customModesManager: mockCustomModesManager,
255268
})
256269

257270
expect(result.success).toBe(true)
@@ -261,6 +274,50 @@ describe("importExport", () => {
261274
})
262275
})
263276

277+
it("should call updateCustomMode for each custom mode in config", async () => {
278+
;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }])
279+
const customModes = [
280+
{
281+
slug: "mode1",
282+
name: "Mode One",
283+
roleDefinition: "Custom role one",
284+
groups: [],
285+
},
286+
{
287+
slug: "mode2",
288+
name: "Mode Two",
289+
roleDefinition: "Custom role two",
290+
groups: [],
291+
},
292+
]
293+
const mockFileContent = JSON.stringify({
294+
providerProfiles: {
295+
currentApiConfigName: "test",
296+
apiConfigs: {},
297+
},
298+
globalSettings: {
299+
mode: "code",
300+
customModes,
301+
},
302+
})
303+
;(fs.readFile as jest.Mock).mockResolvedValue(mockFileContent)
304+
mockProviderSettingsManager.export.mockResolvedValue({
305+
currentApiConfigName: "test",
306+
apiConfigs: {},
307+
})
308+
mockProviderSettingsManager.listConfig.mockResolvedValue([])
309+
const result = await importSettings({
310+
providerSettingsManager: mockProviderSettingsManager,
311+
contextProxy: mockContextProxy,
312+
customModesManager: mockCustomModesManager,
313+
})
314+
expect(result.success).toBe(true)
315+
expect(mockCustomModesManager.updateCustomMode).toHaveBeenCalledTimes(customModes.length)
316+
customModes.forEach((mode) => {
317+
expect(mockCustomModesManager.updateCustomMode).toHaveBeenCalledWith(mode.slug, mode)
318+
})
319+
})
320+
264321
describe("exportSettings", () => {
265322
it("should not export settings when user cancels file selection", async () => {
266323
// Mock user canceling file selection

src/core/config/importExport.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,20 @@ import { z } from "zod"
88
import { globalSettingsSchema } from "../../schemas"
99
import { ProviderSettingsManager, providerProfilesSchema } from "./ProviderSettingsManager"
1010
import { ContextProxy } from "./ContextProxy"
11+
import { CustomModesManager } from "./CustomModesManager"
1112

12-
type ImportExportOptions = {
13+
type ImportOptions = {
1314
providerSettingsManager: ProviderSettingsManager
1415
contextProxy: ContextProxy
16+
customModesManager: CustomModesManager
1517
}
1618

17-
export const importSettings = async ({ providerSettingsManager, contextProxy }: ImportExportOptions) => {
19+
type ExportOptions = {
20+
providerSettingsManager: ProviderSettingsManager
21+
contextProxy: ContextProxy
22+
}
23+
24+
export const importSettings = async ({ providerSettingsManager, contextProxy, customModesManager }: ImportOptions) => {
1825
const uris = await vscode.window.showOpenDialog({
1926
filters: { JSON: ["json"] },
2027
canSelectMany: false,
@@ -31,7 +38,6 @@ export const importSettings = async ({ providerSettingsManager, contextProxy }:
3138

3239
try {
3340
const previousProviderProfiles = await providerSettingsManager.export()
34-
3541
const { providerProfiles: newProviderProfiles, globalSettings } = schema.parse(
3642
JSON.parse(await fs.readFile(uris[0].fsPath, "utf-8")),
3743
)
@@ -48,6 +54,10 @@ export const importSettings = async ({ providerSettingsManager, contextProxy }:
4854
},
4955
}
5056

57+
await Promise.all(
58+
(globalSettings.customModes ?? []).map((mode) => customModesManager.updateCustomMode(mode.slug, mode)),
59+
)
60+
5161
await providerSettingsManager.import(newProviderProfiles)
5262

5363
await contextProxy.setValues(globalSettings)
@@ -60,7 +70,7 @@ export const importSettings = async ({ providerSettingsManager, contextProxy }:
6070
}
6171
}
6272

63-
export const exportSettings = async ({ providerSettingsManager, contextProxy }: ImportExportOptions) => {
73+
export const exportSettings = async ({ providerSettingsManager, contextProxy }: ExportOptions) => {
6474
const uri = await vscode.window.showSaveDialog({
6575
filters: { JSON: ["json"] },
6676
defaultUri: vscode.Uri.file(path.join(os.homedir(), "Documents", "roo-code-settings.json")),

src/core/webview/webviewMessageHandler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
262262
const { success } = await importSettings({
263263
providerSettingsManager: provider.providerSettingsManager,
264264
contextProxy: provider.contextProxy,
265+
customModesManager: provider.customModesManager,
265266
})
266267

267268
if (success) {

0 commit comments

Comments
 (0)