From 313f3adfd20fb762e0f73eeecb8460042c544a54 Mon Sep 17 00:00:00 2001 From: qorexdev Date: Wed, 8 Apr 2026 04:46:12 +0500 Subject: [PATCH] fix: replace empty catch blocks with meaningful error handling - token-storage: warn to stderr when stored token is corrupted instead of silently returning null - file-service: distinguish ENOENT from permission errors in upload - auth, documents, embed-parser, identifier: add explanatory comments to intentional try-parse catch blocks Closes #60 --- src/commands/auth.ts | 5 ++++- src/commands/documents.ts | 1 + src/common/embed-parser.ts | 2 ++ src/common/identifier.ts | 1 + src/common/token-storage.ts | 13 +++++++++++-- src/services/file-service.ts | 14 +++++++++++--- tests/unit/services/file-service.test.ts | 5 ++++- 7 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/commands/auth.ts b/src/commands/auth.ts index 1ed3acb..1840288 100644 --- a/src/commands/auth.ts +++ b/src/commands/auth.ts @@ -140,7 +140,7 @@ export function setupAuthCommands(program: Command): void { ); } } catch { - // No token found anywhere, proceed with login + // resolveApiToken throws when no token exists in any source — expected for fresh installs } } @@ -211,6 +211,7 @@ export function setupAuthCommands(program: Command): void { token = resolved.token; source = resolved.source; } catch { + // resolveApiToken throws when no token is configured — not an error, just unauthenticated outputSuccess({ authenticated: false, message: @@ -227,6 +228,7 @@ export function setupAuthCommands(program: Command): void { user: { id: viewer.id, name: viewer.name, email: viewer.email }, }); } catch { + // validateApiToken throws on invalid/expired/revoked tokens outputSuccess({ authenticated: false, source: SOURCE_LABELS[source], @@ -255,6 +257,7 @@ export function setupAuthCommands(program: Command): void { warning: `A token is still active via ${SOURCE_LABELS[source]}.`, }); } catch { + // no other token source active — clean logout outputSuccess({ message: "Authentication token removed." }); } }), diff --git a/src/commands/documents.ts b/src/commands/documents.ts index fcf4526..b1247a6 100644 --- a/src/commands/documents.ts +++ b/src/commands/documents.ts @@ -66,6 +66,7 @@ export function extractDocumentIdFromUrl(url: string): string | null { return docSlug.substring(lastHyphenIndex + 1) || null; } catch { + // malformed URL → not a valid document link return null; } } diff --git a/src/common/embed-parser.ts b/src/common/embed-parser.ts index e94a9fe..4ec218f 100644 --- a/src/common/embed-parser.ts +++ b/src/common/embed-parser.ts @@ -60,6 +60,7 @@ export function isLinearUploadUrl(url: string): boolean { const urlObj = new URL(url); return urlObj.hostname === "uploads.linear.app"; } catch { + // malformed URL → not a linear upload URL return false; } } @@ -69,6 +70,7 @@ export function extractFilenameFromUrl(url: string): string { const parts = new URL(url).pathname.split("/"); return parts[parts.length - 1] || "download"; } catch { + // malformed URL → fall back to generic filename return "download"; } } diff --git a/src/common/identifier.ts b/src/common/identifier.ts index e2614df..225f459 100644 --- a/src/common/identifier.ts +++ b/src/common/identifier.ts @@ -36,6 +36,7 @@ export function tryParseIssueIdentifier( try { return parseIssueIdentifier(identifier); } catch { + // parseIssueIdentifier throws on malformed identifiers — expected for non-issue inputs return null; } } diff --git a/src/common/token-storage.ts b/src/common/token-storage.ts index d08b88b..369ed1f 100644 --- a/src/common/token-storage.ts +++ b/src/common/token-storage.ts @@ -57,7 +57,12 @@ export function getStoredToken(): string | null { try { const encrypted = fs.readFileSync(legacy, "utf8").trim(); return decryptToken(encrypted); - } catch { + } catch (err) { + // file exists but can't be decrypted — warn instead of silently returning null + const detail = err instanceof Error ? err.message : String(err); + console.error( + `Warning: stored token at ${legacy} is corrupted: ${detail}`, + ); return null; } } @@ -67,7 +72,11 @@ export function getStoredToken(): string | null { try { const encrypted = fs.readFileSync(tokenPath, "utf8").trim(); return decryptToken(encrypted); - } catch { + } catch (err) { + const detail = err instanceof Error ? err.message : String(err); + console.error( + `Warning: stored token at ${tokenPath} is corrupted: ${detail}`, + ); return null; } } diff --git a/src/services/file-service.ts b/src/services/file-service.ts index 94d1f88..ead189c 100644 --- a/src/services/file-service.ts +++ b/src/services/file-service.ts @@ -183,7 +183,7 @@ export class FileService { error: `File already exists: ${outputPath}. Use --overwrite to replace.`, }; } catch { - // File doesn't exist, we can proceed + // access() throws ENOENT when file doesn't exist — that's the expected path } } @@ -266,10 +266,18 @@ export class FileService { // Check if file exists try { await access(filePath); - } catch { + } catch (err) { + // access() fails with ENOENT for missing files, EACCES for permission issues + const detail = + err instanceof Error && "code" in err + ? (err as NodeJS.ErrnoException).code + : undefined; return { success: false, - error: `File not found: ${filePath}`, + error: + detail === "ENOENT" + ? `File not found: ${filePath}` + : `Cannot access file: ${filePath} (${detail || (err instanceof Error ? err.message : String(err))})`, }; } diff --git a/tests/unit/services/file-service.test.ts b/tests/unit/services/file-service.test.ts index 52916ec..502eb85 100644 --- a/tests/unit/services/file-service.test.ts +++ b/tests/unit/services/file-service.test.ts @@ -126,7 +126,10 @@ describe("downloadFile", () => { describe("uploadFile", () => { it("returns error when file not found", async () => { - vi.mocked(access).mockRejectedValue(new Error("ENOENT")); + const err = Object.assign(new Error("ENOENT: no such file or directory"), { + code: "ENOENT", + }); + vi.mocked(access).mockRejectedValue(err); const service = new FileService(TEST_TOKEN); const result = await service.uploadFile("/path/to/missing.png");