Skip to content

Commit c27ac5f

Browse files
committed
refactor: extract settings persistence helpers
1 parent 35280ba commit c27ac5f

2 files changed

Lines changed: 60 additions & 44 deletions

File tree

lib/codex-manager/settings-hub.ts

Lines changed: 16 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { promises as fs } from "node:fs";
21
import { stdin as input, stdout as output } from "node:process";
32
import { createInterface } from "node:readline/promises";
43
import { loadPluginConfig, savePluginConfig } from "../config.js";
@@ -25,7 +24,6 @@ import { ANSI } from "../ui/ansi.js";
2524
import { UI_COPY } from "../ui/copy.js";
2625
import { getUiRuntimeOptions, setUiRuntimeOptions } from "../ui/runtime.js";
2726
import { type MenuItem, select } from "../ui/select.js";
28-
import { getUnifiedSettingsPath } from "../unified-settings.js";
2927
import { sleep } from "../utils.js";
3028
import {
3129
backendSettingsEqual,
@@ -64,10 +62,11 @@ import {
6462
mapExperimentalStatusHotkey,
6563
} from "./experimental-settings-schema.js";
6664
import {
67-
RETRYABLE_SETTINGS_WRITE_CODES,
68-
SETTINGS_WRITE_MAX_ATTEMPTS,
69-
withQueuedRetry,
70-
} from "./settings-write-queue.js";
65+
readFileWithRetry,
66+
resolvePluginConfigSavePathKey,
67+
warnPersistFailure,
68+
} from "./settings-persist-utils.js";
69+
import { withQueuedRetry } from "./settings-write-queue.js";
7170
import { promptStatuslineSettingsPanel } from "./statusline-settings-panel.js";
7271
import { promptThemeSettingsPanel } from "./theme-settings-panel.js";
7372

@@ -267,22 +266,6 @@ function mergeDashboardSettingsForKeys(
267266
return cloneDashboardSettings(next);
268267
}
269268

270-
function resolvePluginConfigSavePathKey(): string {
271-
const envPath = (process.env.CODEX_MULTI_AUTH_CONFIG_PATH ?? "").trim();
272-
return envPath.length > 0 ? envPath : getUnifiedSettingsPath();
273-
}
274-
275-
function formatPersistError(error: unknown): string {
276-
if (error instanceof Error) return error.message;
277-
return String(error);
278-
}
279-
280-
function warnPersistFailure(scope: string, error: unknown): void {
281-
console.warn(
282-
`Settings save failed (${scope}) after retries: ${formatPersistError(error)}`,
283-
);
284-
}
285-
286269
async function persistDashboardSettingsSelection(
287270
selected: DashboardDisplaySettings,
288271
keys: readonly DashboardSettingKey[],
@@ -308,27 +291,6 @@ async function persistDashboardSettingsSelection(
308291
}
309292
}
310293

311-
async function readFileWithRetry(path: string): Promise<string> {
312-
for (let attempt = 0; ; attempt += 1) {
313-
try {
314-
return await fs.readFile(path, "utf-8");
315-
} catch (error) {
316-
const code = (error as NodeJS.ErrnoException).code;
317-
if (code === "ENOENT") {
318-
throw error;
319-
}
320-
if (
321-
!code ||
322-
!RETRYABLE_SETTINGS_WRITE_CODES.has(code) ||
323-
attempt >= SETTINGS_WRITE_MAX_ATTEMPTS - 1
324-
) {
325-
throw error;
326-
}
327-
await sleep(25 * 2 ** attempt);
328-
}
329-
}
330-
}
331-
332294
async function persistBackendConfigSelection(
333295
selected: PluginConfig,
334296
scope: string,
@@ -1177,7 +1139,17 @@ async function loadExperimentalSyncTarget(): Promise<
11771139
}
11781140
try {
11791141
const raw = JSON.parse(
1180-
await readFileWithRetry(detection.descriptor.accountPath),
1142+
await readFileWithRetry(detection.descriptor.accountPath, {
1143+
retryableCodes: new Set([
1144+
"EBUSY",
1145+
"EPERM",
1146+
"EAGAIN",
1147+
"ENOTEMPTY",
1148+
"EACCES",
1149+
]),
1150+
maxAttempts: 4,
1151+
sleep,
1152+
}),
11811153
);
11821154
const normalized = normalizeAccountStorage(raw);
11831155
if (!normalized) {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { promises as fs } from "node:fs";
2+
import { getUnifiedSettingsPath } from "../unified-settings.js";
3+
4+
export function resolvePluginConfigSavePathKey(): string {
5+
const envPath = (process.env.CODEX_MULTI_AUTH_CONFIG_PATH ?? "").trim();
6+
return envPath.length > 0 ? envPath : getUnifiedSettingsPath();
7+
}
8+
9+
export function formatPersistError(error: unknown): string {
10+
if (error instanceof Error) return error.message;
11+
return String(error);
12+
}
13+
14+
export function warnPersistFailure(scope: string, error: unknown): void {
15+
console.warn(
16+
`Settings save failed (${scope}) after retries: ${formatPersistError(error)}`,
17+
);
18+
}
19+
20+
export async function readFileWithRetry(
21+
path: string,
22+
deps: {
23+
retryableCodes: ReadonlySet<string>;
24+
maxAttempts: number;
25+
sleep: (ms: number) => Promise<void>;
26+
},
27+
): Promise<string> {
28+
for (let attempt = 0; ; attempt += 1) {
29+
try {
30+
return await fs.readFile(path, "utf-8");
31+
} catch (error) {
32+
const code = (error as NodeJS.ErrnoException).code;
33+
if (code === "ENOENT") throw error;
34+
if (
35+
!code ||
36+
!deps.retryableCodes.has(code) ||
37+
attempt >= deps.maxAttempts - 1
38+
) {
39+
throw error;
40+
}
41+
await deps.sleep(25 * 2 ** attempt);
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)