From 4596266b555dca589aceb036a2db7b27afd5e960 Mon Sep 17 00:00:00 2001 From: Mochammad Fadhlan Al-Ghiffari Date: Sat, 23 May 2026 08:19:25 +0700 Subject: [PATCH] fix: add offline advisory db sync hint --- src/index.ts | 6 ++++- tests/cli-integration.test.ts | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index d01afde..e3ac4c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -150,7 +150,11 @@ if (parsedArgs) { advisorySource.cleanup(); } catch (error) { if (options.offline || options.offlineDb) { - throw new Error(`Offline advisory database is not available: ${error instanceof Error ? error.message : String(error)}`); + const reason = error instanceof Error ? error.message : String(error); + const syncHint = options.offlineDb + ? `To build it, run: cve-lite advisories sync\nOr to save it to the requested path: cve-lite advisories sync --output ${options.offlineDb}` + : "To build it, run: cve-lite advisories sync"; + throw new Error(`Offline advisory database is not available: ${reason}\n${syncHint}`); } throw error; } diff --git a/tests/cli-integration.test.ts b/tests/cli-integration.test.ts index d8115b9..704ac7e 100644 --- a/tests/cli-integration.test.ts +++ b/tests/cli-integration.test.ts @@ -522,6 +522,57 @@ describe("CLI integration", () => { expect(stripAnsi(result.stdout[2] ?? "")).toContain("Advisory DB freshness: synced"); }); + it("prints the advisories sync hint when the offline DB cannot be opened", async () => { + const createAdvisorySourceMock = (await import("../src/scanner.js")).createAdvisorySource as jest.Mock; + createAdvisorySourceMock.mockImplementationOnce(() => { + throw new Error("file does not exist"); + }); + + parseArgsMock.mockReturnValue({ + command: "scan", + options: { + offline: true, + failOn: "critical", + batchSize: "100", + searchDepth: "4", + minSeverity: "medium", + }, + projectArg: ".", + }); + + const result = await runIndexModule(); + + expect(result.exitCode).toBe(1); + expect(stripAnsi(result.stderr.join("\n"))).toContain("Offline advisory database is not available: file does not exist"); + expect(stripAnsi(result.stderr.join("\n"))).toContain("To build it, run: cve-lite advisories sync"); + }); + + it("prints the requested output path when a custom offline DB cannot be opened", async () => { + const createAdvisorySourceMock = (await import("../src/scanner.js")).createAdvisorySource as jest.Mock; + createAdvisorySourceMock.mockImplementationOnce(() => { + throw new Error("permission denied"); + }); + + parseArgsMock.mockReturnValue({ + command: "scan", + options: { + offlineDb: "/tmp/custom-advisories.db", + failOn: "critical", + batchSize: "100", + searchDepth: "4", + minSeverity: "medium", + }, + projectArg: ".", + }); + + const result = await runIndexModule(); + + expect(result.exitCode).toBe(1); + expect(stripAnsi(result.stderr.join("\n"))).toContain("Offline advisory database is not available: permission denied"); + expect(stripAnsi(result.stderr.join("\n"))).toContain("To build it, run: cve-lite advisories sync"); + expect(stripAnsi(result.stderr.join("\n"))).toContain("cve-lite advisories sync --output /tmp/custom-advisories.db"); + }); + it("warns when the local advisory DB appears stale", async () => { const createAdvisorySourceMock = (await import("../src/scanner.js")).createAdvisorySource as jest.Mock; createAdvisorySourceMock.mockReturnValueOnce({