diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d59d5a9429..37bd15e85f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -101,15 +116,24 @@ 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 }} @@ -117,7 +141,7 @@ jobs: 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 @@ -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 diff --git a/.github/workflows/enforce-release-source.yml b/.github/workflows/enforce-release-source.yml new file mode 100644 index 0000000000..139d66c062 --- /dev/null +++ b/.github/workflows/enforce-release-source.yml @@ -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 diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 0190ff6e67..273a9114d2 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74f9d19243..254c6385b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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_) diff --git a/README.md b/README.md index 8b3860c4bc..d74939ad29 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -118,7 +145,7 @@ npmx.dev supports npm permalinks – just replace `npmjs.com` with `npmx.dev #### Not yet supported -- `/package//access` – package access settings +- `/package//access` – dedicated npm-compatible access settings URL - `/package//dependents` – dependent packages list - `/settings/*` – account settings pages diff --git a/app/app.vue b/app/app.vue index 34fc1d5c01..03fc939fb5 100644 --- a/app/app.vue +++ b/app/app.vue @@ -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' }) +}