[READ-ONLY] a fast, modern browser for the npm registry
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: support resolving tag and version ranges (#103)

authored by

Anthony Fu and committed by
GitHub
22637846 da9e55d0

+77 -16
+12 -6
app/components/PackageDependencies.vue
··· 91 91 > 92 92 <span class="i-carbon-warning-alt w-3 h-3" /> 93 93 </span> 94 - <span 94 + <NuxtLink 95 + :to="{ name: 'package', params: { package: [...dep.split('/'), 'v', version] } }" 95 96 class="font-mono text-xs text-right truncate" 96 97 :class="getVersionClass(outdatedDeps[dep])" 97 98 :title="outdatedDeps[dep] ? getOutdatedTooltip(outdatedDeps[dep]) : version" 98 99 > 99 100 {{ version }} 100 - </span> 101 + </NuxtLink> 101 102 <span v-if="outdatedDeps[dep]" class="sr-only"> 102 103 ({{ getOutdatedTooltip(outdatedDeps[dep]) }}) 103 104 </span> ··· 143 144 optional 144 145 </span> 145 146 </div> 146 - <span 147 + <NuxtLink 148 + :to="{ 149 + name: 'package', 150 + params: { package: [...peer.name.split('/'), 'v', peer.version] }, 151 + }" 147 152 class="font-mono text-xs text-fg-subtle max-w-[40%] text-right truncate" 148 153 :title="peer.version" 149 154 > 150 155 {{ peer.version }} 151 - </span> 156 + </NuxtLink> 152 157 </li> 153 158 </ul> 154 159 <button ··· 187 192 > 188 193 {{ dep }} 189 194 </NuxtLink> 190 - <span 195 + <NuxtLink 196 + :to="{ name: 'package', params: { package: [...dep.split('/'), 'v', version] } }" 191 197 class="font-mono text-xs text-fg-subtle max-w-[50%] text-right truncate" 192 198 :title="version" 193 199 > 194 200 {{ version }} 195 - </span> 201 + </NuxtLink> 196 202 </li> 197 203 </ul> 198 204 <button
+31 -2
app/composables/useNpmRegistry.ts
··· 10 10 } from '#shared/types' 11 11 import type { ReleaseType } from 'semver' 12 12 import { maxSatisfying, prerelease, major, minor, diff, gt } from 'semver' 13 - import { compareVersions } from '~/utils/versions' 13 + import { compareVersions, isExactVersion } from '~/utils/versions' 14 14 15 15 const NPM_REGISTRY = 'https://registry.npmjs.org' 16 16 const NPM_API = 'https://api.npmjs.org' ··· 154 154 name: MaybeRefOrGetter<string>, 155 155 requestedVersion?: MaybeRefOrGetter<string | null>, 156 156 ) { 157 - return useLazyAsyncData( 157 + const asyncData = useLazyAsyncData( 158 158 () => `package:${toValue(name)}:${toValue(requestedVersion) ?? ''}`, 159 159 () => 160 160 fetchNpmPackage(toValue(name)).then(r => transformPackument(r, toValue(requestedVersion))), 161 161 ) 162 + 163 + // Resolve requestedVersion to an exact version 164 + // Handles: exact versions, dist-tags (latest, next), and semver ranges (^4.2, >=1.0.0) 165 + const resolvedVersion = computed(() => { 166 + const pkg = asyncData.data.value 167 + const reqVer = toValue(requestedVersion) 168 + if (!pkg || !reqVer) return null 169 + 170 + // 1. Check if it's already an exact version in pkg.versions 171 + if (isExactVersion(reqVer) && pkg.versions[reqVer]) { 172 + return reqVer 173 + } 174 + 175 + // 2. Check if it's a dist-tag (latest, next, beta, etc.) 176 + const tagVersion = pkg['dist-tags']?.[reqVer] 177 + if (tagVersion) { 178 + return tagVersion 179 + } 180 + 181 + // 3. Try to resolve as a semver range 182 + const versions = Object.keys(pkg.versions) 183 + const resolved = maxSatisfying(versions, reqVer) 184 + return resolved 185 + }) 186 + 187 + return { 188 + ...asyncData, 189 + resolvedVersion, 190 + } 162 191 } 163 192 164 193 export function usePackageDownloads(
+21 -8
app/pages/[...package].vue
··· 50 50 return match ? match[1] : null 51 51 }) 52 52 53 - const { data: pkg, status, error } = usePackage(packageName, requestedVersion) 53 + const { data: pkg, status, error, resolvedVersion } = usePackage(packageName, requestedVersion) 54 54 55 55 const { data: downloads } = usePackageDownloads(packageName, 'last-week') 56 56 const { data: weeklyDownloads } = usePackageWeeklyDownloadEvolution(packageName, { weeks: 52 }) ··· 109 109 return chunks.filter(Boolean).join('\n') 110 110 }) 111 111 112 - // Get the version to display (requested or latest) 112 + // Get the version to display (resolved version or latest) 113 113 const displayVersion = computed(() => { 114 114 if (!pkg.value) return null 115 115 116 - const reqVer = requestedVersion.value 117 - if (reqVer && pkg.value.versions[reqVer]) { 118 - return pkg.value.versions[reqVer] 116 + // Use resolved version if available 117 + if (resolvedVersion.value) { 118 + return pkg.value.versions[resolvedVersion.value] ?? null 119 119 } 120 120 121 + // Fallback to latest 121 122 const latestTag = pkg.value['dist-tags']?.latest 122 123 if (!latestTag) return null 123 124 return pkg.value.versions[latestTag] ?? null ··· 331 332 v-if="displayVersion" 332 333 class="inline-flex items-baseline gap-1.5 font-mono text-base sm:text-lg text-fg-muted shrink-0" 333 334 > 335 + <!-- Version resolution indicator (e.g., "latest → 4.2.0") --> 336 + <template v-if="resolvedVersion !== requestedVersion"> 337 + <span class="font-mono text-fg-muted text-sm">{{ requestedVersion }}</span> 338 + <span class="i-carbon-arrow-right w-3 h-3" aria-hidden="true" /> 339 + </template> 340 + 341 + <NuxtLink 342 + v-if="resolvedVersion !== requestedVersion" 343 + :to="`/${pkg.name}/v/${displayVersion.version}`" 344 + title="View permalink for this version" 345 + >{{ displayVersion.version }}</NuxtLink 346 + > 347 + <span v-else>v{{ displayVersion.version }}</span> 348 + 334 349 <a 335 350 v-if="hasProvenance(displayVersion)" 336 351 :href="`https://www.npmjs.com/package/${pkg.name}/v/${displayVersion.version}#provenance`" 337 352 target="_blank" 338 353 rel="noopener noreferrer" 339 - class="inline-flex items-center gap-1.5 text-fg-muted hover:text-fg-muted/80 transition-colors duration-200" 354 + class="inline-flex items-center gap-1.5 text-fg-muted hover:text-fg transition-colors duration-200" 340 355 title="Verified provenance" 341 356 > 342 - v{{ displayVersion.version }} 343 357 <span 344 358 class="i-solar-shield-check-outline w-3.5 h-3.5 shrink-0" 345 359 aria-hidden="true" 346 360 /> 347 361 </a> 348 - <span v-else>v{{ displayVersion.version }}</span> 349 362 <span 350 363 v-if=" 351 364 requestedVersion &&
+13
app/utils/versions.ts
··· 1 + import { valid } from 'semver' 2 + 1 3 /** 2 4 * Utilities for handling npm package versions and dist-tags 3 5 */ 6 + 7 + /** 8 + * Check if a version string is an exact semver version. 9 + * Returns true for "1.2.3", "1.0.0-beta.1", etc. 10 + * Returns false for ranges like "^1.2.3", ">=1.0.0", tags like "latest", etc. 11 + * @param version - The version string to check 12 + * @returns true if the version is an exact semver version 13 + */ 14 + export function isExactVersion(version: string): boolean { 15 + return valid(version) !== null 16 + } 4 17 5 18 /** Parsed semver version components */ 6 19 export interface ParsedVersion {