From a8780c9d530bbc3604851f132d28d893b44f4cc5 Mon Sep 17 00:00:00 2001 From: Juan Cazala Date: Fri, 24 Jan 2025 15:28:40 -0300 Subject: [PATCH] fix: empty config (#389) --- package-lock.json | 10 ++++++ package.json | 1 + packages/main/src/modules/config.ts | 29 +++++++++++++++-- packages/preload/src/modules/config.ts | 44 +++++++++++++++----------- packages/shared/paths.ts | 7 ++++ packages/shared/types/config.ts | 22 +++++++++++++ 6 files changed, 92 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index c476837c..3343f4dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "cmd-shim": "^6.0.3", "decentraland-connect": "^7.1.0", "decentraland-crypto-fetch": "^2.0.1", + "deepmerge": "^4.3.1", "electron-log": "^5.1.6", "electron-updater": "6.2.1", "http-server": "^14.1.1", @@ -8551,6 +8552,15 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", diff --git a/package.json b/package.json index 97adde5e..fbba2d34 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "cmd-shim": "^6.0.3", "decentraland-connect": "^7.1.0", "decentraland-crypto-fetch": "^2.0.1", + "deepmerge": "^4.3.1", "electron-log": "^5.1.6", "electron-updater": "6.2.1", "http-server": "^14.1.1", diff --git a/packages/main/src/modules/config.ts b/packages/main/src/modules/config.ts index 0254f4a7..a55c3650 100644 --- a/packages/main/src/modules/config.ts +++ b/packages/main/src/modules/config.ts @@ -1,7 +1,30 @@ import path from 'node:path'; import { FileSystemStorage } from '/shared/types/storage'; -import { SETTINGS_DIRECTORY } from '/shared/paths'; +import { SETTINGS_DIRECTORY, CONFIG_FILE_NAME, getFullScenesPath } from '/shared/paths'; +import { DEFAULT_CONFIG, mergeConfig } from '/shared/types/config'; import { getUserDataPath } from './electron'; -export const CONFIG_PATH = path.join(getUserDataPath(), SETTINGS_DIRECTORY, 'config.json'); -export const config = await FileSystemStorage.getOrCreate(CONFIG_PATH); +export const CONFIG_PATH = path.join(getUserDataPath(), SETTINGS_DIRECTORY, CONFIG_FILE_NAME); +const storage = await FileSystemStorage.getOrCreate(CONFIG_PATH); + +// Initialize with default values if empty +const existingConfig = await storage.get>(''); +const defaultConfig = { ...DEFAULT_CONFIG }; +defaultConfig.settings.scenesPath = getFullScenesPath(getUserDataPath()); + +if (!existingConfig || Object.keys(existingConfig).length === 0) { + // Write the default config + for (const [key, value] of Object.entries(defaultConfig)) { + await storage.set(key, value); + } +} else { + // Deep merge with defaults if config exists but might be missing properties + const mergedConfig = mergeConfig(existingConfig, defaultConfig); + if (JSON.stringify(existingConfig) !== JSON.stringify(mergedConfig)) { + for (const [key, value] of Object.entries(mergedConfig)) { + await storage.set(key, value); + } + } +} + +export const config = storage; diff --git a/packages/preload/src/modules/config.ts b/packages/preload/src/modules/config.ts index 8ae173e7..b5a523e5 100644 --- a/packages/preload/src/modules/config.ts +++ b/packages/preload/src/modules/config.ts @@ -1,29 +1,23 @@ import fs from 'node:fs/promises'; import path from 'path'; import { produce, type WritableDraft } from 'immer'; +import deepmerge from 'deepmerge'; import type { Config } from '/shared/types/config'; -import { DEFAULT_DEPENDENCY_UPDATE_STRATEGY } from '/shared/types/settings'; +import { DEFAULT_CONFIG, mergeConfig } from '/shared/types/config'; import { invoke } from './invoke'; -import { getDefaultScenesPath } from './settings'; -import { SETTINGS_DIRECTORY } from '/shared/paths'; - -const CONFIG_FILE_NAME = 'config.json'; +import { SETTINGS_DIRECTORY, CONFIG_FILE_NAME, getFullScenesPath } from '/shared/paths'; let config: Config | undefined; async function getDefaultConfig(): Promise { - return { - version: 1, - workspace: { - paths: [], - }, - settings: { - scenesPath: await getDefaultScenesPath(), - dependencyUpdateStrategy: DEFAULT_DEPENDENCY_UPDATE_STRATEGY, - }, - }; + // Clone the default config to avoid modifying the shared constant + const defaultConfig = deepmerge({}, DEFAULT_CONFIG); + // Update the scenesPath to include the userDataPath + const userDataPath = await invoke('electron.getUserDataPath'); + defaultConfig.settings.scenesPath = getFullScenesPath(userDataPath); + return defaultConfig; } /** @@ -38,7 +32,13 @@ export async function getConfigPath(): Promise { } catch (_) { await fs.mkdir(userDataPath); } - return path.join(userDataPath, SETTINGS_DIRECTORY, CONFIG_FILE_NAME); + const settingsDir = path.join(userDataPath, SETTINGS_DIRECTORY); + try { + await fs.stat(settingsDir); + } catch (_) { + await fs.mkdir(settingsDir); + } + return path.join(settingsDir, CONFIG_FILE_NAME); } /** @@ -50,12 +50,20 @@ export async function getConfigPath(): Promise { */ export async function getConfig(): Promise> { if (!config) { + const defaultConfig = await getDefaultConfig(); try { const configPath = await getConfigPath(); - config = JSON.parse(await fs.readFile(configPath, 'utf-8')) as Config; + const existingConfig = JSON.parse(await fs.readFile(configPath, 'utf-8')) as Partial; + // Deep merge existing config with default config, preserving existing values + config = mergeConfig(existingConfig, defaultConfig); + // If the merged config is different from what's on disk, write it back + if (JSON.stringify(existingConfig) !== JSON.stringify(config)) { + await writeConfig(config); + } } catch (_) { try { - await writeConfig(await getDefaultConfig()); + config = defaultConfig; + await writeConfig(config); } catch (e) { console.error('[Preload] Failed initializing config file', e); } diff --git a/packages/shared/paths.ts b/packages/shared/paths.ts index 62235db0..8489d43b 100644 --- a/packages/shared/paths.ts +++ b/packages/shared/paths.ts @@ -1,3 +1,10 @@ +import path from 'node:path'; + export const SCENES_DIRECTORY = 'Scenes'; export const SETTINGS_DIRECTORY = 'Settings'; export const CUSTOM_ASSETS_DIRECTORY = 'Custom Items'; +export const CONFIG_FILE_NAME = 'config.json'; + +export function getFullScenesPath(userDataPath: string): string { + return path.join(userDataPath, SCENES_DIRECTORY); +} diff --git a/packages/shared/types/config.ts b/packages/shared/types/config.ts index 553016f6..6ca03765 100644 --- a/packages/shared/types/config.ts +++ b/packages/shared/types/config.ts @@ -1,4 +1,7 @@ +import deepmerge from 'deepmerge'; import { type AppSettings } from './settings'; +import { DEFAULT_DEPENDENCY_UPDATE_STRATEGY } from './settings'; +import { SCENES_DIRECTORY } from '/shared/paths'; export type Config = { version: number; @@ -6,4 +9,23 @@ export type Config = { paths: string[]; }; settings: AppSettings; + userId?: string; }; + +export const DEFAULT_CONFIG: Config = { + version: 1, + workspace: { + paths: [], + }, + settings: { + scenesPath: SCENES_DIRECTORY, // Base directory name, will be joined with userDataPath by main/preload + dependencyUpdateStrategy: DEFAULT_DEPENDENCY_UPDATE_STRATEGY, + }, +}; + +export function mergeConfig(target: Partial, source: Config): Config { + return deepmerge(source, target, { + // Clone arrays instead of merging them + arrayMerge: (_, sourceArray) => sourceArray, + }); +}