Skip to content

Commit 4de98a6

Browse files
committed
Import settings bug fix / improvements
1 parent f70e03e commit 4de98a6

File tree

20 files changed

+115
-26
lines changed

20 files changed

+115
-26
lines changed

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,58 @@ describe("importExport", () => {
200200
expect(mockContextProxy.setValues).not.toHaveBeenCalled()
201201
})
202202

203+
it("should import settings successfully when globalSettings key is missing", async () => {
204+
;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }])
205+
206+
const mockFileContent = JSON.stringify({
207+
providerProfiles: {
208+
currentApiConfigName: "test",
209+
apiConfigs: { test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" } },
210+
},
211+
})
212+
213+
;(fs.readFile as jest.Mock).mockResolvedValue(mockFileContent)
214+
215+
const previousProviderProfiles = {
216+
currentApiConfigName: "default",
217+
apiConfigs: { default: { apiProvider: "anthropic" as ProviderName, id: "default-id" } },
218+
}
219+
220+
mockProviderSettingsManager.export.mockResolvedValue(previousProviderProfiles)
221+
222+
mockProviderSettingsManager.listConfig.mockResolvedValue([
223+
{ name: "test", id: "test-id", apiProvider: "openai" as ProviderName },
224+
{ name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName },
225+
])
226+
227+
mockContextProxy.export.mockResolvedValue({ mode: "code" })
228+
229+
const result = await importSettings({
230+
providerSettingsManager: mockProviderSettingsManager,
231+
contextProxy: mockContextProxy,
232+
customModesManager: mockCustomModesManager,
233+
})
234+
235+
expect(result.success).toBe(true)
236+
expect(fs.readFile).toHaveBeenCalledWith("/mock/path/settings.json", "utf-8")
237+
expect(mockProviderSettingsManager.export).toHaveBeenCalled()
238+
expect(mockProviderSettingsManager.import).toHaveBeenCalledWith({
239+
...previousProviderProfiles,
240+
currentApiConfigName: "test",
241+
apiConfigs: {
242+
test: { apiProvider: "openai" as ProviderName, apiKey: "test-key", id: "test-id" },
243+
},
244+
})
245+
246+
// Should call setValues with an empty object since globalSettings is missing.
247+
expect(mockContextProxy.setValues).toHaveBeenCalledWith({})
248+
expect(mockContextProxy.setValue).toHaveBeenCalledWith("currentApiConfigName", "test")
249+
expect(mockContextProxy.setValue).toHaveBeenCalledWith("listApiConfigMeta", [
250+
{ name: "test", id: "test-id", apiProvider: "openai" as ProviderName },
251+
{ name: "default", id: "default-id", apiProvider: "anthropic" as ProviderName },
252+
])
253+
})
254+
203255
it("should return success: false when file content is not valid JSON", async () => {
204256
// Mock successful file selection
205257
;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }])

src/core/config/importExport.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import * as path from "path"
33
import fs from "fs/promises"
44

55
import * as vscode from "vscode"
6-
import { z } from "zod"
6+
import { z, ZodError } from "zod"
77

88
import { globalSettingsSchema } from "../../schemas"
99

1010
import { ProviderSettingsManager, providerProfilesSchema } from "./ProviderSettingsManager"
1111
import { ContextProxy } from "./ContextProxy"
1212
import { CustomModesManager } from "./CustomModesManager"
13+
import { telemetryService } from "../../services/telemetry/TelemetryService"
1314

