Skip to content

Commit ebe6ee7

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/cli-scope-team-validation
2 parents 80a6e84 + fe0260f commit ebe6ee7

32 files changed

Lines changed: 1018 additions & 188 deletions

.lighthouserc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
},
1717
"assert": {
1818
"assertions": {
19-
"categories:accessibility": ["warn", { "minScore": 0.9 }]
19+
"categories:accessibility": ["warn", { "minScore": 1 }]
2020
}
2121
},
2222
"upload": {

README.md

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,18 @@ The aim of [npmx.dev](https://npmx.dev) is to provide a better browser for the n
2929
- **Provenance indicators** – verified build badges for packages with npm provenance
3030
- **Multi-provider repository support** – stars/forks from GitHub, GitLab, Bitbucket, Codeberg, Gitee, and Sourcehut
3131
- **JSR availability** – see if scoped packages are also available on JSR
32-
- **Package badges** – module format (ESM/CJS/dual), TypeScript types, and engine constraints
32+
- **Package badges** – module format (ESM/CJS/dual), TypeScript types (with `@types/*` links), and engine constraints
3333
- **Outdated dependency indicators** – visual cues showing which dependencies are behind
3434
- **Vulnerability warnings** – security advisories from the OSV database
3535
- **Download statistics** – weekly download counts with sparkline charts
36-
- **Install size** – total install size including dependencies
36+
- **Install size** – total install size (including transitive dependencies)
3737
- **Playground links** – quick access to StackBlitz, CodeSandbox, and other demo environments from READMEs
3838
- **Infinite search** – auto-load additional search pages as you scroll
39-
- **Keyboard navigation** – press `/` to focus search, arrow keys to navigate results, Enter to select
39+
- **Keyboard navigation** – press `/` to focus search, `.` to open code viewer, arrow keys to navigate results
40+
- **Deprecation notices** – clear warnings for deprecated packages and versions
41+
- **Version range resolution** – dependency ranges (e.g., `^1.0.0`) resolve to actual installed versions
4042
- **Claim new packages** – register new package names directly from search results (via local connector)
43+
- **Clickable version tags** – navigate directly to any version from the versions list
4144

4245
### User & org pages
4346

@@ -65,10 +68,12 @@ The aim of [npmx.dev](https://npmx.dev) is to provide a better browser for the n
6568
| Install size calculation |||
6669
| JSR cross-reference |||
6770
| Vulnerability warnings |||
71+
| Deprecation notices |||
6872
| Download charts |||
6973
| Playground links |||
7074
| Keyboard navigation |||
7175
| Multi-provider repo support |||
76+
| Version range resolution |||
7277
| Dependents list || 🚧 |
7378
| Package admin (access/owners) || 🚧 |
7479
| Org/team management || 🚧 |
@@ -105,12 +110,13 @@ npmx.dev supports npm permalinks – just replace `npmjs.com` with `npmx.dev
105110

106111
npmx.dev also supports shorter, cleaner URLs:
107112

108-
| Pattern | Example |
109-
| -------------- | -------------------------------------------------- |
110-
| `/<package>` | [`/nuxt`](https://npmx.dev/nuxt) |
111-
| `/@scope/name` | [`/@nuxt/kit`](https://npmx.dev/@nuxt/kit) |
112-
| `/@org` | [`/@nuxt`](https://npmx.dev/@nuxt) |
113-
| `/~username` | [`/~sindresorhus`](https://npmx.dev/~sindresorhus) |
113+
| Pattern | Example |
114+
| ------------------ | -------------------------------------------------- |
115+
| `/<package>` | [`/nuxt`](https://npmx.dev/nuxt) |
116+
| `/<pkg>@<version>` | [`/vue@3.4.0`](https://npmx.dev/vue@3.4.0) |
117+
| `/@scope/name` | [`/@nuxt/kit`](https://npmx.dev/@nuxt/kit) |
118+
| `/@org` | [`/@nuxt`](https://npmx.dev/@nuxt) |
119+
| `/~username` | [`/~sindresorhus`](https://npmx.dev/~sindresorhus) |
114120

115121
## Tech stack
116122

@@ -122,7 +128,7 @@ npmx.dev also supports shorter, cleaner URLs:
122128

123129
## Contributing
124130

125-
I'd welcome contributions &ndash; please do feel free to poke around and improve things. See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on how to get up and running!
131+
We welcome contributions &ndash; please do feel free to poke around and improve things. See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on how to get up and running!
126132

127133
## Related projects
128134

@@ -132,7 +138,7 @@ I'd welcome contributions &ndash; please do feel free to poke around and improve
132138
- [npm-alt](https://npm.willow.sh/) &ndash; An alternative npm package browser
133139
- [npkg.lorypelli.dev](https://npkg.lorypelli.dev/) &ndash; An alternative frontend to npm made with as little client-side JavaScript as possible
134140

135-
If you're building something cool, let me know! 🙏
141+
If you're building something cool, let us know! 🙏
136142

137143
## License
138144

app/components/AppFooter.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ onUnmounted(() => {
8383
: // JS-controlled: fixed position, hidden by default, transition only after mount
8484
isScrollable
8585
? [
86-
'fixed bottom-0 left-0 right-0 z-40 translate-y-full',
86+
'fixed bottom-0 left-0 right-0 z-40',
8787
isMounted && 'transition-transform duration-300 ease-out',
88-
isVisible && 'translate-y-0',
88+
isVisible ? 'translate-y-0' : 'translate-y-full',
8989
]
9090
: 'mt-auto',
9191
]"

app/components/AppHeader.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@ withDefaults(
3030
<NuxtLink
3131
to="/search"
3232
class="link-subtle font-mono text-sm inline-flex items-center gap-2"
33+
aria-keyshortcuts="/"
3334
>
3435
search
3536
<kbd
3637
class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
37-
>/</kbd
38+
aria-hidden="true"
3839
>
40+
/
41+
</kbd>
3942
</NuxtLink>
4043
</li>
4144
<li v-if="showConnector" class="flex">

app/components/PackageCard.vue

Lines changed: 105 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,79 +20,132 @@ const emit = defineEmits<{
2020

2121
<template>
2222
<article
23-
class="group card-interactive scroll-mt-48 scroll-mb-6"
23+
class="group card-interactive scroll-mt-48 scroll-mb-6 relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50"
2424
:class="{ 'bg-bg-muted border-border-hover': selected }"
2525
>
26-
<NuxtLink
27-
:to="{ name: 'package', params: { package: result.package.name.split('/') } }"
28-
:prefetch-on="prefetch ? 'visibility' : 'interaction'"
29-
class="block focus:outline-none decoration-none scroll-mt-48 scroll-mb-6"
30-
:data-result-index="index"
31-
@focus="index != null && emit('focus', index)"
32-
@mouseenter="index != null && emit('focus', index)"
33-
>
34-
<header class="flex items-start justify-between gap-4 mb-2">
35-
<component
36-
:is="headingLevel ?? 'h3'"
37-
class="font-mono text-base font-medium text-fg group-hover:text-fg transition-colors duration-200 min-w-0 break-all"
26+
<div class="mb-2 flex items-baseline justify-between gap-2">
27+
<component
28+
:is="headingLevel ?? 'h3'"
29+
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"
30+
>
31+
<NuxtLink
32+
:to="{ name: 'package', params: { package: result.package.name.split('/') } }"
33+
:prefetch-on="prefetch ? 'visibility' : 'interaction'"
34+
class="focus-visible:outline-none decoration-none scroll-mt-48 scroll-mb-6 after:content-[''] after:absolute after:inset-0"
35+
:data-result-index="index"
36+
@focus="index != null && emit('focus', index)"
37+
@mouseenter="index != null && emit('focus', index)"
3838
>
3939
{{ result.package.name }}
40-
</component>
41-
<div class="flex items-center gap-1.5 shrink-0 max-w-32">
40+
</NuxtLink>
41+
</component>
42+
<!-- Mobile: version next to package name -->
43+
<div class="sm:hidden text-fg-subtle flex items-center gap-1.5 shrink-0">
44+
<span
45+
v-if="result.package.version"
46+
class="font-mono text-xs truncate max-w-20"
47+
:title="result.package.version"
48+
>
49+
v{{ result.package.version }}
50+
</span>
51+
<ProvenanceBadge
52+
v-if="result.package.publisher?.trustedPublisher"
53+
:provider="result.package.publisher.trustedPublisher.id"
54+
:package-name="result.package.name"
55+
:version="result.package.version"
56+
:linked="false"
57+
compact
58+
/>
59+
</div>
60+
</div>
61+
<div class="flex justify-between items-start gap-4 sm:gap-8">
62+
<div class="min-w-0">
63+
<p
64+
v-if="result.package.description"
65+
class="text-fg-muted text-xs sm:text-sm line-clamp-2 mb-2 sm:mb-3"
66+
>
67+
<MarkdownText :text="result.package.description" />
68+
</p>
69+
<div class="flex flex-wrap items-center gap-x-3 sm:gap-x-4 gap-y-2 text-xs text-fg-subtle">
70+
<dl v-if="showPublisher || result.package.date" class="flex items-center gap-4 m-0">
71+
<div
72+
v-if="showPublisher && result.package.publisher?.username"
73+
class="flex items-center gap-1.5"
74+
>
75+
<dt class="sr-only">Publisher</dt>
76+
<dd class="font-mono">@{{ result.package.publisher.username }}</dd>
77+
</div>
78+
<div v-if="result.package.date" class="flex items-center gap-1.5">
79+
<dt class="sr-only">Updated</dt>
80+
<dd>
81+
<NuxtTime
82+
:datetime="result.package.date"
83+
year="numeric"
84+
month="short"
85+
day="numeric"
86+
/>
87+
</dd>
88+
</div>
89+
</dl>
90+
</div>
91+
<!-- Mobile: downloads on separate row -->
92+
<dl
93+
v-if="result.downloads?.weekly"
94+
class="sm:hidden flex items-center gap-4 mt-2 text-xs text-fg-subtle m-0"
95+
>
96+
<div class="flex items-center gap-1.5">
97+
<dt class="sr-only">Weekly downloads</dt>
98+
<dd class="flex items-center gap-1.5">
99+
<span class="i-carbon-chart-line w-3.5 h-3.5 inline-block" aria-hidden="true" />
100+
<span class="font-mono">{{ formatNumber(result.downloads.weekly) }}/w</span>
101+
</dd>
102+
</div>
103+
</dl>
104+
</div>
105+
<!-- Desktop: version and downloads on right side -->
106+
<div class="hidden sm:flex flex-col gap-2 shrink-0">
107+
<div class="text-fg-subtle flex items-start gap-2 justify-end">
42108
<span
43109
v-if="result.package.version"
44-
class="font-mono text-xs text-fg-subtle truncate"
110+
class="font-mono text-xs truncate max-w-32"
45111
:title="result.package.version"
46112
>
47113
v{{ result.package.version }}
48114
</span>
49-
<ProvenanceBadge
50-
v-if="result.package.publisher?.trustedPublisher"
51-
:provider="result.package.publisher.trustedPublisher.id"
52-
:package-name="result.package.name"
53-
:version="result.package.version"
54-
compact
55-
/>
56-
</div>
57-
</header>
58-
59-
<p v-if="result.package.description" class="text-fg-muted text-sm line-clamp-2 mb-3">
60-
<MarkdownText :text="result.package.description" />
61-
</p>
62-
63-
<footer class="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-fg-subtle">
64-
<dl v-if="showPublisher || result.package.date" class="flex items-center gap-4 m-0">
65115
<div
66-
v-if="showPublisher && result.package.publisher?.username"
67-
class="flex items-center gap-1.5"
116+
v-if="result.package.publisher?.trustedPublisher"
117+
class="flex items-center gap-1.5 shrink-0 max-w-32"
68118
>
69-
<dt class="sr-only">Publisher</dt>
70-
<dd class="font-mono">@{{ result.package.publisher.username }}</dd>
71-
</div>
72-
<div v-if="result.package.date" class="flex items-center gap-1.5">
73-
<dt class="sr-only">Updated</dt>
74-
<dd>
75-
<NuxtTime
76-
:datetime="result.package.date"
77-
year="numeric"
78-
month="short"
79-
day="numeric"
80-
/>
81-
</dd>
119+
<ProvenanceBadge
120+
:provider="result.package.publisher.trustedPublisher.id"
121+
:package-name="result.package.name"
122+
:version="result.package.version"
123+
:linked="false"
124+
compact
125+
/>
82126
</div>
83-
</dl>
84-
</footer>
85-
</NuxtLink>
127+
</div>
128+
<div
129+
v-if="result.downloads?.weekly"
130+
class="text-fg-subtle gap-2 flex items-center justify-end"
131+
>
132+
<span class="i-carbon-chart-line w-3.5 h-3.5 inline-block" aria-hidden="true" />
133+
<span class="font-mono text-xs">
134+
{{ formatNumber(result.downloads.weekly) }} / week
135+
</span>
136+
</div>
137+
</div>
138+
</div>
86139

87140
<ul
88141
v-if="result.package.keywords?.length"
89142
aria-label="Keywords"
90-
class="flex flex-wrap gap-1.5 mt-3 pt-3 border-t border-border list-none m-0 p-0"
143+
class="relative z-10 flex flex-wrap gap-1.5 mt-3 pt-3 border-t border-border list-none m-0 p-0"
91144
>
92145
<li v-for="keyword in result.package.keywords.slice(0, 5)" :key="keyword">
93146
<NuxtLink
94147
:to="{ name: 'search', query: { q: `keywords:${keyword}` } }"
95-
class="tag decoration-none"
148+
class="tag decoration-none focus-visible:ring-2 focus-visible:ring-fg/50 focus-visible:outline-none"
96149
>
97150
{{ keyword }}
98151
</NuxtLink>

app/components/PackageDependencies.vue

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
<script setup lang="ts">
2-
import { useOutdatedDependencies, getOutdatedTooltip } from '~/composables/useNpmRegistry'
3-
import type { OutdatedDependencyInfo } from '~/composables/useNpmRegistry'
4-
52
const props = defineProps<{
63
packageName: string
74
dependencies?: Record<string, string>
@@ -13,20 +10,6 @@ const props = defineProps<{
1310
// Fetch outdated info for dependencies
1411
const outdatedDeps = useOutdatedDependencies(() => props.dependencies)
1512
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-
3013
// Expanded state for each section
3114
const depsExpanded = ref(false)
3215
const peerDepsExpanded = ref(false)
@@ -89,15 +72,16 @@ const sortedOptionalDependencies = computed(() => {
8972
:title="getOutdatedTooltip(outdatedDeps[dep])"
9073
aria-hidden="true"
9174
>
92-
<span class="i-carbon-warning-alt w-3 h-3" />
75+
<span class="i-carbon-warning-alt w-3 h-3 block" />
9376
</span>
94-
<span
77+
<NuxtLink
78+
:to="{ name: 'package', params: { package: [...dep.split('/'), 'v', version] } }"
9579
class="font-mono text-xs text-right truncate"
9680
:class="getVersionClass(outdatedDeps[dep])"
9781
:title="outdatedDeps[dep] ? getOutdatedTooltip(outdatedDeps[dep]) : version"
9882
>
9983
{{ version }}
100-
</span>
84+
</NuxtLink>
10185
<span v-if="outdatedDeps[dep]" class="sr-only">
10286
({{ getOutdatedTooltip(outdatedDeps[dep]) }})
10387
</span>
@@ -143,12 +127,16 @@ const sortedOptionalDependencies = computed(() => {
143127
optional
144128
</span>
145129
</div>
146-
<span
130+
<NuxtLink
131+
:to="{
132+
name: 'package',
133+
params: { package: [...peer.name.split('/'), 'v', peer.version] },
134+
}"
147135
class="font-mono text-xs text-fg-subtle max-w-[40%] text-right truncate"
148136
:title="peer.version"
149137
>
150138
{{ peer.version }}
151-
</span>
139+
</NuxtLink>
152140
</li>
153141
</ul>
154142
<button
@@ -187,12 +175,13 @@ const sortedOptionalDependencies = computed(() => {
187175
>
188176
{{ dep }}
189177
</NuxtLink>
190-
<span
178+
<NuxtLink
179+
:to="{ name: 'package', params: { package: [...dep.split('/'), 'v', version] } }"
191180
class="font-mono text-xs text-fg-subtle max-w-[50%] text-right truncate"
192181
:title="version"
193182
>
194183
{{ version }}
195-
</span>
184+
</NuxtLink>
196185
</li>
197186
</ul>
198187
<button

0 commit comments

Comments
 (0)