Skip to content

Commit 9db3aca

Browse files
committed
merge main
2 parents 824ac6f + 5d279f7 commit 9db3aca

File tree

145 files changed

+5684
-5481
lines changed

Some content is hidden

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

145 files changed

+5684
-5481
lines changed

.github/workflows/autofix.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ jobs:
3232
- name: 📦 Install dependencies
3333
run: pnpm install
3434

35-
- name: 🎨 Check for non-RTL CSS classes
36-
run: pnpm rtl:check
35+
- name: 🎨 Check for non-RTL/non-a11y CSS classes
36+
run: pnpm lint:css
3737

3838
- name: 🌐 Compare translations
3939
run: pnpm i18n:check

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ jobs:
184184
run: pnpm build:test
185185

186186
- name: ♿ Accessibility audit (Lighthouse - ${{ matrix.mode }} mode)
187-
run: ./scripts/lighthouse-a11y.sh
187+
run: pnpm test:a11y:prebuilt
188188
env:
189189
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
190190
LIGHTHOUSE_COLOR_MODE: ${{ matrix.mode }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,7 @@ test-results/
4040
# generated files
4141
shared/types/lexicons
4242

43+
**/__screenshots__/**
44+
4345
# output
4446
.vercel

.nuxtrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
setups.@nuxt/test-utils="3.23.0"
1+
setups.@nuxt/test-utils="4.0.0"

CONTRIBUTING.md

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ This focus helps guide our project decisions as a community and what we choose t
4343
- [RTL Support](#rtl-support)
4444
- [Localization (i18n)](#localization-i18n)
4545
- [Approach](#approach)
46+
- [i18n commands](#i18n-commands)
4647
- [Adding a new locale](#adding-a-new-locale)
4748
- [Update translation](#update-translation)
4849
- [Adding translations](#adding-translations)
@@ -52,6 +53,7 @@ This focus helps guide our project decisions as a community and what we choose t
5253
- [Testing](#testing)
5354
- [Unit tests](#unit-tests)
5455
- [Component accessibility tests](#component-accessibility-tests)
56+
- [Lighthouse accessibility tests](#lighthouse-accessibility-tests)
5557
- [End to end tests](#end-to-end-tests)
5658
- [Test fixtures (mocking external APIs)](#test-fixtures-mocking-external-apis)
5759
- [Submitting changes](#submitting-changes)
@@ -111,6 +113,7 @@ pnpm test # Run all Vitest tests
111113
pnpm test:unit # Unit tests only
112114
pnpm test:nuxt # Nuxt component tests
113115
pnpm test:browser # Playwright E2E tests
116+
pnpm test:a11y # Lighthouse accessibility audits
114117
```
115118

116119
### Project structure
@@ -378,6 +381,17 @@ npmx.dev uses [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for internationalization.
378381
- We use the `no_prefix` strategy (no `/en-US/` or `/fr-FR/` in URLs)
379382
- Locale preference is stored in cookies and respected on subsequent visits
380383

384+
### i18n commands
385+
386+
The following scripts help manage translation files. `en.json` is the reference locale.
387+
388+
| Command | Description |
389+
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
390+
| `pnpm i18n:check [locale]` | Compares `en.json` with other locale files. Shows missing and extra keys. Optionally filter output by locale (e.g. `pnpm i18n:check ja-JP`). |
391+
| `pnpm i18n:check:fix [locale]` | Same as check, but adds missing keys to other locales with English placeholders. |
392+
| `pnpm i18n:report` | Audits translation keys against code usage in `.vue` and `.ts` files. Reports missing keys (used in code but not in locale), unused keys (in locale but not in code), and dynamic keys. |
393+
| `pnpm i18n:report:fix` | Removes unused keys from `en.json` and all other locale files. |
394+
381395
### Adding a new locale
382396

383397
We are using localization using country variants (ISO-6391) via [multiple translation files](https://i18n.nuxtjs.org/docs/guide/lazy-load-translations#multiple-files-lazy-loading) to avoid repeating every key per country.
@@ -421,25 +435,7 @@ Check [Pluralization rule callback](https://vue-i18n.intlify.dev/guide/essential
421435
We track the current progress of translations with [Lunaria](https://lunaria.dev/) on this site: https://i18n.npmx.dev/
422436
If you see any outdated translations in your language, feel free to update the keys to match the English version.
423437

424-
In order to make sure you have everything up-to-date, you can run:
425-
426-
```bash
427-
pnpm i18n:check <country-code>
428-
```
429-
430-
For example to check if all Japanese translation keys are up-to-date, run:
431-
432-
```bash
433-
pnpm i18n:check ja-JP
434-
```
435-
436-
To automatically add missing keys with English placeholders, use `--fix`:
437-
438-
```bash
439-
pnpm i18n:check:fix fr-FR
440-
```
441-
442-
This will add missing keys with `"EN TEXT TO REPLACE: {english text}"` as placeholder values, making it easier to see what needs translation.
438+
Use `pnpm i18n:check` and `pnpm i18n:check:fix` to verify and fix your locale (see [i18n commands](#i18n-commands) above for details).
443439

444440
#### Country variants (advanced)
445441

@@ -527,6 +523,32 @@ See how `es`, `es-ES`, and `es-419` are configured in [config/i18n.ts](./config/
527523
- Use `common.*` for shared strings (loading, retry, close, etc.)
528524
- Use component-specific prefixes: `package.card.*`, `settings.*`, `nav.*`
529525
- Do not use dashes (`-`) in translation keys; always use underscore (`_`): e.g., `privacy_policy` instead of `privacy-policy`
526+
- **Always use static string literals as translation keys.** Our i18n scripts (`pnpm i18n:report`) rely on static analysis to detect unused and missing keys. Dynamic keys cannot be analyzed and will be flagged as errors.
527+
528+
**Bad:**
529+
530+
```vue
531+
<!-- Template literal -->
532+
<p>{{ $t(`package.tabs.${tab}`) }}</p>
533+
534+
<!-- Variable -->
535+
<p>{{ $t(myKey) }}</p>
536+
```
537+
538+
**Good:**
539+
540+
```typescript
541+
const { t } = useI18n()
542+
543+
const tabLabels = computed(() => ({
544+
readme: t('package.tabs.readme'),
545+
versions: t('package.tabs.versions'),
546+
}))
547+
```
548+
549+
```vue
550+
<p>{{ tabLabels[tab] }}</p>
551+
```
530552

531553
### Using i18n-ally (recommended)
532554

@@ -598,6 +620,40 @@ A coverage test in `test/unit/a11y-component-coverage.spec.ts` ensures all compo
598620
> [!IMPORTANT]
599621
> 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.
600622
623+
### Lighthouse accessibility tests
624+
625+
In addition to component-level axe audits, the project runs full-page accessibility audits using [Lighthouse CI](https://github.com/GoogleChrome/lighthouse-ci). These test the rendered pages in both light and dark mode against Lighthouse's accessibility category, requiring a perfect score.
626+
627+
#### How it works
628+
629+
1. The project is built in test mode (`pnpm build:test`), which activates server-side fixture mocking
630+
2. Lighthouse CI starts a preview server and audits three URLs: `/`, `/search?q=nuxt`, and `/package/nuxt`
631+
3. A Puppeteer setup script (`lighthouse-setup.cjs`) runs before each audit to set the color mode and intercept client-side API requests using the same fixtures as the E2E tests
632+
633+
#### Running locally
634+
635+
```bash
636+
# Build + run both light and dark audits
637+
pnpm test:a11y
638+
639+
# Or against an existing test build
640+
pnpm test:a11y:prebuilt
641+
642+
# Or run a single color mode manually
643+
pnpm build:test
644+
LIGHTHOUSE_COLOR_MODE=dark ./scripts/lighthouse-a11y.sh
645+
```
646+
647+
This requires Chrome or Chromium to be installed. The script will auto-detect common installation paths. Results are printed to the terminal and saved in `.lighthouseci/`.
648+
649+
#### Configuration
650+
651+
| File | Purpose |
652+
| ---------------------------- | --------------------------------------------------------- |
653+
| `.lighthouserc.cjs` | Lighthouse CI config (URLs, assertions, Chrome path) |
654+
| `lighthouse-setup.cjs` | Puppeteer script for color mode + client-side API mocking |
655+
| `scripts/lighthouse-a11y.sh` | Shell wrapper that runs the audit for a given color mode |
656+
601657
### End to end tests
602658

603659
Write end-to-end tests using Playwright:
@@ -619,10 +675,12 @@ E2E tests use a fixture system to mock external API requests, ensuring tests are
619675
- Serves pre-recorded fixture data from `test/fixtures/`
620676
- Enabled via `NUXT_TEST_FIXTURES=true` or Nuxt test mode
621677

622-
**Client-side mocking** (`test/e2e/test-utils.ts`):
678+
**Client-side mocking** (`test/fixtures/mock-routes.cjs`):
623679

624-
- Uses Playwright's route interception to mock browser requests
625-
- All test files import from `./test-utils` instead of `@nuxt/test-utils/playwright`
680+
- Shared URL matching and response generation logic used by both Playwright E2E tests and Lighthouse CI
681+
- Playwright tests (`test/e2e/test-utils.ts`) use this via `page.route()` interception
682+
- Lighthouse tests (`lighthouse-setup.cjs`) use this via Puppeteer request interception
683+
- All E2E test files import from `./test-utils` instead of `@nuxt/test-utils/playwright`
626684
- Throws a clear error if an unmocked external request is detected
627685

628686
#### Fixture files
@@ -670,7 +728,7 @@ URL: https://registry.npmjs.org/some-package
670728
You need to either:
671729

672730
1. Add a fixture file for that package/endpoint
673-
2. Update the mock handlers in `test/e2e/test-utils.ts` (client) or `modules/runtime/server/cache.ts` (server)
731+
2. Update the mock handlers in `test/fixtures/mock-routes.cjs` (client) or `modules/runtime/server/cache.ts` (server)
674732

675733
## Submitting changes
676734

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ npmx.dev supports npm permalinks &ndash; just replace `npmjs.com` with `npmx.dev
116116
| `npmjs.com/org/nuxt` | [`npmx.dev/org/nuxt`](https://npmx.dev/org/nuxt) |
117117

118118
> [!TIP]
119-
> Want automatic redirects? Try the [npmx-replace browser extension](https://github.com/tylersayshi/npmx-replace-extension).
119+
> Want automatic redirects? Try the [npmx-replace browser extension](https://github.com/tylersayshi/npmx-replace-extension) (Chrome only for now).
120120
121121
#### Not yet supported
122122

@@ -150,7 +150,7 @@ We welcome contributions &ndash; please do feel free to explore the project and
150150

151151
## Related projects
152152

153-
- [npmx-replace-extension](https://github.com/tylersayshi/npmx-replace-extension) &ndash; Browser extension to redirect npmjs.com to npmx.dev
153+
- [npmx-replace-extension](https://github.com/tylersayshi/npmx-replace-extension) &ndash; Browser extension to redirect npmjs.com to npmx.dev (Chrome only for now)
154154
- [JSR](https://jsr.io/) &ndash; The open-source package registry for modern JavaScript and TypeScript
155155
- [npm-userscript](https://github.com/bluwy/npm-userscript) &ndash; Browser userscript with various improvements and fixes for npmjs.com
156156
- [npm-alt](https://npm.willow.sh/) &ndash; An alternative npm package browser

app/assets/main.css

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@
2828
--accent: var(--accent-color, oklch(1 0 0));
2929
--accent-muted: var(--accent-color, oklch(0.922 0 0));
3030

31+
/* accent colors */
32+
--swatch-coral: oklch(0.704 0.177 14.75);
33+
--swatch-amber: oklch(0.828 0.165 84.429);
34+
--swatch-emerald: oklch(0.792 0.153 166.95);
35+
--swatch-sky: oklch(0.787 0.128 230.318);
36+
--swatch-violet: oklch(0.78 0.148 286.067);
37+
--swatch-magenta: oklch(0.78 0.15 330);
38+
3139
/* syntax highlighting colors */
3240
--syntax-fn: oklch(0.727 0.137 299.149);
3341
--syntax-str: oklch(0.829 0.088 252.458);
@@ -90,6 +98,14 @@
9098
--accent: var(--accent-color, oklch(0.145 0 0));
9199
--accent-muted: var(--accent-color, oklch(0.205 0 0));
92100

101+
/* accent colors */
102+
--swatch-coral: oklch(0.7 0.19 14.75);
103+
--swatch-amber: oklch(0.8 0.25 84.429);
104+
--swatch-emerald: oklch(0.7 0.17 166.95);
105+
--swatch-sky: oklch(0.7 0.15 230.318);
106+
--swatch-violet: oklch(0.7 0.17 286.067);
107+
--swatch-magenta: oklch(0.75 0.18 330);
108+
93109
--syntax-fn: oklch(0.502 0.188 294.988);
94110
--syntax-str: oklch(0.425 0.152 252);
95111
--syntax-kw: oklch(0.588 0.193 20.469);
@@ -156,6 +172,26 @@
156172
}
157173
}
158174

