Skip to content

Commit 52be680

Browse files
committed
fix(storage): honor reappeared export storage
1 parent 16cd6c4 commit 52be680

2 files changed

Lines changed: 109 additions & 6 deletions

File tree

lib/storage.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,7 @@ async function migrateLegacyProjectStorageIfNeeded(options?: {
719719
if (!state.currentStoragePath) {
720720
return null;
721721
}
722+
const currentStoragePath = state.currentStoragePath;
722723

723724
const candidatePaths = [
724725
state.currentLegacyWorktreeStoragePath,
@@ -743,10 +744,8 @@ async function migrateLegacyProjectStorageIfNeeded(options?: {
743744
return null;
744745
}
745746

746-
let targetStorage = await loadNormalizedStorageFromPath(
747-
state.currentStoragePath,
748-
"current account storage",
749-
{
747+
const loadCurrentStorageForMigration = async (): Promise<AccountStorageV3 | null> =>
748+
loadNormalizedStorageFromPath(currentStoragePath, "current account storage", {
750749
loadAccountsFromPath: (path) =>
751750
loadAccountsFromPath(path, {
752751
normalizeAccountStorage,
@@ -755,11 +754,29 @@ async function migrateLegacyProjectStorageIfNeeded(options?: {
755754
logWarn: (message, details) => {
756755
log.warn(message, details);
757756
},
758-
},
759-
);
757+
});
758+
const readOnlyExportCurrentStorage = async (): Promise<{
759+
exists: boolean;
760+
storage: AccountStorageV3 | null;
761+
}> => {
762+
if (commit || !existsSync(currentStoragePath)) {
763+
return { exists: false, storage: null };
764+
}
765+
return {
766+
exists: true,
767+
storage: await loadCurrentStorageForMigration(),
768+
};
769+
};
770+
771+
let targetStorage = await loadCurrentStorageForMigration();
760772
let migrated = false;
761773

762774
for (const legacyPath of existingCandidatePaths) {
775+
const liveCurrentStorageBeforeMerge = await readOnlyExportCurrentStorage();
776+
if (liveCurrentStorageBeforeMerge.exists) {
777+
return liveCurrentStorageBeforeMerge.storage;
778+
}
779+
763780
const legacyStorage = await loadNormalizedStorageFromPath(
764781
legacyPath,
765782
"legacy account storage",
@@ -778,6 +795,12 @@ async function migrateLegacyProjectStorageIfNeeded(options?: {
778795
continue;
779796
}
780797

798+
const liveCurrentStorageAfterLegacyRead =
799+
await readOnlyExportCurrentStorage();
800+
if (liveCurrentStorageAfterLegacyRead.exists) {
801+
return liveCurrentStorageAfterLegacyRead.storage;
802+
}
803+
781804
const mergedStorage = mergeStorageForMigration(
782805
targetStorage,
783806
legacyStorage,

test/storage.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,6 +1672,86 @@ describe("storage", () => {
16721672
}
16731673
});
16741674

1675+
it("does not revive legacy accounts when the current storage reappears before export merges legacy storage", async () => {
1676+
const currentStoragePath = join(
1677+
testWorkDir,
1678+
"accounts-reappeared-current.json",
1679+
);
1680+
const legacyStoragePath = join(
1681+
testWorkDir,
1682+
"accounts-reappeared-legacy.json",
1683+
);
1684+
await fs.writeFile(
1685+
legacyStoragePath,
1686+
JSON.stringify({
1687+
version: 3,
1688+
activeIndex: 0,
1689+
activeIndexByFamily: {},
1690+
accounts: [
1691+
{
1692+
accountId: "legacy",
1693+
refreshToken: "legacy-token",
1694+
addedAt: 1,
1695+
lastUsed: 1,
1696+
},
1697+
],
1698+
}),
1699+
);
1700+
1701+
const actualStorageParser = await vi.importActual<
1702+
typeof import("../lib/storage/storage-parser.js")
1703+
>("../lib/storage/storage-parser.js");
1704+
let recreateCurrentStorage = true;
1705+
vi.resetModules();
1706+
vi.doMock("../lib/storage/storage-parser.js", () => ({
1707+
...actualStorageParser,
1708+
loadAccountsFromPath: vi.fn(async (path, deps) => {
1709+
if (path === currentStoragePath && recreateCurrentStorage) {
1710+
recreateCurrentStorage = false;
1711+
await fs.writeFile(
1712+
currentStoragePath,
1713+
JSON.stringify({
1714+
version: 3,
1715+
activeIndex: 0,
1716+
activeIndexByFamily: {},
1717+
accounts: [],
1718+
}),
1719+
);
1720+
throw Object.assign(new Error("missing current storage"), {
1721+
code: "ENOENT",
1722+
});
1723+
}
1724+
return actualStorageParser.loadAccountsFromPath(path, deps);
1725+
}),
1726+
}));
1727+
1728+
try {
1729+
const isolatedStorageModule = await import("../lib/storage.js");
1730+
const isolatedPathState = await import("../lib/storage/path-state.js");
1731+
isolatedPathState.setStoragePathState({
1732+
currentStoragePath,
1733+
currentLegacyProjectStoragePath: legacyStoragePath,
1734+
currentLegacyWorktreeStoragePath: null,
1735+
currentProjectRoot: null,
1736+
});
1737+
1738+
await expect(
1739+
isolatedStorageModule.exportAccounts(exportPath),
1740+
).rejects.toThrow(/No accounts to export/);
1741+
1742+
const currentStorage = JSON.parse(
1743+
await fs.readFile(currentStoragePath, "utf-8"),
1744+
);
1745+
expect(currentStorage.accounts).toEqual([]);
1746+
expect(existsSync(legacyStoragePath)).toBe(true);
1747+
expect(existsSync(exportPath)).toBe(false);
1748+
} finally {
1749+
vi.doUnmock("../lib/storage/storage-parser.js");
1750+
vi.resetModules();
1751+
setStoragePathDirect(testStoragePath);
1752+
}
1753+
});
1754+
16751755
it("does not revive legacy accounts when the current storage has an intentional reset marker", async () => {
16761756
const currentStoragePath = join(testWorkDir, "accounts-reset-current.json");
16771757
const legacyStoragePath = join(testWorkDir, "accounts-reset-legacy.json");

0 commit comments

Comments
 (0)