1415
type ImportOptions = {
1516
providerSettingsManager: ProviderSettingsManager
@@ -34,15 +35,14 @@ export const importSettings = async ({ providerSettingsManager, contextProxy, cu
3435

3536
const schema = z.object({
3637
providerProfiles: providerProfilesSchema,
37-
globalSettings: globalSettingsSchema,
38+
globalSettings: globalSettingsSchema.optional(),
3839
})
3940

4041
try {
4142
const previousProviderProfiles = await providerSettingsManager.export()
4243

43-
const { providerProfiles: newProviderProfiles, globalSettings } = schema.parse(
44-
JSON.parse(await fs.readFile(uris[0].fsPath, "utf-8")),
45-
)
44+
const data = JSON.parse(await fs.readFile(uris[0].fsPath, "utf-8"))
45+
const { providerProfiles: newProviderProfiles, globalSettings = {} } = schema.parse(data)
4646

4747
const providerProfiles = {
4848
currentApiConfigName: newProviderProfiles.currentApiConfigName,
@@ -79,7 +79,16 @@ export const importSettings = async ({ providerSettingsManager, contextProxy, cu
7979

8080
return { providerProfiles, globalSettings, success: true }
8181
} catch (e) {
82-
return { success: false }
82+
let error = "Unknown error"
83+
84+
if (e instanceof ZodError) {
85+
error = e.message
86+
telemetryService.captureSchemaValidationError({ schemaName: "ImportExport", error: e })
87+
} else if (e instanceof Error) {
88+
error = e.message
89+
}
90+
91+
return { success: false, error }
8392
}
8493
}
8594

@@ -97,6 +106,14 @@ export const exportSettings = async ({ providerSettingsManager, contextProxy }:
97106
const providerProfiles = await providerSettingsManager.export()
98107
const globalSettings = await contextProxy.export()
99108

109+
// It's okay if there are no global settings, but if there are no
110+
// provider profile configured then don't export. If we wanted to
111+
// support this case then the `importSettings` function would need to
112+
// be updated to handle the case where there are no provider profiles.
113+
if (typeof providerProfiles === "undefined") {
114+
return
115+
}
116+
100117
const dirname = path.dirname(uri.fsPath)
101118
await fs.mkdir(dirname, { recursive: true })
102119
await fs.writeFile(uri.fsPath, JSON.stringify({ providerProfiles, globalSettings }, null, 2), "utf-8")

src/core/webview/webviewMessageHandler.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,20 +245,23 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
245245
case "exportTaskWithId":
246246
provider.exportTaskWithId(message.text!)
247247
break
248-
case "importSettings":
249-
const { success } = await importSettings({
248+
case "importSettings": {
249+
const result = await importSettings({
250250
providerSettingsManager: provider.providerSettingsManager,
251251
contextProxy: provider.contextProxy,
252252
customModesManager: provider.customModesManager,
253253
})
254254

255-
if (success) {
255+
if (result.success) {
256256
provider.settingsImportedAt = Date.now()
257257
await provider.postStateToWebview()
258258
await vscode.window.showInformationMessage(t("common:info.settings_imported"))
259+
} else if (result.error) {
260+
await vscode.window.showErrorMessage(t("common:errors.settings_import_failed", { error: result.error }))
259261
}
260262

261263
break
264+
}
262265
case "exportSettings":
263266
await exportSettings({
264267
providerSettingsManager: provider.providerSettingsManager,

src/i18n/locales/ca/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@
7171
"mcp_server_not_found": "Servidor \"{{serverName}}\" no trobat a la configuració",
7272
"custom_storage_path_set": "Ruta d'emmagatzematge personalitzada establerta: {{path}}",
7373
"default_storage_path": "S'ha reprès l'ús de la ruta d'emmagatzematge predeterminada",
74-
"settings_imported": "Configuració importada correctament."
74+
"settings_imported": "Configuració importada correctament.",
75+
"settings_import_failed": "Ha fallat la importació de la configuració: {{error}}."
7576
},
7677
"answers": {
7778
"yes": "",

src/i18n/locales/de/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
"mcp_server_not_found": "Server \"{{serverName}}\" nicht in der Konfiguration gefunden",
6868
"custom_storage_path_set": "Benutzerdefinierter Speicherpfad festgelegt: {{path}}",
6969
"default_storage_path": "Auf Standardspeicherpfad zurückgesetzt",
70-
"settings_imported": "Einstellungen erfolgreich importiert."
70+
"settings_imported": "Einstellungen erfolgreich importiert.",
71+
"settings_import_failed": "Fehler beim Importieren der Einstellungen: {{error}}."
7172
},
7273
"answers": {
7374
"yes": "Ja",

src/i18n/locales/en/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
"mcp_server_not_found": "Server \"{{serverName}}\" not found in configuration",
6868
"custom_storage_path_set": "Custom storage path set: {{path}}",
6969
"default_storage_path": "Reverted to using default storage path",
70-
"settings_imported": "Settings imported successfully."
70+
"settings_imported": "Settings imported successfully.",
71+
"settings_import_failed": "Settings import failed: {{error}}."
7172
},
7273
"answers": {
7374
"yes": "Yes",

src/i18n/locales/es/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
"mcp_server_not_found": "Servidor \"{{serverName}}\" no encontrado en la configuración",
6868
"custom_storage_path_set": "Ruta de almacenamiento personalizada establecida: {{path}}",
6969
"default_storage_path": "Se ha vuelto a usar la ruta de almacenamiento predeterminada",
70-
"settings_imported": "Configuración importada correctamente."
70+
"settings_imported": "Configuración importada correctamente.",
71+
"settings_import_failed": "Error al importar la configuración: {{error}}."
7172
},
7273
"answers": {
7374
"yes": "",

src/i18n/locales/fr/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
"mcp_server_not_found": "Serveur \"{{serverName}}\" introuvable dans la configuration",
6868
"custom_storage_path_set": "Chemin de stockage personnalisé défini : {{path}}",
6969
"default_storage_path": "Retour au chemin de stockage par défaut",
70-
"settings_imported": "Paramètres importés avec succès."
70+
"settings_imported": "Paramètres importés avec succès.",
71+
"settings_import_failed": "Échec de l'importation des paramètres : {{error}}."
7172
},
7273
"answers": {
7374
"yes": "Oui",

src/i18n/locales/hi/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
"mcp_server_not_found": "सर्वर \"{{serverName}}\" कॉन्फ़िगरेशन में नहीं मिला",
6868
"custom_storage_path_set": "कस्टम स्टोरेज पाथ सेट किया गया: {{path}}",
6969
"default_storage_path": "डिफ़ॉल्ट स्टोरेज पाथ का उपयोग पुनः शुरू किया गया",
70-
"settings_imported": "सेटिंग्स सफलतापूर्वक इम्पोर्ट की गईं।"
70+
"settings_imported": "सेटिंग्स सफलतापूर्वक इम्पोर्ट की गईं।",
71+
"settings_import_failed": "सेटिंग्स इम्पोर्ट करने में विफल: {{error}}।"
7172
},
7273
"answers": {
7374
"yes": "हां",

src/i18n/locales/it/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
"mcp_server_not_found": "Server \"{{serverName}}\" non trovato nella configurazione",
6868
"custom_storage_path_set": "Percorso di archiviazione personalizzato impostato: {{path}}",
6969
"default_storage_path": "Tornato al percorso di archiviazione predefinito",
70-
"settings_imported": "Impostazioni importate con successo."
70+
"settings_imported": "Impostazioni importate con successo.",
71+
"settings_import_failed": "Importazione delle impostazioni fallita: {{error}}."
7172
},
7273
"answers": {
7374
"yes": "",

0 commit comments

Comments
 (0)