175+
/*
176+
* Forced Colors Mode (WHCM) Override for Icons
177+
*
178+
* By default, `forced-color-adjust: preserve-parent-color` (from UnoConfig) works fine
179+
* for most icons as they inherit the correct text color.
180+
*
181+
* However, if icons disappear in specific contexts (e.g., inside buttons with
182+
* complex backgrounds or transparent states), uncomment the following block
183+
* to enforce visibility using `CanvasText`.
184+
*/
185+
/*
186+
@media (forced-colors: active) {
187+
[class^='i-'],
188+
[class*=' i-'] {
189+
forced-color-adjust: none !important;
190+
color: CanvasText !important;
191+
}
192+
}
193+
*/
194+
159195
html {
160196
-webkit-font-smoothing: antialiased;
161197
-moz-osx-font-smoothing: grayscale;

app/components/Code/DirectoryListing.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import type { PackageFileTree } from '#shared/types'
33
import type { RouteLocationRaw } from 'vue-router'
44
import { getFileIcon } from '~/utils/file-icons'
5-
import { formatBytes } from '~/utils/formatters'
65
76
const props = defineProps<{
87
tree: PackageFileTree[]
@@ -51,6 +50,8 @@ function getCodeRoute(nodePath?: string): RouteLocationRaw {
5150
params: { path: pathSegments as [string, ...string[]] },
5251
}
5352
}
53+
54+
const bytesFormatter = useBytesFormatter()
5455
</script>
5556

5657
<template>
@@ -107,7 +108,7 @@ function getCodeRoute(nodePath?: string): RouteLocationRaw {
107108
v-if="node.type === 'file' && node.size"
108109
class="text-end font-mono text-xs text-fg-subtle"
109110
>
110-
{{ formatBytes(node.size) }}
111+
{{ bytesFormatter.format(node.size) }}
111112
</span>
112113
</NuxtLink>
113114
</td>

app/components/ColumnPicker.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ onKeyDown(
4141
const toggleableColumns = computed(() => props.columns.filter(col => col.id !== 'name'))
4242
4343
// Map column IDs to i18n keys
44-
const columnLabelKey = computed(() => ({
44+
const columnLabels = computed(() => ({
4545
name: $t('filters.columns.name'),
4646
version: $t('filters.columns.version'),
4747
description: $t('filters.columns.description'),
@@ -57,7 +57,7 @@ const columnLabelKey = computed(() => ({
5757
}))
5858
5959
function getColumnLabel(id: ColumnId): string {
60-
const key = columnLabelKey.value[id]
60+
const key = columnLabels.value[id]
6161
return key ?? id
6262
}
6363

app/components/Compare/FacetSelector.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ function isCategoryNoneSelected(category: string): boolean {
2929
<div v-for="category in categoryOrder" :key="category">
3030
<!-- Category header with all/none buttons -->
3131
<div class="flex items-center gap-2 mb-2">
32-
<span class="text-[10px] text-fg-subtle uppercase tracking-wider">
32+
<span class="text-3xs text-fg-subtle uppercase tracking-wider">
3333
{{ getCategoryLabel(category) }}
3434
</span>
3535
<button
3636
type="button"
37-
class="text-[10px] transition-colors focus-visible:outline-none focus-visible:underline focus-visible:underline-accent"
37+
class="text-3xs transition-colors focus-visible:outline-none focus-visible:underline focus-visible:underline-accent"
3838
:class="
3939
isCategoryAllSelected(category)
4040
? 'text-fg-muted'
@@ -48,10 +48,10 @@ function isCategoryNoneSelected(category: string): boolean {
4848
>
4949
{{ $t('compare.facets.all') }}
5050
</button>
51-
<span class="text-[10px] text-fg-muted/40">/</span>
51+
<span class="text-2xs text-fg-muted/40">/</span>
5252
<button
5353
type="button"
54-
class="text-[10px] transition-colors focus-visible:outline-none focus-visible:underline focus-visible:underline-accent"
54+
class="text-3xs transition-colors focus-visible:outline-none focus-visible:underline focus-visible:underline-accent"
5555
:class="
5656
isCategoryNoneSelected(category)
5757
? 'text-fg-muted'
@@ -94,7 +94,7 @@ function isCategoryNoneSelected(category: string): boolean {
9494
aria-hidden="true"
9595
/>
9696
{{ facet.label }}
97-
<span v-if="facet.comingSoon" class="text-[9px]"
97+
<span v-if="facet.comingSoon" class="text-4xs"
9898
>({{ $t('compare.facets.coming_soon') }})</span
9999
>
100100
</button>

0 commit comments

Comments
 (0)