Fix desktop settings legacy override
This commit is contained in:
@@ -94,6 +94,58 @@ describe("desktop-settings", () => {
|
|||||||
expect(files).toEqual(["desktop-settings.json"]);
|
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 () => {
|
it("migrates desktop-owned values from legacy renderer settings once", async () => {
|
||||||
const userDataPath = await createTempUserDataDir();
|
const userDataPath = await createTempUserDataDir();
|
||||||
directories.add(userDataPath);
|
directories.add(userDataPath);
|
||||||
|
|||||||
@@ -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 path from "node:path";
|
||||||
|
|
||||||
import type { AppReleaseChannel } from "../features/auto-updater.js";
|
import type { AppReleaseChannel } from "../features/auto-updater.js";
|
||||||
@@ -58,10 +58,17 @@ function coerceBoolean(value: unknown): boolean | null {
|
|||||||
return typeof value === "boolean" ? value : null;
|
return typeof value === "boolean" ? value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
||||||
|
return error instanceof Error;
|
||||||
|
}
|
||||||
|
|
||||||
function buildDefaultDocument(): PersistedDesktopSettingsDocument {
|
function buildDefaultDocument(): PersistedDesktopSettingsDocument {
|
||||||
return {
|
return {
|
||||||
version: 1,
|
version: 1,
|
||||||
settings: DEFAULT_DESKTOP_SETTINGS,
|
settings: {
|
||||||
|
releaseChannel: DEFAULT_DESKTOP_SETTINGS.releaseChannel,
|
||||||
|
daemon: { ...DEFAULT_DESKTOP_SETTINGS.daemon },
|
||||||
|
},
|
||||||
migrations: {
|
migrations: {
|
||||||
legacyRendererSettingsImported: false,
|
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 {
|
function coerceDocument(input: unknown): PersistedDesktopSettingsDocument {
|
||||||
if (!isRecord(input)) {
|
if (!isRecord(input)) {
|
||||||
return buildDefaultDocument();
|
return buildDefaultDocument();
|
||||||
@@ -201,19 +212,31 @@ export function createDesktopSettingsStore({
|
|||||||
return cachedDocument;
|
return cachedDocument;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let raw: string;
|
||||||
try {
|
try {
|
||||||
await access(filePath);
|
raw = await readFile(filePath, "utf8");
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
if (!isNodeError(error) || error.code !== "ENOENT") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
const document = buildDefaultDocument();
|
const document = buildDefaultDocument();
|
||||||
await persistDocument(document);
|
await persistDocument(document);
|
||||||
return 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 {
|
try {
|
||||||
const raw = await readFile(filePath, "utf8");
|
return await loadDocument();
|
||||||
const document = coerceDocument(JSON.parse(raw));
|
|
||||||
await persistDocument(document);
|
|
||||||
return document;
|
|
||||||
} catch {
|
} catch {
|
||||||
const document = buildDefaultDocument();
|
const document = buildDefaultDocument();
|
||||||
await persistDocument(document);
|
await persistDocument(document);
|
||||||
@@ -228,17 +251,24 @@ export function createDesktopSettingsStore({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async patch(patch: unknown): Promise<DesktopSettings> {
|
async patch(patch: unknown): Promise<DesktopSettings> {
|
||||||
const current = await loadDocument();
|
const current = await loadWritableDocument();
|
||||||
const next = mergeDesktopSettings(current.settings, coerceDesktopSettingsPatch(patch));
|
const coercedPatch = coerceDesktopSettingsPatch(patch);
|
||||||
|
const next = mergeDesktopSettings(current.settings, coercedPatch);
|
||||||
await persistDocument({
|
await persistDocument({
|
||||||
...current,
|
...current,
|
||||||
settings: next,
|
settings: next,
|
||||||
|
migrations: {
|
||||||
|
...current.migrations,
|
||||||
|
legacyRendererSettingsImported:
|
||||||
|
current.migrations.legacyRendererSettingsImported ||
|
||||||
|
hasLegacyRendererOwnedPatch(coercedPatch),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return next;
|
return next;
|
||||||
},
|
},
|
||||||
|
|
||||||
async migrateLegacyRendererSettings(legacySettings: unknown): Promise<DesktopSettings> {
|
async migrateLegacyRendererSettings(legacySettings: unknown): Promise<DesktopSettings> {
|
||||||
const current = await loadDocument();
|
const current = await initializeLegacyRendererMigration();
|
||||||
if (current.migrations.legacyRendererSettingsImported) {
|
if (current.migrations.legacyRendererSettingsImported) {
|
||||||
return current.settings;
|
return current.settings;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user