Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 74 additions & 15 deletions app/components/PackageVersions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface VersionDisplay {
time?: string
tags?: string[]
hasProvenance: boolean
deprecated?: string
}

// Check if a version has provenance/attestations
Expand Down Expand Up @@ -86,16 +87,32 @@ const allTagRows = computed(() => {
time: props.time[version],
tags,
hasProvenance: hasProvenance(versionData),
deprecated: versionData?.deprecated,
} as VersionDisplay,
}))
.sort((a, b) => compareVersions(b.primaryVersion.version, a.primaryVersion.version))
})

// Visible tag rows (limited to MAX_VISIBLE_TAGS)
const visibleTagRows = computed(() => allTagRows.value.slice(0, MAX_VISIBLE_TAGS))
// Check if the whole package is deprecated (latest version is deprecated)
const isPackageDeprecated = computed(() => {
const latestVersion = props.distTags.latest
if (!latestVersion) return false
return !!props.versions[latestVersion]?.deprecated
})

// Visible tag rows: limited to MAX_VISIBLE_TAGS
// If package is NOT deprecated, filter out deprecated tags from visible list
const visibleTagRows = computed(() => {
const rows = isPackageDeprecated.value
? allTagRows.value
: allTagRows.value.filter(row => !row.primaryVersion.deprecated)
return rows.slice(0, MAX_VISIBLE_TAGS)
})

// Hidden tag rows (overflow beyond MAX_VISIBLE_TAGS) - shown in "Other versions"
const hiddenTagRows = computed(() => allTagRows.value.slice(MAX_VISIBLE_TAGS))
// Hidden tag rows (all other tags) - shown in "Other versions"
const hiddenTagRows = computed(() =>
allTagRows.value.filter(row => !visibleTagRows.value.includes(row)),
)

// Client-side state for expansion and loaded versions
const expandedTags = ref<Set<string>>(new Set())
Expand Down Expand Up @@ -166,6 +183,7 @@ function processLoadedVersions(allVersions: PackageVersionInfo[]) {
time: v.time,
tags: versionToTags.value.get(v.version),
hasProvenance: v.hasProvenance,
deprecated: v.deprecated,
}))

tagVersions.value.set(row.tag, channelVersions)
Expand All @@ -190,6 +208,7 @@ function processLoadedVersions(allVersions: PackageVersionInfo[]) {
time: v.time,
tags: versionToTags.value.get(v.version),
hasProvenance: v.hasProvenance,
deprecated: v.deprecated,
})
}

