Skip to content

Commit 3af7e1b

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/detect-npx-deps
2 parents bb5676a + d193569 commit 3af7e1b

67 files changed

Lines changed: 3629 additions & 472 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/autofix.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515

1616
steps:
1717
- uses: actions/checkout@v6
18-
- run: npm i -g --force corepack && corepack enable
18+
- run: corepack enable
1919
- uses: actions/setup-node@v6
2020
with:
2121
node-version: lts/*

.github/workflows/ci.yml

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ on:
88
branches:
99
- main
1010

11+
permissions:
12+
contents: read
13+
1114
jobs:
1215
lint:
1316
runs-on: ubuntu-latest
1417

1518
steps:
1619
- uses: actions/checkout@v6
17-
- run: npm i -g --force corepack && corepack enable
20+
- run: corepack enable
1821
- uses: actions/setup-node@v6
1922
with:
2023
node-version: lts/*
@@ -31,7 +34,7 @@ jobs:
3134

3235
steps:
3336
- uses: actions/checkout@v6
34-
- run: npm i -g --force corepack && corepack enable
37+
- run: corepack enable
3538
- uses: actions/setup-node@v6
3639
with:
3740
node-version: lts/*
@@ -59,7 +62,7 @@ jobs:
5962

6063
steps:
6164
- uses: actions/checkout@v6
62-
- run: npm i -g --force corepack && corepack enable
65+
- run: corepack enable
6366
- uses: actions/setup-node@v6
6467
with:
6568
node-version: lts/*
@@ -70,3 +73,25 @@ jobs:
7073

7174
- name: 🖥️ Test project (browser)
7275
run: pnpm test:browser
76+
77+
a11y:
78+
runs-on: ubuntu-latest
79+
80+
steps:
81+
- uses: actions/checkout@v6
82+
- run: corepack enable
83+
- uses: actions/setup-node@v6
84+
with:
85+
node-version: lts/*
86+
cache: pnpm
87+
88+
- name: 📦 Install dependencies
89+
run: pnpm install
90+
91+
- name: 🏗️ Build project
92+
run: pnpm build
93+
94+
- name: ♿ Accessibility audit (Lighthouse)
95+
run: pnpx @lhci/cli autorun
96+
env:
97+
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,6 @@ coverage/
3333
# Playwright
3434
playwright-report/
3535
test-results/
36+
37+
# Lighthouse
38+
.lighthouseci

.lighthouserc.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"ci": {
3+
"collect": {
4+
"startServerCommand": "pnpm preview",
5+
"startServerReadyPattern": "Listening",
6+
"url": [
7+
"http://localhost:3000/",
8+
"http://localhost:3000/search?q=nuxt",
9+
"http://localhost:3000/nuxt"
10+
],
11+
"numberOfRuns": 1,
12+
"settings": {
13+
"onlyCategories": ["accessibility"],
14+
"skipAudits": ["valid-source-maps"]
15+
}
16+
},
17+
"assert": {
18+
"assertions": {
19+
"categories:accessibility": ["warn", { "minScore": 0.9 }]
20+
}
21+
},
22+
"upload": {
23+
"target": "temporary-public-storage"
24+
}
25+
}
26+
}

CONTRIBUTING.md

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,59 @@ The connector will check your npm authentication, generate a connection token, a
110110
- We care about good types – never cast things to `any` 💪
111111
- Validate rather than just assert
112112

113+
### Server API patterns
114+
115+
#### Input validation with Valibot
116+
117+
Use Valibot schemas from `#shared/schemas/` to validate API inputs. This ensures type safety and provides consistent error messages:
118+
119+
```typescript
120+
import * as v from 'valibot'
121+
import { PackageRouteParamsSchema } from '#shared/schemas/package'
122+
123+
// In your handler:
124+
const { packageName, version } = v.parse(PackageRouteParamsSchema, {
125+
packageName: rawPackageName,
126+
version: rawVersion,
127+
})
128+
```
129+
130+
#### Error handling with `handleApiError`
131+
132+
Use the `handleApiError` utility for consistent error handling in API routes. It re-throws H3 errors (like 404s) and wraps other errors with a fallback message:
133+
134+
```typescript
135+
import { ERROR_NPM_FETCH_FAILED } from '#shared/utils/constants'
136+
137+
try {
138+
// API logic...
139+
} catch (error: unknown) {
140+
handleApiError(error, {
141+
statusCode: 502,
142+
message: ERROR_NPM_FETCH_FAILED,
143+
})
144+
}
145+
```
146+
147+
#### URL parameter parsing with `parsePackageParams`
148+
149+
Use `parsePackageParams` to extract package name and version from URL segments:
150+
151+
```typescript
152+
const pkgParamSegments = getRouterParam(event, 'pkg')?.split('/') ?? []
153+
const { rawPackageName, rawVersion } = parsePackageParams(pkgParamSegments)
154+
```
155+
156+
This handles patterns like `/pkg`, `/pkg/v/1.0.0`, `/@scope/pkg`, and `/@scope/pkg/v/1.0.0`.
157+
158+
#### Constants
159+
160+
Define error messages and other string constants in `#shared/utils/constants.ts` to ensure consistency across the codebase:
161+
162+
```typescript
163+
export const ERROR_NPM_FETCH_FAILED = 'Failed to fetch package from npm registry.'
164+
```
165+
113166
### Import order
114167

115168
1. Type imports first (`import type { ... }`)
@@ -174,7 +227,32 @@ describe('featureName', () => {
174227
> [!TIP]
175228
> If you need access to the Nuxt context in your unit or component test, place your test in the `test/nuxt/` directory and run with `pnpm test:nuxt`
176229
177-
### E2e tests
230+
### Component accessibility tests
231+
232+
All new components should have a basic accessibility test in `test/nuxt/components.spec.ts`. These tests use [axe-core](https://github.com/dequelabs/axe-core) to catch common accessibility violations.
233+
234+
```typescript
235+
import MyComponent from '~/components/MyComponent.vue'
236+
237+
describe('MyComponent', () => {
238+
it('should have no accessibility violations', async () => {
239+
const component = await mountSuspended(MyComponent, {
240+
props: {
241+
/* required props */
242+
},
243+
})
244+
const results = await runAxe(component)
245+
expect(results.violations).toEqual([])
246+
})
247+
})
248+
```
249+
250+
The `runAxe` helper handles DOM isolation and disables page-level rules that don't apply to isolated component testing.
251+
252+
> [!IMPORTANT]
253+
> Just because axe-core doesn't find any obvious issues, it does not mean a component is accessible. Please do additional checks and use best practices.
254+
255+
### End to end tests
178256

179257
Write end-to-end tests using Playwright:
180258

README.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,20 @@ The aim of [npmx.dev](https://npmx.dev) is to provide a better browser for the n
2727
- **Package details** – READMEs, versions, dependencies, and metadata
2828
- **Code viewer** – browse package source code with syntax highlighting and permalink to specific lines
2929
- **Provenance indicators** – verified build badges for packages with npm provenance
30+
- **Multi-provider repository support** – stars/forks from GitHub, GitLab, Bitbucket, Codeberg, Gitee, and Sourcehut
3031
- **JSR availability** – see if scoped packages are also available on JSR
31-
- **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
3233
- **Outdated dependency indicators** – visual cues showing which dependencies are behind
3334
- **Vulnerability warnings** – security advisories from the OSV database
3435
- **Download statistics** – weekly download counts with sparkline charts
35-
- **Install size** – total install size including dependencies
36+
- **Install size** – total install size (including transitive dependencies)
3637
- **Playground links** – quick access to StackBlitz, CodeSandbox, and other demo environments from READMEs
3738
- **Infinite search** – auto-load additional search pages as you scroll
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
3842
- **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
3944

4045
### User & org pages
4146

@@ -63,8 +68,12 @@ The aim of [npmx.dev](https://npmx.dev) is to provide a better browser for the n
6368
| Install size calculation |||
6469
| JSR cross-reference |||
6570
| Vulnerability warnings |||
71+
| Deprecation notices |||
6672
| Download charts |||
6773
| Playground links |||
74+
| Keyboard navigation |||
75+
| Multi-provider repo support |||
76+
| Version range resolution |||
6877
| Dependents list || 🚧 |
6978
| Package admin (access/owners) || 🚧 |
7079
| Org/team management || 🚧 |
@@ -101,12 +110,13 @@ npmx.dev supports npm permalinks – just replace `npmjs.com` with `npmx.dev
101110

