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
8 changes: 4 additions & 4 deletions app/components/PackageVersions.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script setup lang="ts">
import type { PackumentVersion, PackageVersionInfo } from '#shared/types'
import type { RouteLocationRaw } from 'vue-router'
import { compare } from 'semver'
import {
buildVersionToTagsMap,
compareVersions,
filterExcludedTags,
getPrereleaseChannel,
parseVersion,
Expand Down Expand Up @@ -90,7 +90,7 @@ const allTagRows = computed(() => {
deprecated: versionData?.deprecated,
} as VersionDisplay,
}))
.sort((a, b) => compareVersions(b.primaryVersion.version, a.primaryVersion.version))
.sort((a, b) => compare(b.primaryVersion.version, a.primaryVersion.version))
})

// Check if the whole package is deprecated (latest version is deprecated)
Expand Down Expand Up @@ -177,7 +177,7 @@ function processLoadedVersions(allVersions: PackageVersionInfo[]) {
const vChannel = getPrereleaseChannel(v.version)
return vParsed.major === tagParsed.major && vChannel === tagChannel
})
.sort((a, b) => compareVersions(b.version, a.version))
.sort((a, b) => compare(b.version, a.version))
.map(v => ({
version: v.version,
time: v.time,
Expand Down Expand Up @@ -214,7 +214,7 @@ function processLoadedVersions(allVersions: PackageVersionInfo[]) {

// Sort within each major
for (const versions of byMajor.values()) {
versions.sort((a, b) => compareVersions(b.version, a.version))
versions.sort((a, b) => compare(b.version, a.version))
}

// Build major groups sorted by major descending
Expand Down
6 changes: 4 additions & 2 deletions app/composables/useNpmRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import type {
PackageVersionInfo,
} from '#shared/types'
import type { ReleaseType } from 'semver'
import { maxSatisfying, prerelease, major, minor, diff, gt } from 'semver'
import { maxSatisfying, prerelease, major, minor, diff, gt, compare } from 'semver'
import { isExactVersion } from '~/utils/versions'
import { extractInstallScriptsInfo } from '~/utils/install-scripts'

const NPM_REGISTRY = 'https://registry.npmjs.org'
const NPM_API = 'https://api.npmjs.org'
Expand Down Expand Up @@ -432,7 +434,7 @@ export async function fetchAllPackageVersions(packageName: string): Promise<Pack
hasProvenance: false, // Would need to check dist.attestations for each version
deprecated: versionData.deprecated,
}))
.sort((a, b) => compareVersions(b.version, a.version))
.sort((a, b) => compare(b.version, a.version))
})()

allVersionsCache.set(packageName, promise)
Expand Down
27 changes: 2 additions & 25 deletions app/utils/versions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { valid } from 'semver'
import { compare, valid } from 'semver'

/**
* Utilities for handling npm package versions and dist-tags
Expand Down Expand Up @@ -39,29 +39,6 @@ export function parseVersion(version: string): ParsedVersion {
}
}

/**
* Compare two semver versions for sorting
* Returns positive if a > b, negative if a < b, 0 if equal
* @param a - First version string
* @param b - Second version string
* @returns Comparison result for sorting
*/
export function compareVersions(a: string, b: string): number {
const va = parseVersion(a)
const vb = parseVersion(b)

if (va.major !== vb.major) return va.major - vb.major
if (va.minor !== vb.minor) return va.minor - vb.minor
if (va.patch !== vb.patch) return va.patch - vb.patch

// Stable versions (no prerelease) are greater than prereleases
if (va.prerelease && vb.prerelease) return va.prerelease.localeCompare(vb.prerelease)
if (va.prerelease) return -1
if (vb.prerelease) return 1

return 0
}

/**
* Extract the prerelease channel from a version string
* @param version - The version string (e.g., "1.0.0-beta.1")
Expand Down Expand Up @@ -145,7 +122,7 @@ export function buildTaggedVersionRows(distTags: Record<string, string>): Tagged
tags,
version,
}))
.sort((a, b) => compareVersions(b.version, a.version))
.sort((a, b) => compare(b.version, a.version))
}

/**
Expand Down
39 changes: 0 additions & 39 deletions test/unit/versions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest'
import {
buildTaggedVersionRows,
buildVersionToTagsMap,
compareVersions,
filterExcludedTags,
getPrereleaseChannel,
parseVersion,
Expand Down Expand Up @@ -58,44 +57,6 @@ describe('parseVersion', () => {
})
})

describe('compareVersions', () => {
it('compares major versions', () => {
expect(compareVersions('2.0.0', '1.0.0')).toBeGreaterThan(0)
expect(compareVersions('1.0.0', '2.0.0')).toBeLessThan(0)
})

it('compares minor versions', () => {
expect(compareVersions('1.2.0', '1.1.0')).toBeGreaterThan(0)
expect(compareVersions('1.1.0', '1.2.0')).toBeLessThan(0)
})

it('compares patch versions', () => {
expect(compareVersions('1.0.2', '1.0.1')).toBeGreaterThan(0)
expect(compareVersions('1.0.1', '1.0.2')).toBeLessThan(0)
})

it('ranks stable above prerelease', () => {
expect(compareVersions('1.0.0', '1.0.0-beta.1')).toBeGreaterThan(0)
expect(compareVersions('1.0.0-beta.1', '1.0.0')).toBeLessThan(0)
})

it('compares prereleases alphabetically', () => {
expect(compareVersions('1.0.0-beta.1', '1.0.0-alpha.1')).toBeGreaterThan(0)
expect(compareVersions('1.0.0-alpha.1', '1.0.0-beta.1')).toBeLessThan(0)
})

it('returns 0 for equal versions', () => {
expect(compareVersions('1.0.0', '1.0.0')).toBe(0)
expect(compareVersions('1.0.0-beta.1', '1.0.0-beta.1')).toBe(0)
})

it('sorts Nuxt versions correctly', () => {
const versions = ['3.21.0', '4.0.0-alpha.4', '4.0.0-rc.0', '4.3.0']
const sorted = [...versions].sort((a, b) => compareVersions(b, a))
expect(sorted).toEqual(['4.3.0', '4.0.0-rc.0', '4.0.0-alpha.4', '3.21.0'])
})
})

describe('getPrereleaseChannel', () => {
it('returns empty string for stable versions', () => {
expect(getPrereleaseChannel('1.0.0')).toBe('')
Expand Down