From 514d7af9bcf1cf9e21d49f5337e32767c219132e Mon Sep 17 00:00:00 2001 From: Jonathan Yeong Date: Mon, 26 Jan 2026 10:46:07 -0500 Subject: [PATCH 1/5] fix: fetch readme if filename doesn't match locale And is not the standard filenames --- server/api/registry/readme/[...pkg].get.ts | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/server/api/registry/readme/[...pkg].get.ts b/server/api/registry/readme/[...pkg].get.ts index e138bab56a..ee957a7d5b 100644 --- a/server/api/registry/readme/[...pkg].get.ts +++ b/server/api/registry/readme/[...pkg].get.ts @@ -80,7 +80,11 @@ export default defineCachedEventHandler( } // If no README in packument, try fetching from jsdelivr (package tarball) - if (!readmeContent || readmeContent === NPM_MISSING_README_SENTINEL) { + if ( + !readmeContent || + readmeContent === NPM_MISSING_README_SENTINEL || + !readmeFilenameMatchesLocale(packageData.readmeFilename) + ) { readmeContent = (await fetchReadmeFromJsdelivr(packageName, version)) ?? undefined } @@ -108,3 +112,21 @@ export default defineCachedEventHandler( }, }, ) + +function readmeFilenameMatchesLocale(filename: string | undefined): boolean { + if (!filename) { + return false + } + + const filenames = [ + 'README.md', + 'readme.md', + 'Readme.md', + 'README', + 'readme', + 'README.markdown', + 'readme.markdown', + ] + + return filenames.includes(filename) || filename.includes(navigator.language) +} From de5132fe640631a6a47c09fd038c2eea4471fbe0 Mon Sep 17 00:00:00 2001 From: Jonathan Yeong Date: Mon, 26 Jan 2026 10:57:58 -0500 Subject: [PATCH 2/5] Use readme based on your locale --- server/api/registry/readme/[...pkg].get.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/api/registry/readme/[...pkg].get.ts b/server/api/registry/readme/[...pkg].get.ts index ee957a7d5b..24dbd6ddc8 100644 --- a/server/api/registry/readme/[...pkg].get.ts +++ b/server/api/registry/readme/[...pkg].get.ts @@ -15,6 +15,7 @@ async function fetchReadmeFromJsdelivr( version?: string, ): Promise { const filenames = [ + `README.${navigator.language}.md`, 'README.md', 'readme.md', 'Readme.md', From 406ed685dc000d8bff94f00f0eedff3adfd2d104 Mon Sep 17 00:00:00 2001 From: Jonathan Yeong Date: Mon, 26 Jan 2026 11:06:14 -0500 Subject: [PATCH 3/5] refactor filenames const to a shared location and rename method --- server/api/registry/readme/[...pkg].get.ts | 51 +++++++++++----------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/server/api/registry/readme/[...pkg].get.ts b/server/api/registry/readme/[...pkg].get.ts index 24dbd6ddc8..bdf1cdfffa 100644 --- a/server/api/registry/readme/[...pkg].get.ts +++ b/server/api/registry/readme/[...pkg].get.ts @@ -12,21 +12,12 @@ import { */ async function fetchReadmeFromJsdelivr( packageName: string, + readmeFilenames: string[], version?: string, ): Promise { - const filenames = [ - `README.${navigator.language}.md`, - 'README.md', - 'readme.md', - 'Readme.md', - 'README', - 'readme', - 'README.markdown', - 'readme.markdown', - ] const versionSuffix = version ? `@${version}` : '' - for (const filename of filenames) { + for (const filename of readmeFilenames) { try { const url = `https://cdn.jsdelivr.net/npm/${packageName}${versionSuffix}/${filename}` const response = await fetch(url) @@ -80,13 +71,29 @@ export default defineCachedEventHandler( readmeContent = packageData.readme } + const standardReadmeFilenames = [ + 'README.md', + 'readme.md', + 'Readme.md', + 'README', + 'readme', + 'README.markdown', + 'readme.markdown', + ] + // If no README in packument, try fetching from jsdelivr (package tarball) if ( !readmeContent || readmeContent === NPM_MISSING_README_SENTINEL || - !readmeFilenameMatchesLocale(packageData.readmeFilename) + !isPreferredReadme(packageData.readmeFilename, standardReadmeFilenames) ) { - readmeContent = (await fetchReadmeFromJsdelivr(packageName, version)) ?? undefined + const locale = navigator.language + readmeContent = + (await fetchReadmeFromJsdelivr( + packageName, + [`README.${locale}.md`, ...standardReadmeFilenames], + version, + )) ?? undefined } if (!readmeContent) { @@ -114,20 +121,12 @@ export default defineCachedEventHandler( }, ) -function readmeFilenameMatchesLocale(filename: string | undefined): boolean { +function isPreferredReadme( + filename: string | undefined, + standardReadmeFilenames: string[], +): boolean { if (!filename) { return false } - - const filenames = [ - 'README.md', - 'readme.md', - 'Readme.md', - 'README', - 'readme', - 'README.markdown', - 'readme.markdown', - ] - - return filenames.includes(filename) || filename.includes(navigator.language) + return standardReadmeFilenames.includes(filename) || filename.includes(navigator.language) } From 81a37f9b97bd160b9b24bdb2a105cecdd0888203 Mon Sep 17 00:00:00 2001 From: Jonathan Yeong Date: Mon, 26 Jan 2026 17:35:05 -0500 Subject: [PATCH 4/5] remove locale since it won't exist on the server --- server/api/registry/readme/[...pkg].get.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/server/api/registry/readme/[...pkg].get.ts b/server/api/registry/readme/[...pkg].get.ts index bdf1cdfffa..94a3b1085b 100644 --- a/server/api/registry/readme/[...pkg].get.ts +++ b/server/api/registry/readme/[...pkg].get.ts @@ -81,19 +81,15 @@ export default defineCachedEventHandler( 'readme.markdown', ] - // If no README in packument, try fetching from jsdelivr (package tarball) + // If no README in packument or if README is not in the standard filenames, try fetching from jsdelivr (package tarball) if ( !readmeContent || readmeContent === NPM_MISSING_README_SENTINEL || - !isPreferredReadme(packageData.readmeFilename, standardReadmeFilenames) + !isStandardReadme(packageData.readmeFilename, standardReadmeFilenames) ) { - const locale = navigator.language readmeContent = - (await fetchReadmeFromJsdelivr( - packageName, - [`README.${locale}.md`, ...standardReadmeFilenames], - version, - )) ?? undefined + (await fetchReadmeFromJsdelivr(packageName, standardReadmeFilenames, version)) ?? + undefined } if (!readmeContent) { @@ -121,12 +117,12 @@ export default defineCachedEventHandler( }, ) -function isPreferredReadme( +function isStandardReadme( filename: string | undefined, standardReadmeFilenames: string[], ): boolean { if (!filename) { return false } - return standardReadmeFilenames.includes(filename) || filename.includes(navigator.language) + return standardReadmeFilenames.includes(filename) } From 0909eb1ae10eee3265e35e8915a650fdf95b0c9b Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Mon, 26 Jan 2026 23:52:58 +0000 Subject: [PATCH 5/5] refactor: use regexp, hoist filenames, and fall back to npm readme if jsdelivr 404s --- server/api/registry/readme/[...pkg].get.ts | 64 ++++++++++++---------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/server/api/registry/readme/[...pkg].get.ts b/server/api/registry/readme/[...pkg].get.ts index 94a3b1085b..8f455e7989 100644 --- a/server/api/registry/readme/[...pkg].get.ts +++ b/server/api/registry/readme/[...pkg].get.ts @@ -6,6 +6,20 @@ import { ERROR_NPM_FETCH_FAILED, } from '#shared/utils/constants' +/** Standard README filenames to try when fetching from jsdelivr (case-sensitive CDN) */ +const standardReadmeFilenames = [ + 'README.md', + 'readme.md', + 'Readme.md', + 'README', + 'readme', + 'README.markdown', + 'readme.markdown', +] + +/** Matches standard README filenames (case-insensitive, for checking registry metadata) */ +const standardReadmePattern = /^readme(\.md|\.markdown)?$/i + /** * Fetch README from jsdelivr CDN for a specific package version. * Falls back through common README filenames. @@ -59,40 +73,40 @@ export default defineCachedEventHandler( const packageData = await fetchNpmPackage(packageName) let readmeContent: string | undefined + let readmeFilename: string | undefined // If a specific version is requested, get README from that version if (version) { const versionData = packageData.versions[version] if (versionData) { readmeContent = versionData.readme + readmeFilename = versionData.readmeFilename } } else { // Use the packument-level readme (from latest version) readmeContent = packageData.readme + readmeFilename = packageData.readmeFilename } - const standardReadmeFilenames = [ - 'README.md', - 'readme.md', - 'Readme.md', - 'README', - 'readme', - 'README.markdown', - 'readme.markdown', - ] - - // If no README in packument or if README is not in the standard filenames, try fetching from jsdelivr (package tarball) - if ( - !readmeContent || - readmeContent === NPM_MISSING_README_SENTINEL || - !isStandardReadme(packageData.readmeFilename, standardReadmeFilenames) - ) { - readmeContent = - (await fetchReadmeFromJsdelivr(packageName, standardReadmeFilenames, version)) ?? - undefined + const hasValidNpmReadme = readmeContent && readmeContent !== NPM_MISSING_README_SENTINEL + + // If no README in packument, or if readmeFilename is non-standard (e.g., README.zh-TW.md), + // try fetching a standard README from jsdelivr (package tarball). + // Note: When readmeFilename is missing, we defensively fetch from jsdelivr to ensure + // we get a standard English README if one exists. + if (!hasValidNpmReadme || !isStandardReadme(readmeFilename)) { + const jsdelivrReadme = await fetchReadmeFromJsdelivr( + packageName, + standardReadmeFilenames, + version, + ) + // Only replace npm content if jsdelivr returned something + if (jsdelivrReadme) { + readmeContent = jsdelivrReadme + } } - if (!readmeContent) { + if (!readmeContent || readmeContent === NPM_MISSING_README_SENTINEL) { return { html: '', playgroundLinks: [] } } @@ -117,12 +131,6 @@ export default defineCachedEventHandler( }, ) -function isStandardReadme( - filename: string | undefined, - standardReadmeFilenames: string[], -): boolean { - if (!filename) { - return false - } - return standardReadmeFilenames.includes(filename) +function isStandardReadme(filename: string | undefined): boolean { + return !!filename && standardReadmePattern.test(filename) }