[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: display deprecation notice for deprecated versions and packages (#102)

authored by

Lucie and committed by
GitHub
da9e55d0 84c3cdb0

+114 -21
+74 -15
app/components/PackageVersions.vue
··· 26 26 time?: string 27 27 tags?: string[] 28 28 hasProvenance: boolean 29 + deprecated?: string 29 30 } 30 31 31 32 // Check if a version has provenance/attestations ··· 86 87 time: props.time[version], 87 88 tags, 88 89 hasProvenance: hasProvenance(versionData), 90 + deprecated: versionData?.deprecated, 89 91 } as VersionDisplay, 90 92 })) 91 93 .sort((a, b) => compareVersions(b.primaryVersion.version, a.primaryVersion.version)) 92 94 }) 93 95 94 - // Visible tag rows (limited to MAX_VISIBLE_TAGS) 95 - const visibleTagRows = computed(() => allTagRows.value.slice(0, MAX_VISIBLE_TAGS)) 96 + // Check if the whole package is deprecated (latest version is deprecated) 97 + const isPackageDeprecated = computed(() => { 98 + const latestVersion = props.distTags.latest 99 + if (!latestVersion) return false 100 + return !!props.versions[latestVersion]?.deprecated 101 + }) 96 102 97 - // Hidden tag rows (overflow beyond MAX_VISIBLE_TAGS) - shown in "Other versions" 98 - const hiddenTagRows = computed(() => allTagRows.value.slice(MAX_VISIBLE_TAGS)) 103 + // Visible tag rows: limited to MAX_VISIBLE_TAGS 104 + // If package is NOT deprecated, filter out deprecated tags from visible list 105 + const visibleTagRows = computed(() => { 106 + const rows = isPackageDeprecated.value 107 + ? allTagRows.value 108 + : allTagRows.value.filter(row => !row.primaryVersion.deprecated) 109 + return rows.slice(0, MAX_VISIBLE_TAGS) 110 + }) 111 + 112 + // Hidden tag rows (all other tags) - shown in "Other versions" 113 + const hiddenTagRows = computed(() => 114 + allTagRows.value.filter(row => !visibleTagRows.value.includes(row)), 115 + ) 99 116 100 117 // Client-side state for expansion and loaded versions 101 118 const expandedTags = ref<Set<string>>(new Set()) ··· 166 183 time: v.time, 167 184 tags: versionToTags.value.get(v.version), 168 185 hasProvenance: v.hasProvenance, 186 + deprecated: v.deprecated, 169 187 })) 170 188 171 189 tagVersions.value.set(row.tag, channelVersions) ··· 190 208 time: v.time, 191 209 tags: versionToTags.value.get(v.version), 192 210 hasProvenance: v.hasProvenance, 211 + deprecated: v.deprecated, 193 212 }) 194 213 } 195 214 ··· 306 325 <div class="flex items-center justify-between gap-2"> 307 326 <NuxtLink 308 327 :to="versionRoute(row.primaryVersion.version)" 309 - class="font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 truncate" 310 - :title="row.primaryVersion.version" 328 + class="font-mono text-sm transition-colors duration-200 truncate" 329 + :class=" 330 + row.primaryVersion.deprecated 331 + ? 'text-red-400 hover:text-red-300' 332 + : 'text-fg-muted hover:text-fg' 333 + " 334 + :title=" 335 + row.primaryVersion.deprecated 336 + ? `${row.primaryVersion.version} (deprecated)` 337 + : row.primaryVersion.version 338 + " 311 339 > 312 340 {{ row.primaryVersion.version }} 313 341 </NuxtLink> ··· 350 378 <div class="flex items-center justify-between gap-2"> 351 379 <NuxtLink 352 380 :to="versionRoute(v.version)" 353 - class="font-mono text-xs text-fg-subtle hover:text-fg-muted transition-colors duration-200 truncate" 354 - :title="v.version" 381 + class="font-mono text-xs transition-colors duration-200 truncate" 382 + :class=" 383 + v.deprecated 384 + ? 'text-red-400 hover:text-red-300' 385 + : 'text-fg-subtle hover:text-fg-muted' 386 + " 387 + :title="v.deprecated ? `${v.version} (deprecated)` : v.version" 355 388 > 356 389 {{ v.version }} 357 390 </NuxtLink> ··· 422 455 <div class="flex items-center justify-between gap-2"> 423 456 <NuxtLink 424 457 :to="versionRoute(row.primaryVersion.version)" 425 - class="font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200 truncate" 426 - :title="row.primaryVersion.version" 458 + class="font-mono text-xs transition-colors duration-200 truncate" 459 + :class=" 460 + row.primaryVersion.deprecated 461 + ? 'text-red-400 hover:text-red-300' 462 + : 'text-fg-muted hover:text-fg' 463 + " 464 + :title=" 465 + row.primaryVersion.deprecated 466 + ? `${row.primaryVersion.version} (deprecated)` 467 + : row.primaryVersion.version 468 + " 427 469 > 428 470 {{ row.primaryVersion.version }} 429 471 </NuxtLink> ··· 467 509 class="w-3 h-3 transition-transform duration-200 text-fg-subtle" 468 510 :class="group.expanded ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right'" 469 511 /> 470 - <span class="font-mono text-xs text-fg-muted truncate"> 512 + <span 513 + class="font-mono text-xs truncate" 514 + :class="group.versions[0]?.deprecated ? 'text-red-400' : 'text-fg-muted'" 515 + > 471 516 {{ group.versions[0]?.version }} 472 517 </span> 473 518 </div> ··· 492 537 <NuxtLink 493 538 v-if="group.versions[0]" 494 539 :to="versionRoute(group.versions[0].version)" 495 - class="font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200 truncate" 496 - :title="group.versions[0].version" 540 + class="font-mono text-xs transition-colors duration-200 truncate" 541 + :class=" 542 + group.versions[0].deprecated 543 + ? 'text-red-400 hover:text-red-300' 544 + : 'text-fg-muted hover:text-fg' 545 + " 546 + :title=" 547 + group.versions[0].deprecated 548 + ? `${group.versions[0].version} (deprecated)` 549 + : group.versions[0].version 550 + " 497 551 > 498 552 {{ group.versions[0].version }} 499 553 </NuxtLink> ··· 515 569 <div class="flex items-center justify-between gap-2"> 516 570 <NuxtLink 517 571 :to="versionRoute(v.version)" 518 - class="font-mono text-xs text-fg-subtle hover:text-fg-muted transition-colors duration-200 truncate" 519 - :title="v.version" 572 + class="font-mono text-xs transition-colors duration-200 truncate" 573 + :class=" 574 + v.deprecated 575 + ? 'text-red-400 hover:text-red-300' 576 + : 'text-fg-subtle hover:text-fg-muted' 577 + " 578 + :title="v.deprecated ? `${v.version} (deprecated)` : v.version" 520 579 > 521 580 {{ v.version }} 522 581 </NuxtLink>
+8 -6
app/composables/useNpmRegistry.ts
··· 379 379 380 380 const promise = (async () => { 381 381 const encodedName = encodePackageName(packageName) 382 - const data = await $fetch<{ versions: Record<string, unknown>; time: Record<string, string> }>( 383 - `${NPM_REGISTRY}/${encodedName}`, 384 - ) 382 + const data = await $fetch<{ 383 + versions: Record<string, { deprecated?: string }> 384 + time: Record<string, string> 385 + }>(`${NPM_REGISTRY}/${encodedName}`) 385 386 386 - return Object.keys(data.versions) 387 - .filter(v => data.time[v]) 388 - .map(version => ({ 387 + return Object.entries(data.versions) 388 + .filter(([v]) => data.time[v]) 389 + .map(([version, versionData]) => ({ 389 390 version, 390 391 time: data.time[version], 391 392 hasProvenance: false, // Would need to check dist.attestations for each version 393 + deprecated: versionData.deprecated, 392 394 })) 393 395 .sort((a, b) => compareVersions(b.version, a.version)) 394 396 })()
+31
app/pages/[...package].vue
··· 131 131 return pkg.value.versions[latestTag] ?? null 132 132 }) 133 133 134 + const deprecationNotice = computed(() => { 135 + if (!displayVersion.value?.deprecated) return null 136 + 137 + const isLatestDeprecated = !!latestVersion.value?.deprecated 138 + 139 + // If latest is deprecated, show "package deprecated" 140 + if (isLatestDeprecated) { 141 + return { type: 'package' as const, message: displayVersion.value.deprecated } 142 + } 143 + 144 + // Otherwise show "version deprecated" 145 + return { type: 'version' as const, message: displayVersion.value.deprecated } 146 + }) 147 + 134 148 const hasDependencies = computed(() => { 135 149 if (!displayVersion.value) return false 136 150 const deps = displayVersion.value.dependencies ··· 396 410 </button> 397 411 </div> 398 412 </div> 413 + </div> 414 + 415 + <div 416 + v-if="deprecationNotice" 417 + class="border border-red-400 bg-red-400/10 rounded-lg px-3 py-2 text-base text-red-400" 418 + > 419 + <h2 class="font-medium mb-2"> 420 + {{ 421 + deprecationNotice.type === 'package' 422 + ? 'This package has been deprecated.' 423 + : 'This version has been deprecated.' 424 + }} 425 + </h2> 426 + <p v-if="deprecationNotice.message" class="text-base m-0"> 427 + <MarkdownText :text="deprecationNotice.message" /> 428 + </p> 429 + <p v-else class="text-base m-0 italic">No reason provided</p> 399 430 </div> 400 431 401 432 <!-- Stats grid -->
+1
shared/types/npm-registry.ts
··· 48 48 version: string 49 49 time?: string 50 50 hasProvenance: boolean 51 + deprecated?: string 51 52 } 52 53 53 54 /**