Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 15 additions & 33 deletions lib/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ export {
} from "./storage/identity.js";

import { normalizeEmailKey } from "./storage/identity.js";
import {
ACCOUNTS_BACKUP_SUFFIX,
ACCOUNTS_WAL_SUFFIX,
getFlaggedAccountsPath as buildFlaggedAccountsPath,
getAccountsBackupPath,
getAccountsBackupRecoveryCandidates,
getAccountsWalPath,
getIntentionalResetMarkerPath,
RESET_MARKER_SUFFIX,
} from "./storage/file-paths.js";
import {
type AccountMetadataV1,
type AccountMetadataV3,
Expand Down Expand Up @@ -54,12 +64,8 @@ const log = createLogger("storage");
const ACCOUNTS_FILE_NAME = "openai-codex-accounts.json";
const FLAGGED_ACCOUNTS_FILE_NAME = "openai-codex-flagged-accounts.json";
const LEGACY_FLAGGED_ACCOUNTS_FILE_NAME = "openai-codex-blocked-accounts.json";
const ACCOUNTS_BACKUP_SUFFIX = ".bak";
const ACCOUNTS_WAL_SUFFIX = ".wal";
const ACCOUNTS_BACKUP_HISTORY_DEPTH = 3;
const BACKUP_COPY_MAX_ATTEMPTS = 5;
const BACKUP_COPY_BASE_DELAY_MS = 10;
const RESET_MARKER_SUFFIX = ".reset-intent";
let storageBackupEnabled = true;
let lastAccountsSaveTimestamp = 0;

Expand Down Expand Up @@ -367,25 +373,6 @@ export function setStorageBackupEnabled(enabled: boolean): void {
storageBackupEnabled = enabled;
}

function getAccountsBackupPath(path: string): string {
return `${path}${ACCOUNTS_BACKUP_SUFFIX}`;
}

function getAccountsBackupPathAtIndex(path: string, index: number): string {
if (index <= 0) {
return getAccountsBackupPath(path);
}
return `${path}${ACCOUNTS_BACKUP_SUFFIX}.${index}`;
}

function getAccountsBackupRecoveryCandidates(path: string): string[] {
const candidates: string[] = [];
for (let i = 0; i < ACCOUNTS_BACKUP_HISTORY_DEPTH; i += 1) {
candidates.push(getAccountsBackupPathAtIndex(path, i));
}
return candidates;
}

async function getAccountsBackupRecoveryCandidatesWithDiscovery(
path: string,
): Promise<string[]> {
Expand Down Expand Up @@ -425,10 +412,6 @@ async function getAccountsBackupRecoveryCandidatesWithDiscovery(
return [...knownCandidates, ...discoveredOrdered];
}

function getAccountsWalPath(path: string): string {
return `${path}${ACCOUNTS_WAL_SUFFIX}`;
}

async function copyFileWithRetry(
sourcePath: string,
destinationPath: string,
Expand Down Expand Up @@ -605,10 +588,6 @@ function computeSha256(value: string): string {
return createHash("sha256").update(value).digest("hex");
}

function getIntentionalResetMarkerPath(path: string): string {
return `${path}${RESET_MARKER_SUFFIX}`;
}

function createEmptyStorageWithMetadata(
restoreEligible: boolean,
restoreReason: RestoreReason,
Expand Down Expand Up @@ -996,11 +975,14 @@ export async function exportNamedBackup(
}

export function getFlaggedAccountsPath(): string {
return join(dirname(getStoragePath()), FLAGGED_ACCOUNTS_FILE_NAME);
return buildFlaggedAccountsPath(getStoragePath(), FLAGGED_ACCOUNTS_FILE_NAME);
}

function getLegacyFlaggedAccountsPath(): string {
return join(dirname(getStoragePath()), LEGACY_FLAGGED_ACCOUNTS_FILE_NAME);
return buildFlaggedAccountsPath(
getStoragePath(),
LEGACY_FLAGGED_ACCOUNTS_FILE_NAME,
);
}

async function migrateLegacyProjectStorageIfNeeded(
Expand Down
42 changes: 42 additions & 0 deletions lib/storage/file-paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { dirname, join } from "node:path";

export const ACCOUNTS_BACKUP_SUFFIX = ".bak";
export const ACCOUNTS_WAL_SUFFIX = ".wal";
const ACCOUNTS_BACKUP_HISTORY_DEPTH = 3;
export const RESET_MARKER_SUFFIX = ".reset-intent";

export function getAccountsBackupPath(path: string): string {
return `${path}${ACCOUNTS_BACKUP_SUFFIX}`;
}

function getAccountsBackupPathAtIndex(path: string, index: number): string {
if (index <= 0) return getAccountsBackupPath(path);
return `${path}${ACCOUNTS_BACKUP_SUFFIX}.${index}`;
}

export function getAccountsBackupRecoveryCandidates(path: string): string[] {
const candidates: string[] = [];
for (let i = 0; i < ACCOUNTS_BACKUP_HISTORY_DEPTH; i += 1) {
candidates.push(getAccountsBackupPathAtIndex(path, i));
}
return candidates;
}

export function getAccountsWalPath(path: string): string {
return `${path}${ACCOUNTS_WAL_SUFFIX}`;
}

export function getIntentionalResetMarkerPath(path: string): string {
return `${path}${RESET_MARKER_SUFFIX}`;
}

export function getFlaggedAccountsPath(
storagePath: string,
fileName: string,
): string {
return buildSiblingStoragePath(storagePath, fileName);
}

function buildSiblingStoragePath(storagePath: string, fileName: string): string {
return join(dirname(storagePath), fileName);
}
56 changes: 56 additions & 0 deletions test/storage-file-paths.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { dirname, join } from "node:path";
import { describe, expect, it } from "vitest";
import {
ACCOUNTS_BACKUP_SUFFIX,
ACCOUNTS_WAL_SUFFIX,
getAccountsBackupPath,
getAccountsBackupRecoveryCandidates,
getAccountsWalPath,
getFlaggedAccountsPath,
getIntentionalResetMarkerPath,
RESET_MARKER_SUFFIX,
} from "../lib/storage/file-paths.js";

describe("storage file paths", () => {
it("builds the primary backup, wal, and reset marker paths", () => {
const storagePath = "/tmp/openai-codex-accounts.json";

expect(getAccountsBackupPath(storagePath)).toBe(
`${storagePath}${ACCOUNTS_BACKUP_SUFFIX}`,
);
expect(getAccountsWalPath(storagePath)).toBe(
`${storagePath}${ACCOUNTS_WAL_SUFFIX}`,
);
expect(getIntentionalResetMarkerPath(storagePath)).toBe(
`${storagePath}${RESET_MARKER_SUFFIX}`,
);
});

it("returns backup recovery candidates for the base backup and history slots", () => {
const storagePath = "/tmp/openai-codex-accounts.json";

expect(getAccountsBackupRecoveryCandidates(storagePath)).toEqual([
`${storagePath}.bak`,
`${storagePath}.bak.1`,
`${storagePath}.bak.2`,
]);
});

it("builds flagged storage paths next to the active storage file", () => {
const storagePath = "/tmp/config/openai-codex-accounts.json";
const fileName = "openai-codex-flagged-accounts.json";

expect(getFlaggedAccountsPath(storagePath, fileName)).toBe(
join(dirname(storagePath), fileName),
);
});

it("uses dirname/join semantics consistently for windows-like storage paths", () => {
const storagePath = String.raw`C:\Users\user\.codex\openai-codex-accounts.json`;
const fileName = "openai-codex-blocked-accounts.json";

expect(getFlaggedAccountsPath(storagePath, fileName)).toBe(
join(dirname(storagePath), fileName),
);
});
});