Skip to content

Commit f235057

Browse files
committed
fix: surface silent update-check parse failures
The update checker silently swallowed invalid package metadata and cached JSON parse failures. Keep the same fallback behavior, but log those cases at debug level so malformed local state is observable during diagnosis.
1 parent 0964138 commit f235057

2 files changed

Lines changed: 67 additions & 2 deletions

File tree

lib/auto-update-checker.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ function getCurrentVersion(): string {
4242
const packageJsonPath = join(import.meta.dirname ?? __dirname, "..", "package.json");
4343
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as { version: string };
4444
return packageJson.version;
45-
} catch {
45+
} catch (error) {
46+
log.debug("Failed to read current package version", {
47+
error: error instanceof Error ? error.message : String(error),
48+
});
4649
return "0.0.0";
4750
}
4851
}
@@ -52,7 +55,10 @@ function loadCache(): UpdateCheckCache | null {
5255
if (!existsSync(CACHE_FILE)) return null;
5356
const content = readFileSync(CACHE_FILE, "utf8");
5457
return JSON.parse(content) as UpdateCheckCache;
55-
} catch {
58+
} catch (error) {
59+
log.debug("Failed to load update cache", {
60+
error: error instanceof Error ? error.message : String(error),
61+
});
5662
return null;
5763
}
5864
}

test/auto-update-checker.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ describe("auto-update-checker", () => {
1414
let checkForUpdates: typeof import("../lib/auto-update-checker.js").checkForUpdates;
1515
let checkAndNotify: typeof import("../lib/auto-update-checker.js").checkAndNotify;
1616
let clearUpdateCache: typeof import("../lib/auto-update-checker.js").clearUpdateCache;
17+
let logger: {
18+
debug: ReturnType<typeof vi.fn>;
19+
info: ReturnType<typeof vi.fn>;
20+
warn: ReturnType<typeof vi.fn>;
21+
};
1722

1823
const mockPackageJson = { version: "4.12.0" };
1924

@@ -22,6 +27,14 @@ describe("auto-update-checker", () => {
2227
vi.useFakeTimers();
2328
vi.setSystemTime(new Date("2026-01-30T12:00:00Z"));
2429
mockPackageJson.version = "4.12.0";
30+
logger = {
31+
debug: vi.fn(),
32+
info: vi.fn(),
33+
warn: vi.fn(),
34+
};
35+
vi.doMock("../lib/logger.js", () => ({
36+
createLogger: () => logger,
37+
}));
2538

2639
fs = await import("node:fs");
2740
vi.mocked(fs.readFileSync).mockImplementation((path: unknown) => {
@@ -228,6 +241,27 @@ describe("auto-update-checker", () => {
228241
});
229242

230243
describe("checkForUpdates", () => {
244+
it("logs debug details when package metadata cannot be parsed", async () => {
245+
vi.mocked(fs.readFileSync).mockImplementation((path: unknown) => {
246+
if (String(path).includes("package.json")) {
247+
return "{";
248+
}
249+
throw new Error("File not found");
250+
});
251+
vi.mocked(globalThis.fetch).mockResolvedValue({
252+
ok: true,
253+
json: async () => ({ version: "5.0.0" }),
254+
} as Response);
255+
256+
const result = await checkForUpdates(true);
257+
258+
expect(result.currentVersion).toBe("0.0.0");
259+
expect(logger.debug).toHaveBeenCalledWith(
260+
"Failed to read current package version",
261+
expect.objectContaining({ error: expect.any(String) }),
262+
);
263+
});
264+
231265
it("uses cache when check is recent", async () => {
232266
const cacheData = {
233267
lastCheck: Date.now() - 1000 * 60 * 60,
@@ -252,6 +286,31 @@ describe("auto-update-checker", () => {
252286
expect(result.latestVersion).toBe("5.0.0");
253287
});
254288

289+
it("logs debug details when cached update JSON is unreadable", async () => {
290+
vi.mocked(fs.existsSync).mockReturnValue(true);
291+
vi.mocked(fs.readFileSync).mockImplementation((path: unknown) => {
292+
if (String(path).includes("package.json")) {
293+
return JSON.stringify(mockPackageJson);
294+
}
295+
if (String(path).includes("update-check-cache.json")) {
296+
return "{";
297+
}
298+
throw new Error("File not found");
299+
});
300+
vi.mocked(globalThis.fetch).mockResolvedValue({
301+
ok: true,
302+
json: async () => ({ version: "5.0.0" }),
303+
} as Response);
304+
305+
await checkForUpdates();
306+
307+
expect(logger.debug).toHaveBeenCalledWith(
308+
"Failed to load update cache",
309+
expect.objectContaining({ error: expect.any(String) }),
310+
);
311+
expect(globalThis.fetch).toHaveBeenCalled();
312+
});
313+
255314
it("handles cached null latestVersion without update", async () => {
256315
const cacheData = {
257316
lastCheck: Date.now() - 1000 * 60 * 60,

0 commit comments

Comments
 (0)