MCPcopy Index your code
hub / github.com/npmx-dev/npmx.dev / resolveUrl

Function resolveUrl

server/utils/readme.ts:324–405  ·  view source on GitHub ↗

* Resolve a relative URL to an absolute URL. * If repository info is available, resolve to provider's raw file URLs. * For markdown files (.md), use blob URLs so they render properly. * Otherwise, fall back to jsdelivr CDN (except for .md files which are left unchanged).

(url: string, packageName: string, repoInfo?: RepositoryInfo)

Source from the content-addressed store, hash-verified

322 * Otherwise, fall back to jsdelivr CDN (except for .md files which are left unchanged).
323 */
324function resolveUrl(url: string, packageName: string, repoInfo?: RepositoryInfo): string {
325 if (!url) return url
326 if (url.startsWith('#')) {
327 // Prefix anchor links to match heading IDs (avoids collision with page IDs)
328 // Normalize markdown-style heading fragments to the same slug format used
329 // for generated README heading IDs, but leave already-prefixed values as-is.
330 const fragment = url.slice(1)
331 if (!fragment) {
332 return '#'
333 }
334 if (fragment.startsWith(USER_CONTENT_PREFIX)) {
335 return `#${fragment}`
336 }
337
338 const normalizedFragment = slugify(decodeHashFragment(fragment))
339 return toUserContentHash(normalizedFragment || fragment)
340 }
341 // Absolute paths (e.g. /package/foo from a previous npmjs redirect) are already resolved
342 if (url.startsWith('/')) return url
343 if (hasProtocol(url, { acceptRelative: true })) {
344 try {
345 const parsed = new URL(url, 'https://example.com')
346 if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
347 // Redirect npmjs urls to ourself
348 if (isNpmJsUrlThatCanBeRedirected(parsed)) {
349 return parsed.pathname + parsed.search + parsed.hash
350 }
351 return url
352 }
353 } catch {
354 // Invalid URL, fall through to resolve as relative
355 }
356 // return protocol-relative URLs (//example.com) as-is
357 if (url.startsWith('//')) {
358 return url
359 }
360 // for non-HTTP protocols (javascript:, data:, etc.), don't return, treat as relative
361 }
362
363 // Check if this is a markdown file link
364 const isMarkdownFile = /\.md$/i.test(url.split('?')[0]?.split('#')[0] ?? '')
365
366 // Use provider's URL base when repository info is available
367 // This handles assets that exist in the repo but not in the npm tarball
368 if (repoInfo?.rawBaseUrl) {
369 // Normalize the relative path (remove leading ./)
370 let relativePath = url.replace(/^\.\//, '')
371
372 // If package is in a subdirectory, resolve relative paths from there
373 // e.g., for packages/ai with ./assets/hero.gif → packages/ai/assets/hero.gif
374 // but for ../../.github/assets/banner.jpg → resolve relative to subdirectory
375 if (repoInfo.directory) {
376 // Split directory into parts for relative path resolution
377 const dirParts = repoInfo.directory.split('/').filter(Boolean)
378
379 // Handle ../ navigation
380 while (relativePath.startsWith('../')) {
381 relativePath = relativePath.slice(3)

Callers 3

resolveImageUrlFunction · 0.70
processLinkFunction · 0.70
renderReadmeHtmlFunction · 0.70

Calls 4

slugifyFunction · 0.90
decodeHashFragmentFunction · 0.85
toUserContentHashFunction · 0.85

Tested by

no test coverage detected