From a8cdb06d5c52a2f9626ac159881449dd1b4e54ef Mon Sep 17 00:00:00 2001 From: Anthony GAEREMYNCK <1@anthony.sh> Date: Sat, 1 Aug 2026 12:55:56 +0200 Subject: [PATCH] feat(archive): add MIME types and compound-extension aliases for archive formats Co-Authored-By: Claude Sonnet 5 --- src/mime.js | 9 +++++++++ test/mime.test.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/mime.js b/src/mime.js index 690b92d..c145648 100644 --- a/src/mime.js +++ b/src/mime.js @@ -31,6 +31,12 @@ const OUTPUT_MIME_TYPES = { snb: 'application/octet-stream', tcr: 'application/octet-stream', azw3: 'application/vnd.amazon.ebook', + zip: 'application/zip', + tar: 'application/x-tar', + 'tar.gz': 'application/gzip', + 'tar.bz2': 'application/x-bzip2', + '7z': 'application/x-7z-compressed', + 'tar.7z': 'application/x-7z-compressed', }; export async function detectInputMime(filePath) { @@ -67,6 +73,9 @@ function normalizeFormat(format) { if (format === 'heif') return 'heic'; if (format === 'azw3') return 'mobi'; if (format === 'fb2') return 'xml'; + if (format === 'tar.gz') return 'gz'; + if (format === 'tar.bz2') return 'bz2'; + if (format === 'tar.7z') return '7z'; return format; } diff --git a/test/mime.test.js b/test/mime.test.js index b867c4e..1a2e07c 100644 --- a/test/mime.test.js +++ b/test/mime.test.js @@ -242,3 +242,38 @@ describe('resolveInputFormat — dfont', () => { await fs.unlink(fixturePath); }); }); + +describe('outputMimeType — archives', () => { + it('returns the correct MIME type for each archive target format', () => { + expect(outputMimeType('zip')).toBe('application/zip'); + expect(outputMimeType('tar')).toBe('application/x-tar'); + expect(outputMimeType('tar.gz')).toBe('application/gzip'); + expect(outputMimeType('tar.bz2')).toBe('application/x-bzip2'); + expect(outputMimeType('7z')).toBe('application/x-7z-compressed'); + expect(outputMimeType('tar.7z')).toBe('application/x-7z-compressed'); + }); +}); + +describe('resolveInputFormat — compound archive extensions', () => { + it('accepts a real gzip file declared as tar.gz (file-type sniffs the outer gzip layer only)', async () => { + const zlib = await import('node:zlib'); + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.tar.gz'); + await fs.writeFile(fixturePath, zlib.gzipSync(Buffer.from('irrelevant payload for this check'))); + + const result = await resolveInputFormat(fixturePath, 'tar.gz'); + expect(result.valid).toBe(true); + + await fs.unlink(fixturePath); + }); + + it('accepts a real 7z file declared as tar.7z (file-type sniffs the outer 7z layer only)', async () => { + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.tar.7z'); + // Minimal valid 7z signature header (6-byte magic + 2-byte version), enough for file-type to sniff `ext: '7z'`. + await fs.writeFile(fixturePath, Buffer.from([0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c, 0x00, 0x04])); + + const result = await resolveInputFormat(fixturePath, 'tar.7z'); + expect(result.valid).toBe(true); + + await fs.unlink(fixturePath); + }); +});