Skip to content

Commit 23d1bbf

Browse files
authored
Preserve existing provider profiles when importing (#2052)
1 parent 48148e3 commit 23d1bbf

File tree

3 files changed

+64
-8
lines changed

3 files changed

+64
-8
lines changed

.changeset/honest-walls-search.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Preserve existing provider profiles when importing

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

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import fs from "fs/promises"
44
import * as path from "path"
5-
import os from "os"
65

76
import * as vscode from "vscode"
87

@@ -37,6 +36,7 @@ jest.mock("os", () => ({
3736
describe("importExport", () => {
3837
let mockProviderSettingsManager: jest.Mocked<ProviderSettingsManager>
3938
let mockContextProxy: jest.Mocked<ContextProxy>
39+
let mockExtensionContext: jest.Mocked<vscode.ExtensionContext>
4040

4141
beforeEach(() => {
4242
// Reset all mocks
@@ -55,6 +55,15 @@ describe("importExport", () => {
5555
setValue: jest.fn(),
5656
export: jest.fn().mockImplementation(() => Promise.resolve({})),
5757
} as unknown as jest.Mocked<ContextProxy>
58+
59+
const map = new Map<string, string>()
60+
61+
mockExtensionContext = {
62+
secrets: {
63+
get: jest.fn().mockImplementation((key: string) => map.get(key)),
64+
store: jest.fn().mockImplementation((key: string, value: string) => map.set(key, value)),
65+
},
66+
} as unknown as jest.Mocked<vscode.ExtensionContext>
5867
})
5968

6069
describe("importSettings", () => {
@@ -161,9 +170,7 @@ describe("importExport", () => {
161170

162171
// Invalid content (missing required fields)
163172
const mockInvalidContent = JSON.stringify({
164-
providerProfiles: {
165-
apiConfigs: {},
166-
},
173+
providerProfiles: { apiConfigs: {} },
167174
globalSettings: {},
168175
})
169176

@@ -219,6 +226,38 @@ describe("importExport", () => {
219226
expect(mockProviderSettingsManager.import).not.toHaveBeenCalled()
220227
expect(mockContextProxy.setValues).not.toHaveBeenCalled()
221228
})
229+
230+
it("should not clobber existing api configs", async () => {
231+
const providerSettingsManager = new ProviderSettingsManager(mockExtensionContext)
232+
await providerSettingsManager.saveConfig("openai", { apiProvider: "openai", id: "openai" })
233+
234+
const configs = await providerSettingsManager.listConfig()
235+
expect(configs[0].name).toBe("default")
236+
expect(configs[1].name).toBe("openai")
237+
;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }])
238+
239+
const mockFileContent = JSON.stringify({
240+
globalSettings: { mode: "code" },
241+
providerProfiles: {
242+
currentApiConfigName: "anthropic",
243+
apiConfigs: { default: { apiProvider: "anthropic" as const, id: "anthropic" } },
244+
},
245+
})
246+
247+
;(fs.readFile as jest.Mock).mockResolvedValue(mockFileContent)
248+
249+
mockContextProxy.export.mockResolvedValue({ mode: "code" })
250+
251+
const result = await importSettings({
252+
providerSettingsManager,
253+
contextProxy: mockContextProxy,
254+
})
255+
256+
expect(result.success).toBe(true)
257+
expect(result.providerProfiles?.apiConfigs["openai"]).toBeDefined()
258+
expect(result.providerProfiles?.apiConfigs["default"]).toBeDefined()
259+
expect(result.providerProfiles?.apiConfigs["default"].apiProvider).toBe("anthropic")
260+
})
222261
})
223262

224263
describe("exportSettings", () => {

src/core/config/importExport.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,27 @@ export const importSettings = async ({ providerSettingsManager, contextProxy }:
3030
})
3131

3232
try {
33-
const { providerProfiles, globalSettings } = schema.parse(
33+
const previousProviderProfiles = await providerSettingsManager.export()
34+
35+
const { providerProfiles: newProviderProfiles, globalSettings } = schema.parse(
3436
JSON.parse(await fs.readFile(uris[0].fsPath, "utf-8")),
3537
)
3638

37-
const previousProviderProfiles = await providerSettingsManager.export()
39+
const providerProfiles = {
40+
currentApiConfigName: newProviderProfiles.currentApiConfigName,
41+
apiConfigs: {
42+
...previousProviderProfiles.apiConfigs,
43+
...newProviderProfiles.apiConfigs,
44+
},
45+
modeApiConfigs: {
46+
...previousProviderProfiles.modeApiConfigs,
47+
...newProviderProfiles.modeApiConfigs,
48+
},
49+
}
3850

39-
await contextProxy.setValues(globalSettings)
40-
await providerSettingsManager.import({ ...previousProviderProfiles, ...providerProfiles })
51+
await providerSettingsManager.import(newProviderProfiles)
4152

53+
await contextProxy.setValues(globalSettings)
4254
contextProxy.setValue("currentApiConfigName", providerProfiles.currentApiConfigName)
4355
contextProxy.setValue("listApiConfigMeta", await providerSettingsManager.listConfig())
4456

0 commit comments

Comments
 (0)