From 86b891e49f01332a98c4f7e70548695f2009f14f Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Thu, 12 Mar 2026 02:39:33 +0900 Subject: [PATCH 1/3] fix: add `contentType` key to `/api/registry/file/...` response --- server/api/registry/file/[...pkg].get.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/api/registry/file/[...pkg].get.ts b/server/api/registry/file/[...pkg].get.ts index c1dd76c1b3..e3d0c37d2d 100644 --- a/server/api/registry/file/[...pkg].get.ts +++ b/server/api/registry/file/[...pkg].get.ts @@ -50,7 +50,7 @@ async function fetchFileContent( packageName: string, version: string, filePath: string, -): Promise { +): Promise<{ content: string; contentType: string | null }> { const url = `https://cdn.jsdelivr.net/npm/${packageName}@${version}/${filePath}` const response = await fetch(url) @@ -64,6 +64,8 @@ async function fetchFileContent( }) } + const contentType = response.headers.get('content-type') + // Check content-length header if available const contentLength = response.headers.get('content-length') if (contentLength && parseInt(contentLength, 10) > MAX_FILE_SIZE) { @@ -83,7 +85,7 @@ async function fetchFileContent( }) } - return content + return { content, contentType } } /** @@ -123,7 +125,7 @@ export default defineCachedEventHandler( filePath: rawFilePath, }) - const content = await fetchFileContent(packageName, version, filePath) + const { content, contentType } = await fetchFileContent(packageName, version, filePath) const language = getLanguageFromPath(filePath) // For JS/TS files, resolve dependency versions and relative imports for linking @@ -185,6 +187,7 @@ export default defineCachedEventHandler( version, path: filePath, language, + contentType, content, html, lines: content.split('\n').length, From e0629de11d852e79f45be02602de656b11187c75 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Sat, 14 Mar 2026 01:11:47 +0900 Subject: [PATCH 2/3] fix: update `PackageFileContentResponse` type --- shared/types/npm-registry.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/types/npm-registry.ts b/shared/types/npm-registry.ts index 250a9218e6..629c9fbc20 100644 --- a/shared/types/npm-registry.ts +++ b/shared/types/npm-registry.ts @@ -381,6 +381,7 @@ export interface PackageFileContentResponse { version: string path: string language: string + contentType: string | null content: string html: string lines: number From c7914ebd0a36f874513429efbe0f817be64255a2 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Sat, 14 Mar 2026 01:13:03 +0900 Subject: [PATCH 3/3] feat: replace binary file estimation logic using content-type --- .../v/[version]/[...filePath].vue | 24 +++-- app/utils/file-types.ts | 96 ++++++------------- i18n/locales/en.json | 2 +- 3 files changed, 43 insertions(+), 79 deletions(-) diff --git a/app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue b/app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue index cb1d3955d1..6a3cc71763 100644 --- a/app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue +++ b/app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue @@ -4,7 +4,6 @@ import type { PackageFileTreeResponse, PackageFileContentResponse, } from '#shared/types' -import { isBinaryFilePath } from '~/utils/file-types' definePageMeta({ name: 'code', @@ -107,7 +106,12 @@ const isViewingFile = computed(() => currentNode.value?.type === 'file') // Maximum file size we'll try to load (500KB) - must match server const MAX_FILE_SIZE = 500 * 1024 -const isBinaryFile = computed(() => !!filePath.value && isBinaryFilePath(filePath.value)) +// Estimate binary file based on mime type +const isBinaryFile = computed(() => { + const contentType = fileContent.value?.contentType + if (!contentType) return false + return isBinaryContentType(contentType) +}) const isFileTooLarge = computed(() => { const size = currentNode.value?.size @@ -117,13 +121,7 @@ const isFileTooLarge = computed(() => { // Fetch file content when a file is selected (and not too large) const fileContentUrl = computed(() => { // Don't fetch if no file path, file tree not loaded, file is too large, or it's a directory - if ( - !filePath.value || - !fileTree.value || - isFileTooLarge.value || - !isViewingFile.value || - isBinaryFile.value - ) { + if (!filePath.value || !fileTree.value || isFileTooLarge.value || !isViewingFile.value) { return null } return `/api/registry/file/${packageName.value}/v/${version.value}/${filePath.value}` @@ -533,7 +531,13 @@ defineOgImageComponent('Default', {

{{ $t('code.binary_file') }}

-

{{ $t('code.binary_rendering_warning') }}

+

+ {{ + $t('code.binary_rendering_warning', { + contentType: fileContent?.contentType ?? 'unknown', + }) + }} +

-1 ? filePath.slice(dotIndex + 1).toLowerCase() : '' - return BINARY_EXTENSIONS.has(ext) +export function isBinaryContentType(contentType: string): boolean { + for (const prefix of BINARY_MIME_PREFIXES) { + if (contentType.startsWith(prefix)) { + return true + } + } + return false } diff --git a/i18n/locales/en.json b/i18n/locales/en.json index c187265c73..93957b3a22 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -794,7 +794,7 @@ "file_path": "File path", "scroll_to_top": "Scroll to top", "binary_file": "Binary file", - "binary_rendering_warning": "File type not supported for preview." + "binary_rendering_warning": "File type \"{contentType}\" is not supported for preview." }, "badges": { "provenance": {