Skip to content

Commit 6d73887

Browse files
authored
Merge branch 'main' into renovate/pin-dependencies
2 parents 0ba4482 + 2a5ed7d commit 6d73887

12 files changed

Lines changed: 1041 additions & 109 deletions

File tree

app/components/PackageDependencies.vue

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<script setup lang="ts">
2+
import { useOutdatedDependencies, getOutdatedTooltip } from '~/composables/useNpmRegistry'
3+
import type { OutdatedDependencyInfo } from '~/composables/useNpmRegistry'
4+
25
const props = defineProps<{
36
packageName: string
47
dependencies?: Record<string, string>
@@ -7,6 +10,23 @@ const props = defineProps<{
710
optionalDependencies?: Record<string, string>
811
}>()
912
13+
// Fetch outdated info for dependencies
14+
const outdatedDeps = useOutdatedDependencies(() => props.dependencies)
15+
16+
/**
17+
* Get CSS class for a dependency version based on outdated status
18+
*/
19+
function getVersionClass(info: OutdatedDependencyInfo | undefined): string {
20+
if (!info) return 'text-fg-subtle'
21+
22+
// Red for major versions behind
23+
if (info.majorsBehind > 0) return 'text-red-500 cursor-help'
24+
// Orange for minor versions behind
25+
if (info.minorsBehind > 0) return 'text-orange-500 cursor-help'
26+
// Yellow for patch versions behind
27+
return 'text-yellow-500 cursor-help'
28+
}
29+
1030
// Expanded state for each section
1131
const depsExpanded = ref(false)
1232
const peerDepsExpanded = ref(false)
@@ -61,11 +81,26 @@ const sortedOptionalDependencies = computed(() => {
6181
>
6282
{{ dep }}
6383
</NuxtLink>
64-
<span
65-
class="font-mono text-xs text-fg-subtle max-w-[50%] text-right truncate"
66-
:title="version"
67-
>
68-
{{ version }}
84+
<span class="flex items-center gap-1">
85+
<span
86+
v-if="outdatedDeps[dep]"
87+
class="shrink-0"
88+
:class="getVersionClass(outdatedDeps[dep])"
89+
:title="getOutdatedTooltip(outdatedDeps[dep])"
90+
aria-hidden="true"
91+
>
92+
<span class="i-carbon-warning-alt w-3 h-3" />
93+
</span>
94+
<span
95+
class="font-mono text-xs text-right truncate"
96+
:class="getVersionClass(outdatedDeps[dep])"
97+
:title="outdatedDeps[dep] ? getOutdatedTooltip(outdatedDeps[dep]) : version"
98+
>
99+
{{ version }}
100+
</span>
101+
<span v-if="outdatedDeps[dep]" class="sr-only">
102+
({{ getOutdatedTooltip(outdatedDeps[dep]) }})
103+
</span>
69104
</span>
70105
</li>
71106
</ul>

app/components/PackageDownloadStats.vue

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,7 @@ const config = computed(() => ({
7575
<!-- Download stats -->
7676
<section>
7777
<div class="flex items-center justify-between mb-3">
78-
<h2 id="dependencies-heading" class="text-xs text-fg-subtle uppercase tracking-wider">
79-
Weekly Downloads
80-
</h2>
78+
<h2 class="text-xs text-fg-subtle uppercase tracking-wider">Weekly Downloads</h2>
8179
</div>
8280
<div class="w-full overflow-hidden">
8381
<ClientOnly>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<script setup lang="ts">
2+
import type { ModuleFormat, TypesStatus } from '#shared/utils/package-analysis'
3+
4+
const props = defineProps<{
5+
packageName: string
6+
version?: string
7+
}>()
8+
9+
interface PackageAnalysisResponse {
10+
package: string
11+
version: string
12+
moduleFormat: ModuleFormat
13+
types: TypesStatus
14+
engines?: {
15+
node?: string
16+
npm?: string
17+
}
18+
}
19+
20+
const { data: analysis, status } = useLazyFetch<PackageAnalysisResponse>(
21+
() => {
22+
const base = `/api/registry/analysis/${props.packageName}`
23+
return props.version ? `${base}/v/${props.version}` : base
24+
},
25+
{
26+
server: false, // Client-side only to avoid blocking initial render
27+
},
28+
)
29+
30+
const moduleFormatLabel = computed(() => {
31+
if (!analysis.value) return null
32+
switch (analysis.value.moduleFormat) {
33+
case 'esm':
34+
return 'ESM'
35+
case 'cjs':
36+
return 'CJS'
37+
case 'dual':
38+
return 'CJS/ESM'
39+
default:
40+
return null
41+
}
42+
})
43+
44+
const moduleFormatTooltip = computed(() => {
45+
if (!analysis.value) return ''
46+
switch (analysis.value.moduleFormat) {
47+
case 'esm':
48+
return 'ES Modules only'
49+
case 'cjs':
50+
return 'CommonJS only'
51+
case 'dual':
52+
return 'Supports both CommonJS and ES Modules'
53+
default:
54+
return 'Unknown module format'
55+
}
56+
})
57+
58+
const hasTypes = computed(() => {
59+
if (!analysis.value) return false
60+
return analysis.value.types?.kind === 'included' || analysis.value.types?.kind === '@types'
61+
})
62+
63+
const typesTooltip = computed(() => {
64+
if (!analysis.value) return ''
65+
switch (analysis.value.types?.kind) {
66+
case 'included':
67+
return 'TypeScript types included'
68+
case '@types':
69+
return `Types from ${analysis.value.types.packageName}`
70+
default:
71+
return ''
72+
}
73+
})
74+
75+
const typesHref = computed(() => {
76+
if (!analysis.value) return null
77+
if (analysis.value.types.kind === '@types') {
78+
return `/${analysis.value.types.packageName}`
79+
}
80+
return null
81+
})
82+
</script>
83+
84+
<template>
85+
<ul v-if="analysis" class="flex items-center gap-1.5 list-none m-0 p-0">
86+
<!-- TypeScript types -->
87+
<li v-if="hasTypes">
88+
<component
89+
:is="typesHref ? 'NuxtLink' : 'span'"
90+
:to="typesHref"
91+
class="inline-flex items-center px-1.5 py-0.5 font-mono text-xs text-fg-muted bg-bg-muted border border-border rounded transition-colors duration-200"
92+
:class="typesHref ? 'hover:text-fg hover:border-border-hover' : ''"
93+
:title="typesTooltip"
94+
>
95+
TS
96+
</component>
97+
</li>
98+
99+
<!-- Module format -->
100+
<li v-if="moduleFormatLabel">
101+
<span
102+
class="inline-flex items-center px-1.5 py-0.5 font-mono text-xs text-fg-muted bg-bg-muted border border-border rounded transition-colors duration-200"
103+
:title="moduleFormatTooltip"
104+
>
105+
{{ moduleFormatLabel }}
106+
</span>
107+
</li>
108+
</ul>
109+
</template>

app/components/PackageVersions.vue

Lines changed: 30 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getPrereleaseChannel,
99
parseVersion,
1010
} from '~/utils/versions'
11+
import { fetchAllPackageVersions } from '~/composables/useNpmRegistry'
1112
1213
const props = defineProps<{
1314
packageName: string
@@ -107,18 +108,12 @@ const otherMajorGroups = ref<
107108
>([])
108109
const otherVersionsLoading = ref(false)
109110
110-
// Cached full version list
111+
// Cached full version list (local to component instance)
111112
const allVersionsCache = ref<PackageVersionInfo[] | null>(null)
112113
const loadingVersions = ref(false)
113114
const hasLoadedAll = ref(false)
114115
115-
// npm registry packument type (simplified)
116-
interface NpmPackument {
117-
versions: Record<string, unknown>
118-
time: Record<string, string>
119-
}
120-
121-
// Load all versions directly from npm registry
116+
// Load all versions using shared function
122117
async function loadAllVersions(): Promise<PackageVersionInfo[]> {
123118
if (allVersionsCache.value) return allVersionsCache.value
124119
@@ -136,23 +131,7 @@ async function loadAllVersions(): Promise<PackageVersionInfo[]> {
136131
137132
loadingVersions.value = true
138133
try {
139-
// Fetch directly from npm registry
140-
const encodedName = props.packageName.startsWith('@')
141-
? `@${encodeURIComponent(props.packageName.slice(1))}`
142-
: encodeURIComponent(props.packageName)
143-
144-
const data = await $fetch<NpmPackument>(`https://registry.npmjs.org/${encodedName}`)
145-
146-
// Convert to our format
147-
const versions: PackageVersionInfo[] = Object.keys(data.versions)
148-
.filter(v => data.time[v])
149-
.map(version => ({
150-
version,
151-
time: data.time[version],
152-
hasProvenance: false,
153-
}))
154-
.sort((a, b) => compareVersions(b.version, a.version))
155-
134+
const versions = await fetchAllPackageVersions(props.packageName)
156135
allVersionsCache.value = versions
157136
hasLoadedAll.value = true
158137
return versions
@@ -290,14 +269,6 @@ function toggleMajorGroup(index: number) {
290269
function getTagVersions(tag: string): VersionDisplay[] {
291270
return tagVersions.value.get(tag) ?? []
292271
}
293-
294-
function formatDate(dateStr: string): string {
295-
return new Date(dateStr).toLocaleDateString('en-US', {
296-
year: 'numeric',
297-
month: 'short',
298-
day: 'numeric',
299-
})
300-
}
301272
</script>
302273

303274
<template>
@@ -341,13 +312,14 @@ function formatDate(dateStr: string): string {
341312
{{ row.primaryVersion.version }}
342313
</NuxtLink>
343314
<div class="flex items-center gap-2 shrink-0">
344-
<time
315+
<NuxtTime
345316
v-if="row.primaryVersion.time"
346317
:datetime="row.primaryVersion.time"
318+
year="numeric"
319+
month="short"
320+
day="numeric"
347321
class="text-xs text-fg-subtle"
348-
>
349-
{{ formatDate(row.primaryVersion.time) }}
350-
</time>
322+
/>
351323
<ProvenanceBadge
352324
v-if="row.primaryVersion.hasProvenance"
353325
:package-name="packageName"
@@ -384,9 +356,14 @@ function formatDate(dateStr: string): string {
384356
{{ v.version }}
385357
</NuxtLink>
386358
<div class="flex items-center gap-2 shrink-0">
387-
<time v-if="v.time" :datetime="v.time" class="text-[10px] text-fg-subtle">
388-
{{ formatDate(v.time) }}
389-
</time>
359+
<NuxtTime
360+
v-if="v.time"
361+
:datetime="v.time"
362+
class="text-[10px] text-fg-subtle"
363+
year="numeric"
364+
month="short"
365+
day="numeric"
366+
/>
390367
<ProvenanceBadge
391368
v-if="v.hasProvenance"
392369
:package-name="packageName"
@@ -451,13 +428,14 @@ function formatDate(dateStr: string): string {
451428
{{ row.primaryVersion.version }}
452429
</NuxtLink>
453430
<div class="flex items-center gap-2 shrink-0">
454-
<time
431+
<NuxtTime
455432
v-if="row.primaryVersion.time"
456433
:datetime="row.primaryVersion.time"
457434
class="text-[10px] text-fg-subtle"
458-
>
459-
{{ formatDate(row.primaryVersion.time) }}
460-
</time>
435+
year="numeric"
436+
month="short"
437+
day="numeric"
438+
/>
461439
</div>
462440
</div>
463441
<div v-if="row.tags.length" class="flex items-center gap-1 mt-0.5 flex-wrap">
@@ -543,9 +521,14 @@ function formatDate(dateStr: string): string {
543521
{{ v.version }}
544522
</NuxtLink>
545523
<div class="flex items-center gap-2 shrink-0">
546-
<time v-if="v.time" :datetime="v.time" class="text-[10px] text-fg-subtle">
547-
{{ formatDate(v.time) }}
548-
</time>
524+
<NuxtTime
525+
v-if="v.time"
526+
:datetime="v.time"
527+
class="text-[10px] text-fg-subtle"
528+
year="numeric"
529+
month="short"
530+
day="numeric"
531+
/>
549532
<ProvenanceBadge
550533
v-if="v.hasProvenance"
551534
:package-name="packageName"

0 commit comments

Comments
 (0)