Roo-Code/src/utils/migrateSettings.ts

115 lines
4 KiB
TypeScript

import * as vscode from "vscode"
import * as path from "path"
import * as fs from "fs/promises"
import { fileExistsAtPath } from "./fs"
import { GlobalFileNames } from "../shared/globalFileNames"
import * as yaml from "yaml"
const deprecatedCustomModesJSONFilename = "custom_modes.json"
/**
* Migrates old settings files to new file names
*
* TODO: Remove this migration code in September 2025 (6 months after implementation)
*/
export async function migrateSettings(
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
): Promise<void> {
// Legacy file names that need to be migrated to the new names in GlobalFileNames
const fileMigrations = [
// custom_modes.json to custom_modes.yaml is handled separately below
{ oldName: "cline_custom_modes.json", newName: deprecatedCustomModesJSONFilename },
{ oldName: "cline_mcp_settings.json", newName: GlobalFileNames.mcpSettings },
]
try {
const settingsDir = path.join(context.globalStorageUri.fsPath, "settings")
// Check if settings directory exists first
if (!(await fileExistsAtPath(settingsDir))) {
outputChannel.appendLine("No settings directory found, no migrations necessary")
return
}
// Process each file migration
try {
for (const migration of fileMigrations) {
const oldPath = path.join(settingsDir, migration.oldName)
const newPath = path.join(settingsDir, migration.newName)
// Only migrate if old file exists and new file doesn't exist yet
// This ensures we don't overwrite any existing new files
const oldFileExists = await fileExistsAtPath(oldPath)
const newFileExists = await fileExistsAtPath(newPath)
if (oldFileExists && !newFileExists) {
await fs.rename(oldPath, newPath)
outputChannel.appendLine(`Renamed ${migration.oldName} to ${migration.newName}`)
} else {
outputChannel.appendLine(
`Skipping migration of ${migration.oldName} to ${migration.newName}: ${oldFileExists ? "new file already exists" : "old file not found"}`,
)
}
}
// Special migration for custom_modes.json to custom_modes.yaml with content transformation
await migrateCustomModesToYaml(settingsDir, outputChannel)
} catch (error) {
outputChannel.appendLine(`Error in file migrations: ${error}`)
}
} catch (error) {
outputChannel.appendLine(`Error migrating settings files: ${error}`)
}
}
/**
* Special migration function to convert custom_modes.json to YAML format
*/
async function migrateCustomModesToYaml(settingsDir: string, outputChannel: vscode.OutputChannel): Promise<void> {
const oldJsonPath = path.join(settingsDir, deprecatedCustomModesJSONFilename)
const newYamlPath = path.join(settingsDir, GlobalFileNames.customModes)
// Only proceed if JSON exists and YAML doesn't
const jsonExists = await fileExistsAtPath(oldJsonPath)
const yamlExists = await fileExistsAtPath(newYamlPath)
if (!jsonExists) {
outputChannel.appendLine("No custom_modes.json found, skipping YAML migration")
return
}
if (yamlExists) {
outputChannel.appendLine("custom_modes.yaml already exists, skipping migration")
return
}
try {
// Read JSON content
const jsonContent = await fs.readFile(oldJsonPath, "utf-8")
try {
// Parse JSON to object (using the yaml library just to be safe/consistent)
const customModesData = yaml.parse(jsonContent)
// Convert to YAML with no line width limit to prevent line breaks
const yamlContent = yaml.stringify(customModesData, { lineWidth: 0 })
// Write YAML file
await fs.writeFile(newYamlPath, yamlContent, "utf-8")
// Keeping the old JSON file for backward compatibility
// This allows users to roll back if needed
outputChannel.appendLine(
"Successfully migrated custom_modes.json to YAML format (original JSON file preserved for rollback purposes)",
)
} catch (parseError) {
// Handle corrupt JSON file
outputChannel.appendLine(
`Error parsing custom_modes.json: ${parseError}. File might be corrupted. Skipping migration.`,
)
}
} catch (fileError) {
outputChannel.appendLine(`Error reading custom_modes.json: ${fileError}. Skipping migration.`)
}
}