Skip to content

Commit 05c201f

Browse files
committed
fix(storage): defer export legacy migration
1 parent 3756bd9 commit 05c201f

2 files changed

Lines changed: 125 additions & 6 deletions

File tree

lib/storage.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,11 +1450,9 @@ async function loadAccountsForExport(
14501450
const persistMigrations = mode === "locked";
14511451
const path = getStoragePath();
14521452
const resetMarkerPath = getIntentionalResetMarkerPath(path);
1453-
const migratedLegacyStorage = await migrateLegacyProjectStorageIfNeeded(
1454-
persistMigrations
1455-
? { persist: saveAccountsUnlocked }
1456-
: { commit: false },
1457-
);
1453+
const migrationOptions = persistMigrations
1454+
? { persist: saveAccountsUnlocked }
1455+
: { commit: false };
14581456

14591457
if (existsSync(resetMarkerPath)) {
14601458
return createEmptyStorageWithMetadata(false, "intentional-reset");
@@ -1496,6 +1494,11 @@ async function loadAccountsForExport(
14961494
return createEmptyStorageWithMetadata(false, "intentional-reset");
14971495
}
14981496
if (code === "ENOENT") {
1497+
const migratedLegacyStorage =
1498+
await migrateLegacyProjectStorageIfNeeded(migrationOptions);
1499+
if (existsSync(resetMarkerPath)) {
1500+
return createEmptyStorageWithMetadata(false, "intentional-reset");
1501+
}
14991502
return migratedLegacyStorage;
15001503
}
15011504
throw error;

test/storage.test.ts

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1234,7 +1234,6 @@ describe("storage", () => {
12341234
});
12351235

12361236
it("should fail export when no accounts exist", async () => {
1237-
const { exportAccounts } = await import("../lib/storage.js");
12381237
setStoragePathDirect(testStoragePath);
12391238
await clearAccounts();
12401239
await expect(exportAccounts(exportPath)).rejects.toThrow(
@@ -1302,6 +1301,22 @@ describe("storage", () => {
13021301
const transactionStoragePath = join(testWorkDir, "accounts-transaction.json");
13031302
const currentStoragePath = join(testWorkDir, "accounts-current.json");
13041303
const legacyStoragePath = join(testWorkDir, "accounts-legacy.json");
1304+
await fs.writeFile(
1305+
transactionStoragePath,
1306+
JSON.stringify({
1307+
version: 3,
1308+
activeIndex: 0,
1309+
activeIndexByFamily: {},
1310+
accounts: [
1311+
{
1312+
accountId: "transaction",
1313+
refreshToken: "transaction-token",
1314+
addedAt: 1,
1315+
lastUsed: 1,
1316+
},
1317+
],
1318+
}),
1319+
);
13051320
await fs.writeFile(
13061321
legacyStoragePath,
13071322
JSON.stringify({
@@ -1332,9 +1347,15 @@ describe("storage", () => {
13321347
});
13331348

13341349
const exported = JSON.parse(await fs.readFile(exportPath, "utf-8"));
1350+
const transactionStorage = JSON.parse(
1351+
await fs.readFile(transactionStoragePath, "utf-8"),
1352+
);
13351353
expect(exported.accounts).toEqual([
13361354
expect.objectContaining({ refreshToken: "legacy-token" }),
13371355
]);
1356+
expect(transactionStorage.accounts).toEqual([
1357+
expect.objectContaining({ refreshToken: "transaction-token" }),
1358+
]);
13381359
expect(existsSync(currentStoragePath)).toBe(false);
13391360
expect(existsSync(legacyStoragePath)).toBe(true);
13401361
} finally {
@@ -1435,6 +1456,101 @@ describe("storage", () => {
14351456
},
14361457
);
14371458

1459+
it("does not revive legacy accounts when the current storage exists but is empty", async () => {
1460+
const currentStoragePath = join(testWorkDir, "accounts-empty-current.json");
1461+
const legacyStoragePath = join(testWorkDir, "accounts-empty-legacy.json");
1462+
await fs.writeFile(
1463+
currentStoragePath,
1464+
JSON.stringify({
1465+
version: 3,
1466+
activeIndex: 0,
1467+
activeIndexByFamily: {},
1468+
accounts: [],
1469+
}),
1470+
);
1471+
await fs.writeFile(
1472+
legacyStoragePath,
1473+
JSON.stringify({
1474+
version: 3,
1475+
activeIndex: 0,
1476+
activeIndexByFamily: {},
1477+
accounts: [
1478+
{
1479+
accountId: "legacy",
1480+
refreshToken: "legacy-token",
1481+
addedAt: 1,
1482+
lastUsed: 1,
1483+
},
1484+
],
1485+
}),
1486+
);
1487+
1488+
setStoragePathDirect(currentStoragePath);
1489+
try {
1490+
setStoragePathState({
1491+
currentStoragePath,
1492+
currentLegacyProjectStoragePath: legacyStoragePath,
1493+
currentLegacyWorktreeStoragePath: null,
1494+
currentProjectRoot: null,
1495+
});
1496+
1497+
await expect(exportAccounts(exportPath)).rejects.toThrow(
1498+
/No accounts to export/,
1499+
);
1500+
1501+
const currentStorage = JSON.parse(
1502+
await fs.readFile(currentStoragePath, "utf-8"),
1503+
);
1504+
expect(currentStorage.accounts).toEqual([]);
1505+
expect(existsSync(legacyStoragePath)).toBe(true);
1506+
expect(existsSync(exportPath)).toBe(false);
1507+
} finally {
1508+
setStoragePathDirect(testStoragePath);
1509+
}
1510+
});
1511+
1512+
it("does not revive legacy accounts when the current storage has an intentional reset marker", async () => {
1513+
const currentStoragePath = join(testWorkDir, "accounts-reset-current.json");
1514+
const legacyStoragePath = join(testWorkDir, "accounts-reset-legacy.json");
1515+
await fs.writeFile(
1516+
legacyStoragePath,
1517+
JSON.stringify({
1518+
version: 3,
1519+
activeIndex: 0,
1520+
activeIndexByFamily: {},
1521+
accounts: [
1522+
{
1523+
accountId: "legacy",
1524+
refreshToken: "legacy-token",
1525+
addedAt: 1,
1526+
lastUsed: 1,
1527+
},
1528+
],
1529+
}),
1530+
);
1531+
1532+
setStoragePathDirect(currentStoragePath);
1533+
await clearAccounts();
1534+
try {
1535+
setStoragePathState({
1536+
currentStoragePath,
1537+
currentLegacyProjectStoragePath: legacyStoragePath,
1538+
currentLegacyWorktreeStoragePath: null,
1539+
currentProjectRoot: null,
1540+
});
1541+
1542+
await expect(exportAccounts(exportPath)).rejects.toThrow(
1543+
/No accounts to export/,
1544+
);
1545+
1546+
expect(existsSync(currentStoragePath)).toBe(false);
1547+
expect(existsSync(legacyStoragePath)).toBe(true);
1548+
expect(existsSync(exportPath)).toBe(false);
1549+
} finally {
1550+
setStoragePathDirect(testStoragePath);
1551+
}
1552+
});
1553+
14381554
it("should fail import when file does not exist", async () => {
14391555
const { importAccounts } = await import("../lib/storage.js");
14401556
const nonexistentPath = join(testWorkDir, "nonexistent-file.json");

0 commit comments

Comments
 (0)