Expand Down Expand Up @@ -306,8 +325,17 @@ function getTagVersions(tag: string): VersionDisplay[] {
<div class="flex items-center justify-between gap-2">
<NuxtLink
:to="versionRoute(row.primaryVersion.version)"
class="font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 truncate"
:title="row.primaryVersion.version"
class="font-mono text-sm transition-colors duration-200 truncate"
:class="
row.primaryVersion.deprecated
? 'text-red-400 hover:text-red-300'
: 'text-fg-muted hover:text-fg'
"
:title="
row.primaryVersion.deprecated
? `${row.primaryVersion.version} (deprecated)`
: row.primaryVersion.version
"
>
{{ row.primaryVersion.version }}
</NuxtLink>
Expand Down Expand Up @@ -350,8 +378,13 @@ function getTagVersions(tag: string): VersionDisplay[] {
<div class="flex items-center justify-between gap-2">
<NuxtLink
:to="versionRoute(v.version)"
class="font-mono text-xs text-fg-subtle hover:text-fg-muted transition-colors duration-200 truncate"
:title="v.version"
class="font-mono text-xs transition-colors duration-200 truncate"
:class="
v.deprecated
? 'text-red-400 hover:text-red-300'
: 'text-fg-subtle hover:text-fg-muted'
"
:title="v.deprecated ? `${v.version} (deprecated)` : v.version"
>
{{ v.version }}
</NuxtLink>
Expand Down Expand Up @@ -422,8 +455,17 @@ function getTagVersions(tag: string): VersionDisplay[] {
<div class="flex items-center justify-between gap-2">
<NuxtLink
:to="versionRoute(row.primaryVersion.version)"
class="font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200 truncate"
:title="row.primaryVersion.version"
class="font-mono text-xs transition-colors duration-200 truncate"
:class="
row.primaryVersion.deprecated
? 'text-red-400 hover:text-red-300'
: 'text-fg-muted hover:text-fg'
"
:title="
row.primaryVersion.deprecated
? `${row.primaryVersion.version} (deprecated)`
: row.primaryVersion.version
"
>
{{ row.primaryVersion.version }}
</NuxtLink>
Expand Down Expand Up @@ -467,7 +509,10 @@ function getTagVersions(tag: string): VersionDisplay[] {
class="w-3 h-3 transition-transform duration-200 text-fg-subtle"
:class="group.expanded ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right'"
/>
<span class="font-mono text-xs text-fg-muted truncate">
<span
class="font-mono text-xs truncate"
:class="group.versions[0]?.deprecated ? 'text-red-400' : 'text-fg-muted'"
>
{{ group.versions[0]?.version }}
</span>
</div>
Expand All @@ -492,8 +537,17 @@ function getTagVersions(tag: string): VersionDisplay[] {
<NuxtLink
v-if="group.versions[0]"
:to="versionRoute(group.versions[0].version)"
class="font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200 truncate"
:title="group.versions[0].version"
class="font-mono text-xs transition-colors duration-200 truncate"
:class="
group.versions[0].deprecated
? 'text-red-400 hover:text-red-300'
: 'text-fg-muted hover:text-fg'
"
:title="
group.versions[0].deprecated
? `${group.versions[0].version} (deprecated)`
: group.versions[0].version
"
>
{{ group.versions[0].version }}
</NuxtLink>
Expand All @@ -515,8 +569,13 @@ function getTagVersions(tag: string): VersionDisplay[] {
<div class="flex items-center justify-between gap-2">
<NuxtLink
:to="versionRoute(v.version)"
class="font-mono text-xs text-fg-subtle hover:text-fg-muted transition-colors duration-200 truncate"
:title="v.version"
class="font-mono text-xs transition-colors duration-200 truncate"
:class="
v.deprecated
? 'text-red-400 hover:text-red-300'
: 'text-fg-subtle hover:text-fg-muted'
"
:title="v.deprecated ? `${v.version} (deprecated)` : v.version"
>
{{ v.version }}
</NuxtLink>
Expand Down
16 changes: 9 additions & 7 deletions app/composables/useNpmRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,16 +379,18 @@ export async function fetchAllPackageVersions(packageName: string): Promise<Pack

const promise = (async () => {
const encodedName = encodePackageName(packageName)
const data = await $fetch<{ versions: Record<string, unknown>; time: Record<string, string> }>(
`${NPM_REGISTRY}/${encodedName}`,
)

return Object.keys(data.versions)
.filter(v => data.time[v])
.map(version => ({
const data = await $fetch<{
versions: Record<string, { deprecated?: string }>
time: Record<string, string>
}>(`${NPM_REGISTRY}/${encodedName}`)

return Object.entries(data.versions)
.filter(([v]) => data.time[v])
.map(([version, versionData]) => ({
version,
time: data.time[version],
hasProvenance: false, // Would need to check dist.attestations for each version
deprecated: versionData.deprecated,
}))
.sort((a, b) => compareVersions(b.version, a.version))
})()
Expand Down
31 changes: 31 additions & 0 deletions app/pages/[...package].vue
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,20 @@ const latestVersion = computed(() => {
return pkg.value.versions[latestTag] ?? null
})

const deprecationNotice = computed(() => {
if (!displayVersion.value?.deprecated) return null

const isLatestDeprecated = !!latestVersion.value?.deprecated

// If latest is deprecated, show "package deprecated"
if (isLatestDeprecated) {
return { type: 'package' as const, message: displayVersion.value.deprecated }
}

// Otherwise show "version deprecated"
return { type: 'version' as const, message: displayVersion.value.deprecated }
})

const hasDependencies = computed(() => {
if (!displayVersion.value) return false
const deps = displayVersion.value.dependencies
Expand Down Expand Up @@ -398,6 +412,23 @@ defineOgImageComponent('Package', {
</div>
</div>

<div
v-if="deprecationNotice"
class="border border-red-400 bg-red-400/10 rounded-lg px-3 py-2 text-base text-red-400"
>
<h2 class="font-medium mb-2">
{{
deprecationNotice.type === 'package'
? 'This package has been deprecated.'
: 'This version has been deprecated.'
}}
</h2>
<p v-if="deprecationNotice.message" class="text-base m-0">
<MarkdownText :text="deprecationNotice.message" />
</p>
<p v-else class="text-base m-0 italic">No reason provided</p>
</div>

<!-- Stats grid -->
<dl class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-3 sm:gap-4 mt-4 sm:mt-6">
<div v-if="pkg.license" class="space-y-1">
Expand Down
1 change: 1 addition & 0 deletions shared/types/npm-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface PackageVersionInfo {
version: string
time?: string
hasProvenance: boolean
deprecated?: string
}

/**
Expand Down