Skip to content
17 changes: 15 additions & 2 deletions app/composables/usePackageComparison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
Packument,
VulnerabilityTreeResult,
} from '#shared/types'
import type { PackageLikes } from '#shared/types/social'
import { encodePackageName } from '#shared/utils/npm'
import type { PackageAnalysisResponse } from './usePackageAnalysis'
import { isBinaryOnlyPackage } from '#shared/utils/binary-detection'
Expand All @@ -28,6 +29,8 @@ export const NoDependencyDisplay = {
export interface PackageComparisonData {
package: ComparisonPackage
downloads?: number
/** Total likes from atproto */
totalLikes?: number
/** Package's own unpacked size (from dist.unpackedSize) */
packageSize?: number
/** Number of direct dependencies */
Expand Down Expand Up @@ -127,7 +130,7 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter<string[]>) {
if (!latestVersion) return null

// Fetch fast additional data in parallel (optional - failures are ok)
const [downloads, analysis, vulns] = await Promise.all([
const [downloads, analysis, vulns, likes] = await Promise.all([
$fetch<{ downloads: number }>(
`https://api.npmjs.org/downloads/point/last-week/${encodePackageName(name)}`,
).catch(() => null),
Expand All @@ -137,8 +140,8 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter<string[]>) {
$fetch<VulnerabilityTreeResult>(
`/api/registry/vulnerabilities/${encodePackageName(name)}`,
).catch(() => null),
$fetch<PackageLikes>(`/api/social/likes/${name}`).catch(() => null),

@coderabbitai coderabbitai Bot Feb 5, 2026

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n app/composables/usePackageComparison.ts | sed -n '130,150p'

Repository: npmx-dev/npmx.dev

Length of output: 1254


🏁 Script executed:

# Search for the API route definition for /api/social/likes
rg -n "social/likes" --type ts --type js -B 2 -A 5

Repository: npmx-dev/npmx.dev

Length of output: 1906


🏁 Script executed:

# Find where encodePackageName is defined and used
rg -n "encodePackageName" --type ts --type js | head -20

Repository: npmx-dev/npmx.dev

Length of output: 2075


🏁 Script executed:

# Check the broader context of lines around 143 to see other API calls
cat -n app/composables/usePackageComparison.ts | sed -n '125,160p'

Repository: npmx-dev/npmx.dev

Length of output: 1890


🏁 Script executed:

# Find the API route definition for social/likes
fd -type f -name "*.ts" -o -name "*.js" | xargs rg -l "social/likes" | head -10

Repository: npmx-dev/npmx.dev

Length of output: 358


🏁 Script executed:

# Search for the server-side route handler
rg -n "api/social/likes" --type ts --type js -B 3 -A 10

Repository: npmx-dev/npmx.dev

Length of output: 3146


🏁 Script executed:

# Look for likes route in server/api
fd -path "*/server/api/*" -name "*likes*" -o -path "*/server/api/*" -name "*social*"

Repository: npmx-dev/npmx.dev

Length of output: 229


🏁 Script executed:

# Find server API routes for social/likes
find server -name "*.ts" -o -name "*.js" | xargs grep -l "social" 2>/dev/null | head -10

Repository: npmx-dev/npmx.dev

Length of output: 124


🏁 Script executed:

# Search more broadly for likes route handler
rg "likes" server/api --type-list | head -5

Repository: npmx-dev/npmx.dev

Length of output: 450


🏁 Script executed:

# List the server/api directory structure
find server/api -type f -name "*.ts" | sort

Repository: npmx-dev/npmx.dev

Length of output: 1035


🏁 Script executed:

cat -n server/api/social/likes/[...pkg].get.ts

Repository: npmx-dev/npmx.dev

Length of output: 520


Package name should be encoded for consistency and scoped package support.

The likes API call uses the raw name without encodePackageName(), unlike the other API calls in this block. This causes issues with scoped packages (e.g., @vue/core) because the forward slash in the scope becomes a path separator. The same issue exists in two other locations: app/pages/package/[...package].vue line 402 and app/components/OgImage/Package.vue line 49.

🐛 Proposed fix
-              $fetch<PackageLikes>(`/api/social/likes/${name}`).catch(() => null),
+              $fetch<PackageLikes>(`/api/social/likes/${encodePackageName(name)}`).catch(() => null),

Apply the same fix to app/pages/package/[...package].vue line 402 and app/components/OgImage/Package.vue line 49.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$fetch<PackageLikes>(`/api/social/likes/${name}`).catch(() => null),
$fetch<PackageLikes>(`/api/social/likes/${encodePackageName(name)}`).catch(() => null),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Whoops thought i did it my bad coderabbit

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.

@Sukiiu No worries at all! It's an easy thing to miss, especially since the API route uses [...pkg] which handles it server-side. Thanks for taking a look! 🐰

🐰✨
( •_•)
/>🔧

])

const versionData = pkgData.versions[latestVersion]
const packageSize = versionData?.dist?.unpackedSize

Expand Down Expand Up @@ -188,6 +191,7 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter<string[]>) {
deprecated: versionData?.deprecated,
},
isBinaryOnly: isBinary,
totalLikes: likes?.totalLikes,
}
} catch {
return null
Expand Down Expand Up @@ -299,6 +303,7 @@ function createNoDependencyData(): PackageComparisonData {
},
isNoDependency: true,
downloads: undefined,
totalLikes: undefined,
packageSize: 0,
directDeps: 0,
installSize: {
Expand Down Expand Up @@ -353,6 +358,14 @@ function computeFacetValue(
status: 'neutral',
}
}
case 'totalLikes': {
if (data.totalLikes === undefined) return null
return {
raw: data.totalLikes,
display: formatCompactNumber(data.totalLikes),
status: 'neutral',
}
}
case 'packageSize': {
// A size of zero is valid
if (data.packageSize == null) return null
Expand Down
4 changes: 4 additions & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,10 @@
"label": "Downloads/wk",
"description": "Weekly download count"
},
"totalLikes": {
"label": "Likes",
"description": "Number of likes"
},
"lastUpdated": {
"label": "Published",
"description": "When this version was published"
Expand Down
4 changes: 4 additions & 0 deletions i18n/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,10 @@
"label": "Téléch./semaine",
"description": "Nombre de téléchargements par semaine"
},
"totalLikes": {
"label": "Likes",
"description": "Nombre de likes"
},
"lastUpdated": {
"label": "Publié",
"description": "Quand cette version a été publiée"
Expand Down
4 changes: 4 additions & 0 deletions lunaria/files/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,10 @@
"label": "Downloads/wk",
"description": "Weekly download count"
},
"totalLikes": {
"label": "Likes",
"description": "Number of likes"
},
"lastUpdated": {
"label": "Published",
"description": "When this version was published"
Expand Down
4 changes: 4 additions & 0 deletions lunaria/files/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,10 @@
"label": "Downloads/wk",
"description": "Weekly download count"
},
"totalLikes": {
"label": "Likes",
"description": "Number of likes"
},
"lastUpdated": {
"label": "Published",
"description": "When this version was published"
Expand Down
4 changes: 4 additions & 0 deletions lunaria/files/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,10 @@
"label": "Téléch./semaine",
"description": "Nombre de téléchargements par semaine"
},
"totalLikes": {
"label": "Likes",
"description": "Nombre de likes"
},
"lastUpdated": {
"label": "Publié",
"description": "Quand cette version a été publiée"
Expand Down
4 changes: 4 additions & 0 deletions shared/types/comparison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type ComparisonFacet =
| 'dependencies'
| 'totalDependencies'
| 'deprecated'
| 'totalLikes'

/** Facet metadata for UI display */
export interface FacetInfo {
Expand Down Expand Up @@ -46,6 +47,9 @@ export const FACET_INFO: Record<ComparisonFacet, Omit<FacetInfo, 'id'>> = {
downloads: {
category: 'health',
},
totalLikes: {
category: 'health',
},
lastUpdated: {
category: 'health',
},
Expand Down
1 change: 1 addition & 0 deletions test/nuxt/components/compare/FacetSelector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const facetLabels: Record<ComparisonFacet, { label: string; description: string
description: 'Total number of dependencies including transitive',
},
deprecated: { label: 'Deprecated?', description: 'Whether the package is deprecated' },
totalLikes: { label: 'Likes', description: 'Number of likes' },
}

const categoryLabels: Record<string, string> = {
Expand Down
Loading