Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/core/config/ContextProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ export class ContextProxy {
public async export(): Promise<GlobalSettings | undefined> {
try {
const globalSettings = globalSettingsExportSchema.parse(this.getValues())

// Exports should only contain global settings, so this skips project custom modes (those exist in the .roomode folder)
globalSettings.customModes = globalSettings.customModes?.filter((mode) => mode.source === "global")

return Object.fromEntries(Object.entries(globalSettings).filter(([_, value]) => value !== undefined))
} catch (error) {
if (error instanceof ZodError) {
Expand Down
57 changes: 57 additions & 0 deletions src/core/config/__tests__/importExport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ProviderName } from "../../../schemas"
import { importSettings, exportSettings } from "../importExport"
import { ProviderSettingsManager } from "../ProviderSettingsManager"
import { ContextProxy } from "../ContextProxy"
import { CustomModesManager } from "../CustomModesManager"

// Mock VSCode modules
jest.mock("vscode", () => ({
Expand Down Expand Up @@ -37,6 +38,7 @@ describe("importExport", () => {
let mockProviderSettingsManager: jest.Mocked<ProviderSettingsManager>
let mockContextProxy: jest.Mocked<ContextProxy>
let mockExtensionContext: jest.Mocked<vscode.ExtensionContext>
let mockCustomModesManager: jest.Mocked<CustomModesManager>

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

// Setup customModesManager mock
mockCustomModesManager = {
updateCustomMode: jest.fn(),
} as unknown as jest.Mocked<CustomModesManager>

const map = new Map<string, string>()

mockExtensionContext = {
Expand All @@ -74,6 +81,7 @@ describe("importExport", () => {
const result = await importSettings({
providerSettingsManager: mockProviderSettingsManager,
contextProxy: mockContextProxy,
customModesManager: mockCustomModesManager,
})

expect(result).toEqual({ success: false })
Expand Down Expand Up @@ -138,6 +146,7 @@ describe("importExport", () => {
const result = await importSettings({
providerSettingsManager: mockProviderSettingsManager,
contextProxy: mockContextProxy,
customModesManager: mockCustomModesManager,
})

expect(result.success).toBe(true)
Expand Down Expand Up @@ -181,6 +190,7 @@ describe("importExport", () => {
const result = await importSettings({
providerSettingsManager: mockProviderSettingsManager,
contextProxy: mockContextProxy,
customModesManager: mockCustomModesManager,
})

expect(result).toEqual({ success: false })
Expand All @@ -202,6 +212,7 @@ describe("importExport", () => {
const result = await importSettings({
providerSettingsManager: mockProviderSettingsManager,
contextProxy: mockContextProxy,
customModesManager: mockCustomModesManager,
})

expect(result).toEqual({ success: false })
Expand All @@ -220,6 +231,7 @@ describe("importExport", () => {
const result = await importSettings({
providerSettingsManager: mockProviderSettingsManager,
contextProxy: mockContextProxy,
customModesManager: mockCustomModesManager,
})

expect(result).toEqual({ success: false })
Expand Down Expand Up @@ -252,6 +264,7 @@ describe("importExport", () => {
const result = await importSettings({
providerSettingsManager,
contextProxy: mockContextProxy,
customModesManager: mockCustomModesManager,
})

expect(result.success).toBe(true)
Expand All @@ -261,6 +274,50 @@ describe("importExport", () => {
})
})

it("should call updateCustomMode for each custom mode in config", async () => {
;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }])
const customModes = [
{
slug: "mode1",
name: "Mode One",
roleDefinition: "Custom role one",
groups: [],
},
{
slug: "mode2",
name: "Mode Two",
roleDefinition: "Custom role two",
groups: [],
},
]
const mockFileContent = JSON.stringify({
providerProfiles: {
currentApiConfigName: "test",
apiConfigs: {},
},
globalSettings: {
mode: "code",
customModes,
},
})
;(fs.readFile as jest.Mock).mockResolvedValue(mockFileContent)
mockProviderSettingsManager.export.mockResolvedValue({
currentApiConfigName: "test",
apiConfigs: {},
})
mockProviderSettingsManager.listConfig.mockResolvedValue([])
const result = await importSettings({
providerSettingsManager: mockProviderSettingsManager,
contextProxy: mockContextProxy,
customModesManager: mockCustomModesManager,
})
expect(result.success).toBe(true)
expect(mockCustomModesManager.updateCustomMode).toHaveBeenCalledTimes(customModes.length)
customModes.forEach((mode) => {
expect(mockCustomModesManager.updateCustomMode).toHaveBeenCalledWith(mode.slug, mode)
})
})

describe("exportSettings", () => {
it("should not export settings when user cancels file selection", async () => {
// Mock user canceling file selection
Expand Down
18 changes: 14 additions & 4 deletions src/core/config/importExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ import { z } from "zod"
import { globalSettingsSchema } from "../../schemas"
import { ProviderSettingsManager, providerProfilesSchema } from "./ProviderSettingsManager"
import { ContextProxy } from "./ContextProxy"
import { CustomModesManager } from "./CustomModesManager"

type ImportExportOptions = {
type ImportOptions = {
providerSettingsManager: ProviderSettingsManager
contextProxy: ContextProxy
customModesManager: CustomModesManager
}

export const importSettings = async ({ providerSettingsManager, contextProxy }: ImportExportOptions) => {
type ExportOptions = {
providerSettingsManager: ProviderSettingsManager
contextProxy: ContextProxy
}

export const importSettings = async ({ providerSettingsManager, contextProxy, customModesManager }: ImportOptions) => {
const uris = await vscode.window.showOpenDialog({
filters: { JSON: ["json"] },
canSelectMany: false,
Expand All @@ -31,7 +38,6 @@ export const importSettings = async ({ providerSettingsManager, contextProxy }:

try {
const previousProviderProfiles = await providerSettingsManager.export()

const { providerProfiles: newProviderProfiles, globalSettings } = schema.parse(
JSON.parse(await fs.readFile(uris[0].fsPath, "utf-8")),
)
Expand All @@ -48,6 +54,10 @@ export const importSettings = async ({ providerSettingsManager, contextProxy }:
},
}

await Promise.all(
(globalSettings.customModes ?? []).map((mode) => customModesManager.updateCustomMode(mode.slug, mode)),
)

await providerSettingsManager.import(newProviderProfiles)

await contextProxy.setValues(globalSettings)
Expand All @@ -60,7 +70,7 @@ export const importSettings = async ({ providerSettingsManager, contextProxy }:
}
}

export const exportSettings = async ({ providerSettingsManager, contextProxy }: ImportExportOptions) => {
export const exportSettings = async ({ providerSettingsManager, contextProxy }: ExportOptions) => {
const uri = await vscode.window.showSaveDialog({
filters: { JSON: ["json"] },
defaultUri: vscode.Uri.file(path.join(os.homedir(), "Documents", "roo-code-settings.json")),
Expand Down
1 change: 1 addition & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
const { success } = await importSettings({
providerSettingsManager: provider.providerSettingsManager,
contextProxy: provider.contextProxy,
customModesManager: provider.customModesManager,
})

if (success) {
Expand Down