Skip to content

Commit

Permalink
fix: empty config (#389)
Browse files Browse the repository at this point in the history
  • Loading branch information
cazala authored Jan 24, 2025
1 parent 6f66805 commit a8780c9
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 21 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
29 changes: 26 additions & 3 deletions packages/main/src/modules/config.ts
Original file line number Diff line number Diff line change
@@ -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<Record<string, any>>('');
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;
44 changes: 26 additions & 18 deletions packages/preload/src/modules/config.ts
Original file line number Diff line number Diff line change
@@ -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<Config> {
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;
}

/**
Expand All @@ -38,7 +32,13 @@ export async function getConfigPath(): Promise<string> {
} 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);
}

/**
Expand All @@ -50,12 +50,20 @@ export async function getConfigPath(): Promise<string> {
*/
export async function getConfig(): Promise<Readonly<Config>> {
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<Config>;
// 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);
}
Expand Down
7 changes: 7 additions & 0 deletions packages/shared/paths.ts
Original file line number Diff line number Diff line change
@@ -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);
}
22 changes: 22 additions & 0 deletions packages/shared/types/config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
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;
workspace: {
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<Config>, source: Config): Config {
return deepmerge(source, target, {
// Clone arrays instead of merging them
arrayMerge: (_, sourceArray) => sourceArray,
});
}

0 comments on commit a8780c9

Please sign in to comment.