From d0eaf0e51d40853a9a8136eb58a9f1d5fbb63b61 Mon Sep 17 00:00:00 2001 From: webdevcody Date: Sun, 18 Jan 2026 16:25:25 -0500 Subject: [PATCH] feat: enhance migration process to copy entire data directory from legacy Electron userData location This update expands the migration functionality in the SettingsService to include the entire data directory, rather than just specific files. The migration now handles all files and directories, including settings.json, credentials.json, sessions-metadata.json, and conversation histories. Additionally, logging has been improved to reflect the migration of all items and to provide clearer information on the migration process. Key changes: - Updated migration logic to recursively copy all contents from the legacy directory. - Enhanced logging for migration status and errors. - Added a new private method, `copyDirectoryContents`, to facilitate the recursive copying of files and directories. --- apps/server/src/services/settings-service.ts | 120 ++++++++++++++----- 1 file changed, 87 insertions(+), 33 deletions(-) diff --git a/apps/server/src/services/settings-service.ts b/apps/server/src/services/settings-service.ts index 16d9527a..1b65fbd4 100644 --- a/apps/server/src/services/settings-service.ts +++ b/apps/server/src/services/settings-service.ts @@ -824,16 +824,21 @@ export class SettingsService { } /** - * Migrate settings from legacy Electron userData location to new shared data directory + * Migrate entire data directory from legacy Electron userData location to new shared data directory * - * This handles the migration from when Electron stored settings in the platform-specific + * This handles the migration from when Electron stored data in the platform-specific * userData directory (e.g., ~/.config/Automaker) to the new shared ./data directory. * * Migration only occurs if: * 1. The new location does NOT have settings.json * 2. The legacy location DOES have settings.json * - * Files migrated: settings.json, credentials.json + * Migrates all files and directories including: + * - settings.json (global settings) + * - credentials.json (API keys) + * - sessions-metadata.json (chat session metadata) + * - agent-sessions/ (conversation histories) + * - Any other files in the data directory * * @returns Promise resolving to migration result */ @@ -853,7 +858,7 @@ export class SettingsService { return { migrated: false, migratedFiles, legacyPath, errors }; } - logger.info(`Checking for legacy settings migration from: ${legacyPath}`); + logger.info(`Checking for legacy data migration from: ${legacyPath}`); logger.info(`Current data directory: ${this.dataDir}`); // Check if new settings already exist @@ -871,7 +876,7 @@ export class SettingsService { return { migrated: false, migratedFiles, legacyPath, errors }; } - // Check if legacy settings exist + // Check if legacy directory exists and has settings const legacySettingsPath = path.join(legacyPath, 'settings.json'); let legacySettingsExist = false; try { @@ -886,8 +891,8 @@ export class SettingsService { return { migrated: false, migratedFiles, legacyPath, errors }; } - // Perform migration - logger.info('Found legacy settings, migrating to new location...'); + // Perform migration of entire directory + logger.info('Found legacy data directory, migrating all contents to new location...'); // Ensure new data directory exists try { @@ -899,35 +904,12 @@ export class SettingsService { return { migrated: false, migratedFiles, legacyPath, errors }; } - // Migrate settings.json - try { - const settingsContent = await fs.readFile(legacySettingsPath, 'utf-8'); - await fs.writeFile(newSettingsPath, settingsContent, 'utf-8'); - migratedFiles.push('settings.json'); - logger.info('Migrated settings.json from legacy location'); - } catch (error) { - const msg = `Failed to migrate settings.json: ${error}`; - logger.error(msg); - errors.push(msg); - } - - // Migrate credentials.json if it exists - const legacyCredentialsPath = path.join(legacyPath, 'credentials.json'); - const newCredentialsPath = getCredentialsPath(this.dataDir); - try { - await fs.access(legacyCredentialsPath); - const credentialsContent = await fs.readFile(legacyCredentialsPath, 'utf-8'); - await fs.writeFile(newCredentialsPath, credentialsContent, 'utf-8'); - migratedFiles.push('credentials.json'); - logger.info('Migrated credentials.json from legacy location'); - } catch { - // Credentials file doesn't exist in legacy location, that's fine - logger.debug('No legacy credentials.json found'); - } + // Recursively copy all files and directories + await this.copyDirectoryContents(legacyPath, this.dataDir, migratedFiles, errors); if (migratedFiles.length > 0) { logger.info( - `Migration complete. Migrated ${migratedFiles.length} file(s): ${migratedFiles.join(', ')}` + `Migration complete. Migrated ${migratedFiles.length} item(s): ${migratedFiles.join(', ')}` ); logger.info(`Legacy path: ${legacyPath}`); logger.info(`New path: ${this.dataDir}`); @@ -940,4 +922,76 @@ export class SettingsService { errors, }; } + + /** + * Recursively copy directory contents from source to destination + * + * @param srcDir - Source directory path + * @param destDir - Destination directory path + * @param migratedFiles - Array to track migrated files + * @param errors - Array to track errors + * @param relativePath - Current relative path for logging + */ + private async copyDirectoryContents( + srcDir: string, + destDir: string, + migratedFiles: string[], + errors: string[], + relativePath: string = '' + ): Promise { + try { + const entries = await fs.readdir(srcDir, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = path.join(srcDir, entry.name); + const destPath = path.join(destDir, entry.name); + const itemRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name; + + // Skip if destination already exists + try { + await fs.access(destPath); + logger.debug(`Skipping ${itemRelativePath} - already exists in destination`); + continue; + } catch { + // Destination doesn't exist, proceed with copy + } + + if (entry.isDirectory()) { + // Create directory and recursively copy contents + try { + await fs.mkdir(destPath, { recursive: true }); + await this.copyDirectoryContents( + srcPath, + destPath, + migratedFiles, + errors, + itemRelativePath + ); + migratedFiles.push(itemRelativePath + '/'); + logger.info(`Migrated directory: ${itemRelativePath}/`); + } catch (error) { + const msg = `Failed to migrate directory ${itemRelativePath}: ${error}`; + logger.error(msg); + errors.push(msg); + } + } else if (entry.isFile()) { + // Copy file + try { + const content = await fs.readFile(srcPath); + await fs.writeFile(destPath, content); + migratedFiles.push(itemRelativePath); + logger.info(`Migrated file: ${itemRelativePath}`); + } catch (error) { + const msg = `Failed to migrate file ${itemRelativePath}: ${error}`; + logger.error(msg); + errors.push(msg); + } + } + } + } catch (error) { + const msg = `Failed to read directory ${srcDir}: ${error}`; + logger.error(msg); + errors.push(msg); + } + } }