forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import * as v from 'valibot'
2import { PackageRouteParamsSchema } from '#shared/schemas/package'
3import {
4 CACHE_MAX_AGE_ONE_HOUR,
5 NPM_MISSING_README_SENTINEL,
6 NPM_README_TRUNCATION_THRESHOLD,
7} from '#shared/utils/constants'
8
9/** Standard README filenames to try when fetching from jsdelivr (case-sensitive CDN) */
10const standardReadmeFilenames = [
11 'README.md',
12 'readme.md',
13 'Readme.md',
14 'README',
15 'readme',
16 'README.markdown',
17 'readme.markdown',
18]
19
20/** Matches standard README filenames (case-insensitive, for checking registry metadata) */
21const standardReadmePattern = /^readme(?:\.md|\.markdown)?$/i
22
23export function isStandardReadme(filename: string | undefined): boolean {
24 return !!filename && standardReadmePattern.test(filename)
25}
26
27/**
28 * Fetch README from jsdelivr CDN for a specific package version.
29 * Falls back through common README filenames.
30 */
31export async function fetchReadmeFromJsdelivr(
32 packageName: string,
33 readmeFilenames: string[],
34 version?: string,
35): Promise<string | null> {
36 const versionSuffix = version ? `@${version}` : ''
37
38 for (const filename of readmeFilenames) {
39 try {
40 const url = `https://cdn.jsdelivr.net/npm/${packageName}${versionSuffix}/${filename}`
41 const response = await fetch(url)
42 if (response.ok) {
43 return await response.text()
44 }
45 } catch {
46 // Try next filename
47 }
48 }
49
50 return null
51}
52
53export const resolvePackageReadmeSource = defineCachedFunction(
54 async (packagePath: string) => {
55 const pkgParamSegments = packagePath.split('/')
56
57 const { rawPackageName, rawVersion } = parsePackageParams(pkgParamSegments)
58
59 const { packageName, version } = v.parse(PackageRouteParamsSchema, {
60 packageName: rawPackageName,
61 version: rawVersion,
62 })
63
64 const packageData = await fetchNpmPackage(packageName)
65
66 let readmeContent: string | undefined
67 let readmeFilename: string | undefined
68
69 if (version) {
70 const versionData = packageData.versions[version]
71 if (versionData) {
72 readmeContent = versionData.readme
73 readmeFilename = versionData.readmeFilename
74 }
75 } else {
76 readmeContent = packageData.readme
77 readmeFilename = packageData.readmeFilename
78 }
79
80 const hasValidNpmReadme = readmeContent && readmeContent !== NPM_MISSING_README_SENTINEL
81
82 if (
83 !hasValidNpmReadme ||
84 !isStandardReadme(readmeFilename) ||
85 readmeContent!.length >= NPM_README_TRUNCATION_THRESHOLD
86 ) {
87 const resolvedVersion = version ?? packageData['dist-tags']?.latest
88 const jsdelivrReadme = await fetchReadmeFromJsdelivr(
89 packageName,
90 standardReadmeFilenames,
91 resolvedVersion,
92 )
93 if (jsdelivrReadme) {
94 readmeContent = jsdelivrReadme
95 }
96 }
97
98 if (!readmeContent || readmeContent === NPM_MISSING_README_SENTINEL) {
99 return {
100 packageName,
101 version,
102 markdown: undefined,
103 repoInfo: undefined,
104 }
105 }
106
107 const repoInfo = parseRepositoryInfo(packageData.repository)
108
109 return {
110 packageName,
111 version,
112 markdown: readmeContent,
113 repoInfo,
114 }
115 },
116 {
117 maxAge: CACHE_MAX_AGE_ONE_HOUR,
118 swr: true,
119 getKey: (packagePath: string) => packagePath,
120 },
121)