From e98249a624d4c1950a2f824cbcc053b4fdbbec5c Mon Sep 17 00:00:00 2001 From: Tony Guan Date: Wed, 29 Jul 2026 16:07:32 -0700 Subject: [PATCH] fix(files): display zero-byte file sizes (#3608) --- src/commands/files.ts | 12 ++++++++++-- test/files.test.ts | 21 ++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/commands/files.ts b/src/commands/files.ts index d38bd5dcf..391644699 100644 --- a/src/commands/files.ts +++ b/src/commands/files.ts @@ -16,7 +16,7 @@ interface FileRecord { filename: string; storage_path: string; mime_type: string | null; - size_bytes: number; + size_bytes: number | bigint | string | null; content_hash: string; metadata: Record; created_at: string; @@ -42,6 +42,14 @@ function fileHash(filePath: string): string { return createHash('sha256').update(content).digest('hex'); } +export function formatFileSizeKb(rawSizeBytes: number | bigint | string | null): string { + if (rawSizeBytes == null) return '?'; + const sizeBytes = Number(rawSizeBytes); + return Number.isFinite(sizeBytes) && sizeBytes >= 0 + ? `${Math.round(sizeBytes / 1024)}KB` + : '?'; +} + export async function runFiles(engine: BrainEngine, args: string[]) { const subcommand = args[0]; @@ -116,7 +124,7 @@ async function listFiles(engine: BrainEngine, slug?: string) { console.log(`${rows.length} file(s):`); for (const row of rows) { - const size = row.size_bytes ? `${Math.round(Number(row.size_bytes) / 1024)}KB` : '?'; + const size = formatFileSizeKb(row.size_bytes as FileRecord['size_bytes']); console.log(` ${row.page_slug || '(unlinked)'} / ${row.filename} [${size}, ${row.mime_type || '?'}]`); } } diff --git a/test/files.test.ts b/test/files.test.ts index 8d8a7e58a..897c5f411 100644 --- a/test/files.test.ts +++ b/test/files.test.ts @@ -4,7 +4,7 @@ import { join, basename } from 'path'; import { createHash } from 'crypto'; import { extname } from 'path'; import { tmpdir } from 'os'; -import { collectFiles } from '../src/commands/files.ts'; +import { collectFiles, formatFileSizeKb } from '../src/commands/files.ts'; import { operationsByName } from '../src/core/operations.ts'; import * as db from '../src/core/db.ts'; @@ -51,6 +51,25 @@ afterAll(() => { rmSync(TMP, { recursive: true, force: true }); }); +describe('formatFileSizeKb', () => { + test('formats number, bigint, and string database values', () => { + expect(formatFileSizeKb(35 * 1024)).toBe('35KB'); + expect(formatFileSizeKb(35n * 1024n)).toBe('35KB'); + expect(formatFileSizeKb('35840')).toBe('35KB'); + }); + + test('preserves zero-byte files instead of reporting an unknown size', () => { + expect(formatFileSizeKb(0)).toBe('0KB'); + expect(formatFileSizeKb(0n)).toBe('0KB'); + }); + + test('reports missing or invalid sizes as unknown', () => { + expect(formatFileSizeKb(null)).toBe('?'); + expect(formatFileSizeKb('not-a-number')).toBe('?'); + expect(formatFileSizeKb(-1)).toBe('?'); + }); +}); + describe('getMimeType', () => { test('returns correct MIME for .jpg', () => { expect(getMimeType('photo.jpg')).toBe('image/jpeg');