Fix desktop settings legacy override

This commit is contained in:
Mohamed Boudra
2026-05-03 22:57:33 +07:00
parent f0d96f8e5a
commit 6d8c7c4d2d
2 changed files with 93 additions and 11 deletions

View File

@@ -94,6 +94,58 @@ describe("desktop-settings", () => {
expect(files).toEqual(["desktop-settings.json"]);
});
it("does not let stale legacy renderer settings override an explicit desktop patch", async () => {
const userDataPath = await createTempUserDataDir();
directories.add(userDataPath);
const store = createDesktopSettingsStore({ userDataPath });
const patched = await store.patch({
daemon: {
manageBuiltInDaemon: false,
},
});
const migrated = await store.migrateLegacyRendererSettings({
manageBuiltInDaemon: true,
releaseChannel: "beta",
});
const persisted = JSON.parse(await readFile(settingsFilePath(userDataPath), "utf8")) as {
migrations: { legacyRendererSettingsImported: boolean };
settings: DesktopSettings;
};
expect(patched.daemon.manageBuiltInDaemon).toBe(false);
expect(migrated.daemon.manageBuiltInDaemon).toBe(false);
expect(migrated.releaseChannel).toBe("stable");
expect(persisted.migrations.legacyRendererSettingsImported).toBe(true);
expect(persisted.settings.daemon.manageBuiltInDaemon).toBe(false);
});
it("does not rewrite existing settings while reading them", async () => {
const userDataPath = await createTempUserDataDir();
directories.add(userDataPath);
const raw = JSON.stringify({
version: 1,
settings: {
releaseChannel: "stable",
daemon: {
manageBuiltInDaemon: false,
keepRunningAfterQuit: true,
},
},
migrations: {
legacyRendererSettingsImported: false,
},
});
await writeFile(settingsFilePath(userDataPath), raw);
const store = createDesktopSettingsStore({ userDataPath });
const settings = await store.get();
const persisted = await readFile(settingsFilePath(userDataPath), "utf8");
expect(settings.daemon.manageBuiltInDaemon).toBe(false);
expect(persisted).toBe(raw);
});
it("migrates desktop-owned values from legacy renderer settings once", async () => {
const userDataPath = await createTempUserDataDir();
directories.add(userDataPath);

View File

@@ -1,4 +1,4 @@
import { access, mkdir, readFile, rename, writeFile } from "node:fs/promises";
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
import path from "node:path";
import type { AppReleaseChannel } from "../features/auto-updater.js";
@@ -58,10 +58,17 @@ function coerceBoolean(value: unknown): boolean | null {
return typeof value === "boolean" ? value : null;
}
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
return error instanceof Error;
}
function buildDefaultDocument(): PersistedDesktopSettingsDocument {
return {
version: 1,
settings: DEFAULT_DESKTOP_SETTINGS,
settings: {
releaseChannel: DEFAULT_DESKTOP_SETTINGS.releaseChannel,
daemon: { ...DEFAULT_DESKTOP_SETTINGS.daemon },
},
migrations: {
legacyRendererSettingsImported: false,
},
@@ -159,6 +166,10 @@ function mergeDesktopSettings(
};
}
function hasLegacyRendererOwnedPatch(patch: DesktopSettingsPatch): boolean {
return patch.releaseChannel !== undefined || patch.daemon?.manageBuiltInDaemon !== undefined;
}
function coerceDocument(input: unknown): PersistedDesktopSettingsDocument {
if (!isRecord(input)) {
return buildDefaultDocument();
@@ -201,19 +212,31 @@ export function createDesktopSettingsStore({
return cachedDocument;
}
let raw: string;
try {
await access(filePath);
} catch {
raw = await readFile(filePath, "utf8");
} catch (error) {
if (!isNodeError(error) || error.code !== "ENOENT") {
throw error;
}
const document = buildDefaultDocument();
await persistDocument(document);
return document;
}
const document = coerceDocument(JSON.parse(raw));
cachedDocument = document;
return document;
}
async function loadWritableDocument(): Promise<PersistedDesktopSettingsDocument> {
const document = await loadDocument();
await persistDocument(document);
return document;
}
async function initializeLegacyRendererMigration(): Promise<PersistedDesktopSettingsDocument> {
try {
const raw = await readFile(filePath, "utf8");
const document = coerceDocument(JSON.parse(raw));
await persistDocument(document);
return document;
return await loadDocument();
} catch {
const document = buildDefaultDocument();
await persistDocument(document);
@@ -228,17 +251,24 @@ export function createDesktopSettingsStore({
},
async patch(patch: unknown): Promise<DesktopSettings> {
const current = await loadDocument();
const next = mergeDesktopSettings(current.settings, coerceDesktopSettingsPatch(patch));
const current = await loadWritableDocument();
const coercedPatch = coerceDesktopSettingsPatch(patch);
const next = mergeDesktopSettings(current.settings, coercedPatch);
await persistDocument({
...current,
settings: next,
migrations: {
...current.migrations,
legacyRendererSettingsImported:
current.migrations.legacyRendererSettingsImported ||
hasLegacyRendererOwnedPatch(coercedPatch),
},
});
return next;
},
async migrateLegacyRendererSettings(legacySettings: unknown): Promise<DesktopSettings> {
const current = await loadDocument();
const current = await initializeLegacyRendererMigration();
if (current.migrations.legacyRendererSettingsImported) {
return current.settings;
}