Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
b72ad4e
chore(deps): update dependency vite-plugin-pwa to v1.3.0 (#2706)
renovate[bot] May 11, 2026
ed4ba1a
chore(deps): update dependency nuxt to v4.4.5 (#2705)
renovate[bot] May 11, 2026
14be489
fix(i18n): update zh-CN locale (#2712)
BabyLy233 May 11, 2026
f0ecbbf
chore(deps): update dependency validate-npm-package-name to v8 (#2707)
renovate[bot] May 11, 2026
1ef8d3c
feat: adding gh changelog/releases to npmx (#1233)
WilcoSp May 11, 2026
274f781
fix(i18n): add-missing-norwegian-nb-NO-translations (#2715)
bonsak May 11, 2026
dc8b7fc
chore(deps): update devdependency @vitest/coverage-v8 to v4.1.6 (#2718)
renovate[bot] May 11, 2026
2eb8b91
fix(i18n): footer translations should actually be applied (#2711)
BabyLy233 May 11, 2026
52b86d6
feat: improve badge width estimation (#2487)
t128n May 11, 2026
b95d123
docs(ui): add stories for blog index page (#2679)
IestynGage May 11, 2026
92cbee7
fix(i18n): update ukrainian translations (#2704)
Sasha125588 May 11, 2026
f91f8c4
fix(i18n): add missing Russian translations (#2724)
dragomano May 12, 2026
8ebf42a
feat: toggle stable versions in timeline chart (#2728)
graphieros May 13, 2026
d65f1aa
ci: enforce prs to `release` must come from `main`
alexdln May 13, 2026
f2f0187
chore: ignore `enforce-release-source.yml`
danielroe May 13, 2026
85e7525
chore: sync changes made on `release` to `main`
danielroe May 13, 2026
fb6916b
ci: update release source concurrency
danielroe May 13, 2026
c2c2754
ci: replace deprecated codecov action, improve setup (#2702)
serhalp May 13, 2026
7033500
fix: full osscar URL in crystal chronicle post (#2741)
cylewaitforit May 13, 2026
06d1a42
chore: bump vue-data-ui from 3.19.6 to 3.19.7 (#2744)
graphieros May 14, 2026
500c2e3
fix: increase z-index for CopyToClipboardButton (#2701)
gameroman May 14, 2026
6ebb0d2
fix: always color replaceable packages as orange (#2748)
gameroman May 15, 2026
01e3d83
fix(i18n): update Japanese translations (#2745)
shuuji3 May 15, 2026
d1e566c
chore: improve toggle transition on touch devices (#2742)
alexdln May 15, 2026
0439359
fix(i18n): added missing spanish traslations (#2757)
alrico88 May 16, 2026
065e9db
fix(ui): improve layout of line charts (#2759)
graphieros May 16, 2026
dfa8658
fix: package comparison broken on org and user pages (#2758)
MatteoGabriele May 17, 2026
fe95911
test: fix test flakiness (#2760)
gameroman May 17, 2026
ea539fd
chore(deps): update pnpm to v11 (#2708)
renovate[bot] May 17, 2026
b539c51
refactor: remove dead parse() code from git provider adapters (#2761)
serhalp May 17, 2026
bda290f
feat: set cursor-pointer on buttons (#2752)
aryanpingle May 17, 2026
4f8c651
refactor: extract repo metadata utils from composable (#2764)
serhalp May 18, 2026
839154a
chore: update codecov action and fix version comments (#2773)
ghostdevv May 19, 2026
f2be533
fix(i18n): add missing Dutch translations (#2772)
WilcoSp May 19, 2026
0e9f1c8
chore(deps): upgrade Playwright to 1.60.0 to fix hanging CI (#2787)
serhalp May 24, 2026
24694d7
fix: update outdated feature documentation (#2782)
serhalp May 24, 2026
d922052
chore(deps): update knip ✂️ and add enable treatConfigHintsAsErrors (…
TkDodo May 24, 2026
6bd8a00
fix: package sub-banner no longer blocks "connect" dropdown (#2770)
ptdewey May 24, 2026
8b0cfc3
feat: strip version info from the search query (#2763)
Codefoxdev May 24, 2026
b54d09b
fix: preserve repository meta headers (#2769)
VinayakMaharaj May 24, 2026
113c2dd
docs(ui): add stories for Likes Leaderboard page (#2789)
cylewaitforit May 24, 2026
8879a03
feat: add nodejs noodle (#2778)
alexdln May 25, 2026
db64da9
chore: bump vue-data-ui from 3.19.8 to 3.20.10 (#2793)
graphieros May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 42 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,24 @@ jobs:

- name: ⬆︎ Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
disable_search: true
files: test-report.junit.xml
flags: unit
report_type: test_results
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

- name: ⬆︎ Upload coverage reports to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
with:
disable_search: true
files: coverage/clover.xml
flags: unit
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

test:
name: 🧪 Component tests
Expand All @@ -101,23 +116,32 @@ jobs:
- name: 🧪 Component tests
run: vp test --project nuxt --coverage --reporter=default --reporter=junit --outputFile=test-report.junit.xml

- name: ⬆︎ Upload coverage reports to Codecov
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
- name: ⬆︎ Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
with:
disable_search: true
files: test-report.junit.xml
flags: component
report_type: test_results
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

- name: ⬆︎ Upload coverage reports to Codecov
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
if: ${{ !cancelled() }}
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
with:
disable_search: true
files: coverage/clover.xml
flags: component
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

browser:
name: 🖥️ Browser tests
runs-on: ubuntu-24.04-arm
container:
image: mcr.microsoft.com/playwright:v1.58.2-noble@sha256:6446946a1d9fd62d9ae501312a2d76a43ee688542b21622056a372959b65d63d
image: mcr.microsoft.com/playwright:v1.60.0-noble@sha256:9bd26ad900bb5e0f4dee75839e957a89ae89c2b7ab1e76050e559790e946b948

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand All @@ -140,6 +164,18 @@ jobs:
- name: 🖥️ Test project (browser)
run: vp run test:browser:prebuilt

- name: ⬆︎ Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
with:
disable_search: true
files: test-report.junit.xml
flags: browser
report_type: test_results
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# TODO(serhalp): Upload browser *coverage* report once we've instrumented the prebuilt bundle

a11y:
name: ♿ Accessibility audit
runs-on: ubuntu-latest # See https://github.com/GoogleChrome/lighthouse/discussions/16834
Expand Down
32 changes: 32 additions & 0 deletions .github/workflows/enforce-release-source.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: chore

on:
pull_request_target:
branches:
- release
types:
- opened
- reopened
- edited
- synchronize

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}

permissions: {}

jobs:
enforce-source:
if: github.repository == 'npmx-dev/npmx.dev'
runs-on: ubuntu-slim
name: 🚦 Enforce release source
steps:
- name: Check PR is from main
env:
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
if [ "$HEAD_REPO" != "npmx-dev/npmx.dev" ] || [ "$HEAD_REF" != "main" ]; then
echo "::error::PRs to 'release' must come from npmx-dev/npmx.dev:main (got ${HEAD_REPO}:${HEAD_REF})"
exit 1
fi
1 change: 1 addition & 0 deletions .github/zizmor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ rules:
- lunaria.yml:38
dangerous-triggers:
ignore:
- enforce-release-source.yml
- dependency-diff-comment.yml
- lunaria.yml
- semantic-pull-requests.yml
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ The command palette is a first-class navigation surface. When you add a new user

### Cursor and navigation

**npmx** uses `cursor: pointer` only for links to match users’ everyday experience. For all other interactive elements, including buttons, use the default cursor (_or another appropriate cursor to indicate state_).
**npmx** uses `cursor: pointer` for links and buttons to match users’ everyday experience. For all other interactive elements, use the default cursor (_or another appropriate cursor to indicate state_).

> [!NOTE]
> A link is any element that leads to another content (_go to another page, authorize_)
Expand Down
87 changes: 57 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ What npmx offers:
- **Speed** – Fast searching, filtering, and navigation.
- **Simplicity** – Get the information you need when you need it in an intuitive UI.
- **URL Compatibility** – Replace `npmjs.com` with `xnpmjs.com` or `npmx.dev` in any URL and it just works.
- **First-class accessibility** – Keyboard-friendly, screen-reader-aware experience baked in from the start.
- **Internationalized UI** – Use npmx in dozens of locales, including RTL languages.
- **Enhanced admin experience** – Manage your packages, teams, and organizations from the browser, powered by your local npm CLI.
- **Shareable URLs** – Every view on every page is shareable through the URL, making sharing a breeze.

## Shortcuts

Expand All @@ -39,62 +42,86 @@ What npmx offers:
### Package browsing

- **Dark mode and light mode** – plus customize the color palette to your preferences
- **Translated interface** – localized UI across 39+ locales, including RTL support
- **First-class accessibility** – accessible components, keyboard workflows, and automated axe/Lighthouse checks
- **URL-driven feature views** – share exact package versions, search results, compare sets, source files and lines, diffs, docs, changelogs, and timelines
- **Fast search** – quick package search with instant results
- **Package details** – READMEs, versions, dependencies, and metadata
- **Code viewer** – browse package source code with syntax highlighting and permalink to specific lines
- **Generated API docs** – browse generated docs for typed packages when available
- **Version diff** – inspect source and dependency changes between package versions
- **Changelog view** – read release notes when packages publish them
- **Timeline view** – scan publish history and notable version events such as deprecations and install size changes
- **Provenance indicators** – verified build badges and provenance section below the README
- **Multi-provider repository support** – stars/forks from GitHub, GitLab, Bitbucket, Codeberg, Gitee, Sourcehut, Forgejo, Gitea, Radicle, and Tangled
- **JSR availability** – see if scoped packages are also available on JSR
- **Package badges** – module format (ESM/CJS/dual), TypeScript types (with `@types/*` links), and engine constraints
- **Outdated dependency indicators** – visual cues showing which dependencies are behind
- **Vulnerability warnings** – security advisories from the OSV database
- **License, replacement, install script, and size-change warnings** – spot package health and maintenance signals quickly
- **Download statistics** – weekly download counts with sparkline charts
- **Install size** – total install size (including transitive dependencies)
- **Package comparison** – compare packages by downloads, size, dependencies, types, security, repository health, and more
- **Social signals** – like packages, view profile likes, and browse the most-liked packages leaderboard
- **Playground links** – quick access to StackBlitz, CodeSandbox, and other demo environments from READMEs
- **Infinite search** – auto-load additional search pages as you scroll
- **Keyboard navigation** – press `/` to focus search, `.` to open code viewer, arrow keys to navigate results
- **Deprecation notices** – clear warnings for deprecated packages and versions
- **Version range resolution** – dependency ranges (e.g., `^1.0.0`) resolve to actual installed versions
- **Claim new packages** – register new package names directly from search results (via local connector)
- **Claim new packages** – register new package names directly from search results
- **Clickable version tags** – navigate directly to any version from the versions list

### User & org pages

- **User profiles** – view any npm user's public packages at `/~username`
- **Organization pages** – browse org packages at `/@orgname`
- **Package access and owner management** – grant package access, revoke team access, and add/remove package owners
- **Organization member and team management** – add/remove org members, change roles, create teams, and manage team membership
- **Search, filter & sort** – find packages within user/org lists
- **Infinite scroll** – paginated lists that load as you scroll

### Comparison with npmjs.com

| Feature | npmjs.com | npmx.dev |
| ------------------------------ | :-------: | :------: |
| Package search | ✅ | ✅ |
| Package details & README | ✅ | ✅ |
| Version history | ✅ | ✅ |
| Dependencies list | ✅ | ✅ |
| User profiles | ✅ | ✅ |
| Organization pages | ✅ | ✅ |
| Provenance indicators | ✅ | ✅ |
| Code browser | ✅ | ✅ |
| Dark mode | ✅ | ✅ |
| Outdated dependency warnings | ❌ | ✅ |
| Module format badges (ESM/CJS) | ❌ | ✅ |
| TypeScript types indicator | ✅ | ✅ |
| Install size calculation | ❌ | ✅ |
| JSR cross-reference | ❌ | ✅ |
| Vulnerability warnings | ✅ | ✅ |
| Deprecation notices | ✅ | ✅ |
| Download charts | ✅ | ✅ |
| Playground links | ❌ | ✅ |
| Keyboard navigation | ❌ | ✅ |
| Multi-provider repo support | ❌ | ✅ |
| Version range resolution | ❌ | ✅ |
| Dependents list | ✅ | 🚧 |
| Package admin (access/owners) | ✅ | 🚧 |
| Org/team management | ✅ | 🚧 |
| 2FA/account settings | ✅ | ❌ |
| Claim new package names | ✅ | ✅ |
| Feature | npmjs.com | npmx.dev |
| ----------------------------------- | :-------: | :------: |
| Package search | ✅ | ✅ |
| Package details & README | ✅ | ✅ |
| Version history | ✅ | ✅ |
| Dependencies list | ✅ | ✅ |
| Dependents list | ✅ | 🚧 |
| User profiles | ✅ | ✅ |
| Organization pages | ✅ | ✅ |
| Package comparison | ❌ | ✅ |
| URL-driven/shareable feature views | ❌ | ✅ |
| Provenance indicators | ✅ | ✅ |
| Code browser | ✅ | ✅ |
| Generated API docs | ❌ | ✅ |
| Version diff | ❌ | ✅ |
| Changelog view | ❌ | ✅ |
| Timeline view | ❌ | ✅ |
| Dark mode | ✅ 🆕 | ✅ |
| Internationalization | ❌ | ✅ |
| Accessibility statement and audits | ❌ | ✅ |
| Outdated dependency warnings | ❌ | ✅ |
| Module format badges (ESM/CJS/WASM) | ❌ | ✅ |
| TypeScript types indicator | ✅ | ✅ |
| Install size calculation | ❌ | ✅ |
| Install script warnings | ❌ | ✅ |
| License change warnings | ❌ | ✅ |
| Module replacement suggestions | ❌ | ✅ |
| JSR cross-reference | ❌ | ✅ |
| Vulnerability warnings | ✅ | ✅ |
| Deprecation notices | ✅ | ✅ |
| Download charts | ✅ | ✅ |
| Package likes and leaderboard | ❌ | ✅ |
| Playground links | ❌ | ✅ |
| Keyboard navigation | ❌ | ✅ |
| Multi-provider repo support | ❌ | ✅ |
| Version range resolution | ❌ | ✅ |
| Package admin (access/owners) | ✅ | ✅ |
| Org/team management | ✅ | ✅ |
| 2FA/account settings | ✅ | ❌ |
| Claim new package names | ✅ | ✅ |

🚧 = coming soon

Expand All @@ -118,7 +145,7 @@ npmx.dev supports npm permalinks – just replace `npmjs.com` with `npmx.dev

#### Not yet supported

- `/package/<name>/access` &ndash; package access settings
- `/package/<name>/access` &ndash; dedicated npm-compatible access settings URL
- `/package/<name>/dependents` &ndash; dependent packages list
- `/settings/*` &ndash; account settings pages

Expand Down
14 changes: 11 additions & 3 deletions app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,17 @@ if (import.meta.client) {
}
}

// title and description will be inferred
// this will be overridden by upstream pages that use different templates
defineOgImage('Page.takumi', {}, { alt: 'npmx — a fast, modern browser for the npm registry' })
const isBlogPostRoute = computed(() => {
return route.path.startsWith('/blog/') && route.path !== '/blog/'
})

// This is a priority bug that when we set og:image at the component level via useSeoMeta,
// it is ignored and the image from app.vue is written over it.
if (!isBlogPostRoute.value) {
// title and description will be inferred
// this will be overridden by upstream pages that use different templates
defineOgImage('Page.takumi', {}, { alt: 'npmx — a fast, modern browser for the npm registry' })
}
</script>

<template>
Expand Down
4 changes: 2 additions & 2 deletions app/components/AppFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const socialLinks = computed(() => [
},
])

const footerSections: Array<{ label: string; links: FooterLink[] }> = [
const footerSections = computed<Array<{ label: string; links: FooterLink[] }>>(() => [
{
label: t('footer.resources'),
links: [
Expand Down Expand Up @@ -93,7 +93,7 @@ const footerSections: Array<{ label: string; links: FooterLink[] }> = [
},
],
},
]
])
</script>

<template>
Expand Down
2 changes: 1 addition & 1 deletion app/components/Button/Base.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ defineExpose({
<template>
<button
ref="el"
class="group gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 disabled:(opacity-40 cursor-not-allowed border-transparent)"
class="group gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 cursor-pointer disabled:(opacity-40 cursor-not-allowed border-transparent)"
:class="{
'inline-flex': !block,
'flex': block,
Expand Down
78 changes: 78 additions & 0 deletions app/components/Changelog/Card.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<script setup lang="ts">
import type { ReleaseData } from '~~/shared/types/changelog'
import { slugify } from '~~/shared/utils/html'

const { release } = defineProps<{
release: ReleaseData
}>()
const formattedDate = computed(() => {
if (!release.publishedAt) {
return
}
return new Date(release.publishedAt).toISOString().split('T')[0]
})

const cardId = computed(() => (formattedDate.value ? `date-${formattedDate.value}` : undefined))

const navId = computed(() => `release-${slugify(release.title)}`)

function navigateToTitle() {
navigateTo(`#${navId.value}`)
}
</script>
<template>
<section
class="border border-border rounded-lg p-4 pt-2 sm:p-6 sm:pt-4 scroll-mt-18"
:id="cardId"
>
<div
class="flex gap-2 items-center sticky z-3 text-2xl p-2 border-border bg-bg top-[--combined-header-height]"
>
<h2
class="text-1xl sm:text-2xl font-medium min-w-0 break-words py-2 scroll-mt-20"
:id="navId"
>
<a
class="hover:decoration-accent hover:text-accent focus-visible:decoration-accent focus-visible:text-accent transition-colors duration-200"
:class="$style.linkTitle"
:href="`#${navId}`"
@click.prevent="navigateToTitle()"
>
{{ release.title }}
</a>
</h2>
<TagStatic v-if="release.prerelease" variant="default" class="h-unset">
{{ $t('changelog.pre_release') }}
</TagStatic>
<TagStatic v-if="release.draft" variant="default" class="h-unset">
{{ $t('changelog.draft') }}
</TagStatic>
<div class="flex-1" aria-hidden="true"></div>
<ReadmeTocDropdown
v-if="release?.toc && release.toc.length > 1"
:toc="release.toc"
class="ms-auto"
/>
<!-- :active-id="activeTocId" -->
</div>
<DateTime
v-if="release.publishedAt"
:datetime="release.publishedAt"
date-style="medium"
class="mb-2 block"
/>
<Readme v-if="release.html" :html="release.html"></Readme>
</section>
</template>

<style module>
.linkTitle::after {
content: '__';
@apply inline i-lucide:link rtl-flip ms-1 opacity-0;
font-size: 0.75em;
}

.linkTitle:hover::after {
@apply opacity-100;
}
</style>
Loading
Loading