import { app } from "electron" import log from "electron-log/main.js" import { existsSync, readdirSync, readFileSync } from "node:fs" import { homedir } from "node:os" import { join } from "node:path" import { CHANNEL } from "./constants" import { getStore } from "./store" const TAURI_MIGRATED_KEY = "tauriMigrated" // Resolve the directory where Tauri stored its .dat files for the given app identifier. // Mirrors Tauri's AppLocalData / AppData resolution per OS. function tauriDir(id: string) { switch (process.platform) { case "darwin": return join(homedir(), "Library", "Application Support", id) case "win32": return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), id) default: return join(process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"), id) } } // The Tauri app identifier changes between dev/beta/prod builds. const TAURI_APP_IDS: Record = { dev: "ai.opencode.desktop.dev", beta: "ai.opencode.desktop.beta", prod: "ai.opencode.desktop", } function tauriAppId() { return app.isPackaged ? TAURI_APP_IDS[CHANNEL] : "ai.opencode.desktop.dev" } // Migrate a single Tauri .dat file into the corresponding electron-store. // `opencode.settings.dat` is special: it maps to the `opencode.settings` store // (the electron-store name without the `.dat` extension). All other .dat files // keep their full filename as the electron-store name so they match what the // renderer already passes via IPC (e.g. `"default.dat"`, `"opencode.global.dat"`). function migrateFile(datPath: string, filename: string) { let data: Record try { data = JSON.parse(readFileSync(datPath, "utf-8")) } catch (err) { log.warn("tauri migration: failed to parse", filename, err) return } // opencode.settings.dat → the electron settings store ("opencode.settings"). // All other .dat files keep their full filename as the store name so they match // what the renderer passes via IPC (e.g. "default.dat", "opencode.global.dat"). const storeName = filename === "opencode.settings.dat" ? "opencode.settings" : filename const target = getStore(storeName) const migrated: string[] = [] const skipped: string[] = [] for (const [key, value] of Object.entries(data)) { // Don't overwrite values the user has already set in the Electron app. if (target.has(key)) { skipped.push(key) continue } target.set(key, value) migrated.push(key) } log.log("tauri migration: migrated", filename, "→", storeName, { migrated, skipped }) } export function migrate() { if (getStore().get(TAURI_MIGRATED_KEY)) { log.log("tauri migration: already done, skipping") return } const dir = tauriDir(tauriAppId()) log.log("tauri migration: starting", { dir }) if (!existsSync(dir)) { log.log("tauri migration: no tauri data directory found, nothing to migrate") getStore().set(TAURI_MIGRATED_KEY, true) return } for (const filename of readdirSync(dir)) { if (!filename.endsWith(".dat")) continue migrateFile(join(dir, filename), filename) } log.log("tauri migration: complete") getStore().set(TAURI_MIGRATED_KEY, true) }