102111
npmx.dev also supports shorter, cleaner URLs:
103112

104-
| Pattern | Example |
105-
| -------------- | -------------------------------------------------- |
106-
| `/<package>` | [`/nuxt`](https://npmx.dev/nuxt) |
107-
| `/@scope/name` | [`/@nuxt/kit`](https://npmx.dev/@nuxt/kit) |
108-
| `/@org` | [`/@nuxt`](https://npmx.dev/@nuxt) |
109-
| `/~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) |
110120

111121
## Tech stack
112122

@@ -118,7 +128,7 @@ npmx.dev also supports shorter, cleaner URLs:
118128

119129
## Contributing
120130

121-
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!
122132

123133
## Related projects
124134

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

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

133143
## License
134144

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
## Reporting a Vulnerability
44

5-
To report a vulnerability, please [privately report it via the Security tab](https://github.com/danielroe/npmx.dev/security/advisories/new). If that is impossible, feel free to send an email to **security@roe.dev** instead.
5+
To report a vulnerability, please [privately report it via the Security tab](https://github.com/npmx-dev/npmx.dev/security/advisories/new). If that is impossible, feel free to send an email to **security@roe.dev** instead.
66

77
All security vulnerabilities will be promptly verified and addressed.

app/app.vue

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import { useEventListener } from '@vueuse/core'
3+
24
const route = useRoute()
35
const router = useRouter()
46
@@ -12,9 +14,12 @@ useHead({
1214
1315
// Global keyboard shortcut: "/" focuses search or navigates to search page
1416
function handleGlobalKeydown(e: KeyboardEvent) {
15-
// Ignore if user is typing in an input, textarea, or contenteditable
1617
const target = e.target as HTMLElement
17-
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
18+
19+
const isEditableTarget =
20+
target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable
21+
22+
if (isEditableTarget) {
1823
return
1924
}
2025
@@ -28,20 +33,16 @@ function handleGlobalKeydown(e: KeyboardEvent) {
2833
2934
if (searchInput) {
3035
searchInput.focus()
31-
} else {
32-
// Navigate to search page
33-
router.push('/search')
36+
return
3437
}
38+
39+
router.push('/search')
3540
}
3641
}
3742
38-
onMounted(() => {
39-
document.addEventListener('keydown', handleGlobalKeydown)
40-
})
41-
42-
onUnmounted(() => {
43-
document.removeEventListener('keydown', handleGlobalKeydown)
44-
})
43+
if (import.meta.client) {
44+
useEventListener(document, 'keydown', handleGlobalKeydown)
45+
}
4546
</script>
4647

4748
<template>
@@ -50,15 +51,17 @@ onUnmounted(() => {
5051

5152
<AppHeader :show-logo="!isHomepage" />
5253

53-
<div id="main-content" class="flex-1">
54+
<div id="main-content" class="flex-1 flex flex-col">
5455
<NuxtPage />
5556
</div>
5657

5758
<AppFooter />
59+
60+
<ScrollToTop />
5861
</div>
5962
</template>
6063

61-
<style>
64+
<style lang="postcss">
6265
/* Base reset and defaults */
6366
*,
6467
*::before,
@@ -72,11 +75,23 @@ html {
7275
text-rendering: optimizeLegibility;
7376
}
7477
78+
/*
79+
* Enable CSS scroll-state container queries for the document
80+
* This allows the footer to query the scroll state using pure CSS
81+
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/@container#scroll-state_container_descriptors
82+
*/
83+
@supports (container-type: scroll-state) {
84+
html {
85+
container-type: scroll-state;
86+
}
87+
}
88+
7589
body {
7690
margin: 0;
7791
background-color: #0a0a0a;
7892
color: #fafafa;
7993
line-height: 1.6;
94+
padding-bottom: var(--footer-height, 0);
8095
}
8196
8297
/* Default link styling for accessibility on dark background */
@@ -284,7 +299,7 @@ button {
284299
border-left: 2px solid #262626;
285300
padding-left: 1rem;
286301
margin: 1.5rem 0;
287-
color: #666666;
302+
color: #8a8a8a;
288303
font-style: italic;
289304
}
290305

0 commit comments

Comments
 (0)