Skip to content

Commit da9e55d

Browse files
authored
feat: display deprecation notice for deprecated versions and packages (#102)
1 parent 84c3cdb commit da9e55d

4 files changed

Lines changed: 115 additions & 22 deletions

File tree

app/components/PackageVersions.vue

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ interface VersionDisplay {
2626
time?: string
2727
tags?: string[]
2828
hasProvenance: boolean
29+
deprecated?: string
2930
}
3031
3132
// Check if a version has provenance/attestations
@@ -86,16 +87,32 @@ const allTagRows = computed(() => {
8687
time: props.time[version],
8788
tags,
8889
hasProvenance: hasProvenance(versionData),
90+
deprecated: versionData?.deprecated,
8991
} as VersionDisplay,
9092
}))
9193
.sort((a, b) => compareVersions(b.primaryVersion.version, a.primaryVersion.version))
9294
})
9395
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+
})
102+
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+
})
96111
97-
// Hidden tag rows (overflow beyond MAX_VISIBLE_TAGS) - shown in "Other versions"
98-
const hiddenTagRows = computed(() => allTagRows.value.slice(MAX_VISIBLE_TAGS))
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+
)
99116
100117
// Client-side state for expansion and loaded versions
101118
const expandedTags = ref<Set<string>>(new Set())
@@ -166,6 +183,7 @@ function processLoadedVersions(allVersions: PackageVersionInfo[]) {
166183
time: v.time,
167184
tags: versionToTags.value.get(v.version),
168185
hasProvenance: v.hasProvenance,
186+
deprecated: v.deprecated,
169187
}))
170188
171189
tagVersions.value.set(row.tag, channelVersions)
@@ -190,6 +208,7 @@ function processLoadedVersions(allVersions: PackageVersionInfo[]) {
190208
time: v.time,
191209
tags: versionToTags.value.get(v.version),
192210
hasProvenance: v.hasProvenance,
211+
deprecated: v.deprecated,
193212
})
194213
}
195214
@@ -306,8 +325,17 @@ function getTagVersions(tag: string): VersionDisplay[] {
306325
<div class="flex items-center justify-between gap-2">
307326
<NuxtLink
308327
: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+
"
311339
>
312340
{{ row.primaryVersion.version }}
313341
</NuxtLink>
@@ -350,8 +378,13 @@ function getTagVersions(tag: string): VersionDisplay[] {
350378
<div class="flex items-center justify-between gap-2">
351379
<NuxtLink
352380
: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"
355388
>
356389
{{ v.version }}
357390
</NuxtLink>
@@ -422,8 +455,17 @@ function getTagVersions(tag: string): VersionDisplay[] {
422455
<div class="flex items-center justify-between gap-2">
423456
<NuxtLink
424457
: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+
"
427469
>
428470
{{ row.primaryVersion.version }}
429471
</NuxtLink>
@@ -467,7 +509,10 @@ function getTagVersions(tag: string): VersionDisplay[] {
467509
class="w-3 h-3 transition-transform duration-200 text-fg-subtle"
468510
:class="group.expanded ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right'"
469511
/>
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+
>
471516
{{ group.versions[0]?.version }}
472517
</span>
473518
</div>
@@ -492,8 +537,17 @@ function getTagVersions(tag: string): VersionDisplay[] {
492537
<NuxtLink
493538
v-if="group.versions[0]"
494539
: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+
"
497551
>
498552
{{ group.versions[0].version }}
499553
</NuxtLink>
@@ -515,8 +569,13 @@ function getTagVersions(tag: string): VersionDisplay[] {
515569
<div class="flex items-center justify-between gap-2">
516570
<NuxtLink
517571
: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"
520579
>
521580
{{ v.version }}
522581
</NuxtLink>

app/composables/useNpmRegistry.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -379,16 +379,18 @@ export async function fetchAllPackageVersions(packageName: string): Promise<Pack
379379

380380
const promise = (async () => {
381381
const encodedName = encodePackageName(packageName)
382-
const data = await $fetch<{ versions: Record<string, unknown>; time: Record<string, string> }>(
383-
`${NPM_REGISTRY}/${encodedName}`,
384-
)
385-
386-
return Object.keys(data.versions)
387-
.filter(v => data.time[v])
388-
.map(version => ({
382+
const data = await $fetch<{
383+
versions: Record<string, { deprecated?: string }>
384+
time: Record<string, string>
385+
}>(`${NPM_REGISTRY}/${encodedName}`)
386+
387+
return Object.entries(data.versions)
388+
.filter(([v]) => data.time[v])
389+
.map(([version, versionData]) => ({
389390
version,
390391
time: data.time[version],
391392
hasProvenance: false, // Would need to check dist.attestations for each version
393+
deprecated: versionData.deprecated,
392394
}))
393395
.sort((a, b) => compareVersions(b.version, a.version))
394396
})()

app/pages/[...package].vue

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,20 @@ const latestVersion = computed(() => {
131131
return pkg.value.versions[latestTag] ?? null
132132
})
133133
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+
134148
const hasDependencies = computed(() => {
135149
if (!displayVersion.value) return false
136150
const deps = displayVersion.value.dependencies
@@ -398,6 +412,23 @@ defineOgImageComponent('Package', {
398412
</div>
399413
</div>
400414

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>
430+
</div>
431+
401432
<!-- Stats grid -->
402433
<dl class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-3 sm:gap-4 mt-4 sm:mt-6">
403434
<div v-if="pkg.license" class="space-y-1">

shared/types/npm-registry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface PackageVersionInfo {
4848
version: string
4949
time?: string
5050
hasProvenance: boolean
51+
deprecated?: string
5152
}
5253

5354
/**

0 commit comments

Comments
 (0)