Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion app/components/PackageCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const emit = defineEmits<{
class="font-mono text-sm sm:text-base font-medium text-fg group-hover:text-fg transition-colors duration-200 min-w-0 break-all"
>
<NuxtLink
:to="{ name: 'package', params: { package: result.package.name.split('/') } }"
:to="{ name: 'package', params: parsePackageRouteParams(result.package.name) }"
:prefetch-on="prefetch ? 'visibility' : 'interaction'"
class="focus-visible:outline-none decoration-none scroll-mt-48 scroll-mb-6 after:content-[''] after:absolute after:inset-0"
:data-result-index="index"
Expand Down
12 changes: 6 additions & 6 deletions app/components/PackageDependencies.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const sortedOptionalDependencies = computed(() => {
class="flex items-center justify-between py-1 text-sm gap-2"
>
<NuxtLink
:to="{ name: 'package', params: { package: dep.split('/') } }"
:to="{ name: 'package', params: parsePackageRouteParams(dep) }"
class="font-mono text-fg-muted hover:text-fg transition-colors duration-200 truncate min-w-0"
>
{{ dep }}
Expand All @@ -75,7 +75,7 @@ const sortedOptionalDependencies = computed(() => {
<span class="i-carbon-warning-alt w-3 h-3 block" />
</span>
<NuxtLink
:to="{ name: 'package', params: { package: [...dep.split('/'), 'v', version] } }"
:to="{ name: 'package', params: { ...parsePackageRouteParams(dep), version } }"
class="font-mono text-xs text-right truncate"
:class="getVersionClass(outdatedDeps[dep])"
:title="outdatedDeps[dep] ? getOutdatedTooltip(outdatedDeps[dep]) : version"
Expand Down Expand Up @@ -114,7 +114,7 @@ const sortedOptionalDependencies = computed(() => {
>
<div class="flex items-center gap-2 min-w-0">
<NuxtLink
:to="{ name: 'package', params: { package: peer.name.split('/') } }"
:to="{ name: 'package', params: parsePackageRouteParams(peer.name) }"
class="font-mono text-fg-muted hover:text-fg transition-colors duration-200 truncate"
>
{{ peer.name }}
Expand All @@ -130,7 +130,7 @@ const sortedOptionalDependencies = computed(() => {
<NuxtLink
:to="{
name: 'package',
params: { package: [...peer.name.split('/'), 'v', peer.version] },
params: { ...parsePackageRouteParams(peer.name), version: peer.version },
}"
class="font-mono text-xs text-fg-subtle max-w-[40%] text-right truncate"
:title="peer.version"
Expand Down Expand Up @@ -170,13 +170,13 @@ const sortedOptionalDependencies = computed(() => {
class="flex items-center justify-between py-1 text-sm gap-2"
>
<NuxtLink
:to="{ name: 'package', params: { package: dep.split('/') } }"
:to="{ name: 'package', params: parsePackageRouteParams(dep) }"
class="font-mono text-fg-muted hover:text-fg transition-colors duration-200 truncate min-w-0"
>
{{ dep }}
</NuxtLink>
<NuxtLink
:to="{ name: 'package', params: { package: [...dep.split('/'), 'v', version] } }"
:to="{ name: 'package', params: { ...parsePackageRouteParams(dep), version } }"
class="font-mono text-xs text-fg-subtle max-w-[50%] text-right truncate"
:title="version"
>
Expand Down
2 changes: 1 addition & 1 deletion app/components/PackageInstallScripts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const isExpanded = shallowRef(false)
class="flex items-center justify-between py-0.5 text-sm gap-2"
>
<NuxtLink
:to="{ name: 'package', params: { package: dep.split('/') } }"
:to="{ name: 'package', params: parsePackageRouteParams(dep) }"
class="font-mono text-fg-muted hover:text-fg transition-colors duration-200 truncate min-w-0"
>
{{ dep }}
Expand Down
2 changes: 1 addition & 1 deletion app/components/PackageVersions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function hasProvenance(version: PackumentVersion | undefined): boolean {
function versionRoute(version: string): RouteLocationRaw {
return {
name: 'package',
params: { package: [...props.packageName.split('/'), 'v', version] },
params: { ...parsePackageRouteParams(props.packageName), version },
}
}

Expand Down
5 changes: 5 additions & 0 deletions app/composables/usePackageRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function parsePackageRouteParams(pkg: string) {
const [org, name] = pkg.startsWith('@') ? pkg.split('/') : [null, pkg]

return { org, name }
}
68 changes: 20 additions & 48 deletions app/pages/[...package].vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,37 @@ import { areUrlsEquivalent } from '#shared/utils/url'

definePageMeta({
name: 'package',
alias: ['/package/:package(.*)*'],
/**
* Supported patterns:
* /nuxt → packageName: "nuxt", requestedVersion: null
* /nuxt/v/4.2.0 → packageName: "nuxt", requestedVersion: "4.2.0"
* /@nuxt/kit → packageName: "@nuxt/kit", requestedVersion: null
* /@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0"
* /axios@1.13.3 → packageName: "axios", requestedVersion: "1.13.3"
* /@nuxt/kit@1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0"
*/
path: '/:org(@[^/]+/)?:name([^@/]+):version()?',
alias: [
'/:org(@[^/]+/)?:name([^@/]+)/v/:version()?',
'/package/:org(@[^/]+/)?:name([^@/]+):version()?',
'/package/:org(@[^/]+/)?:name([^@/]+)/v/:version()?',
],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm learning so much!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not quite working - I want to render the version conditionally, and only if there is one 😞 - optionality of version is killing me here....

I'll have to implement slightly differently I fear.

in the next vue-router version we'll get proper matching

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})

const route = useRoute('package')

const router = useRouter()

// Parse package name and optional version from URL
// Patterns:
// /nuxt → packageName: "nuxt", requestedVersion: null
// /nuxt/v/4.2.0 → packageName: "nuxt", requestedVersion: "4.2.0"
// /@nuxt/kit → packageName: "@nuxt/kit", requestedVersion: null
// /@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0"
// /axios@1.13.3 → packageName: "axios", requestedVersion: "1.13.3"
// /@nuxt/kit@1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0"
const parsedRoute = computed(() => {
const segments = route.params.package || []

// Find the /v/ separator for version
const vIndex = segments.indexOf('v')
if (vIndex !== -1 && vIndex < segments.length - 1) {
return {
packageName: segments.slice(0, vIndex).join('/'),
requestedVersion: segments.slice(vIndex + 1).join('/'),
}
}

// Parse @ versioned package
const fullPath = segments.join('/')
const versionMatch = fullPath.match(/^(@[^/]+\/[^/]+|[^/]+)@([^/]+)$/)
if (versionMatch) {
const [, packageName, requestedVersion] = versionMatch as [string, string, string]
return {
packageName,
requestedVersion,
}
}

return {
packageName: fullPath,
requestedVersion: null as string | null,
}
})

const packageName = computed(() => parsedRoute.value.packageName)
const requestedVersion = computed(() => parsedRoute.value.requestedVersion)
const orgName = computed(() => route.params.org)
const requestedVersion = computed(() => route.params.version || null)
const packageName = computed(() =>
orgName.value ? `${orgName.value}/${route.params.name}` : route.params.name,
)

if (import.meta.server) {
assertValidPackageName(packageName.value)
}

// Extract org name from scoped package (e.g., "@nuxt/kit" -> "nuxt")
const orgName = computed(() => {
const name = packageName.value
if (!name.startsWith('@')) return null
const match = name.match(/^@([^/]+)\//)
return match ? match[1] : null
})

const { data: pkg, status, error, resolvedVersion } = usePackage(packageName, requestedVersion)

const { data: downloads } = usePackageDownloads(packageName, 'last-week')
Expand Down
7 changes: 3 additions & 4 deletions app/pages/code/[...path].vue
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,10 @@ const orgName = computed(() => {

// Build route object for package link (with optional version)
function packageRoute(ver?: string | null) {
const segments = packageName.value.split('/')
if (ver) {
segments.push('v', ver)
return {
name: 'package' as const,
params: { ...parsePackageRouteParams(packageName.value), version: ver },
}
return { name: 'package' as const, params: { package: segments } }
}

// Format file size
Expand Down
2 changes: 1 addition & 1 deletion app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ defineOgImageComponent('Default')
:key="pkg"
>
<NuxtLink
:to="{ name: 'package', params: { package: [pkg] } }"
:to="{ name: 'package', params: parsePackageRouteParams(pkg) }"
class="link-subtle font-mono text-sm inline-flex items-center gap-2 group"
>
<span
Expand Down
Loading