Skip to content

feat: tools#3079

Open
alexdln wants to merge 5 commits into
npmx-dev:mainfrom
alexdln:feat/tools
Open

feat: tools#3079
alexdln wants to merge 5 commits into
npmx-dev:mainfrom
alexdln:feat/tools

Conversation

@alexdln

@alexdln alexdln commented Jul 25, 2026

Copy link
Copy Markdown
Member

🧭 Context

Added a new section - tools. We've become a service with big data, features and capabilities. I think the logical next step is to start adding development tools for all package-related issues.
One of these is the ability to quickly analyze your project's dependencies - to understand their status, relevance, and alternatives. So I added the deps-stats page.

📚 Description

image
Additional screenshot image image

@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Jul 25, 2026 12:23pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Jul 25, 2026 12:23pm
npmx-lunaria Ignored Ignored Jul 25, 2026 12:23pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 81b57756-1372-4076-affe-a7d27a280bd7

📥 Commits

Reviewing files that changed from the base of the PR and between dfb92a7 and 9f8b651.

📒 Files selected for processing (3)
  • app/components/DepsStats/PackageJsonUpload.vue
  • i18n/locales/en.json
  • i18n/schema.json
💤 Files with no reviewable changes (2)
  • i18n/locales/en.json
  • i18n/schema.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/components/DepsStats/PackageJsonUpload.vue

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added a Tools landing page with navigation from the footer and command palette.
    • Added a Dependency Stats tool for uploading package.json files, browsing dependencies, filtering results, and viewing versions, vulnerabilities, deprecations, licences and install-size trends.
    • Added support for drag-and-drop uploads and non-registry dependency indicators.
  • Documentation
    • Documented shareable Tools and Dependency Stats URLs.
  • Accessibility
    • Added accessibility coverage for the new tool components.

Walkthrough

Adds a Tools area with a dependency statistics page. The feature parses uploaded package manifests, displays categorised dependencies and package statistics, and retrieves vulnerability and deprecation data through a validated server API.

Changes

Tools dependency statistics

Layer / File(s) Summary
Dependency model and package parsing
app/utils/parse-package-json-deps.ts, test/unit/app/utils/parse-package-json-deps.spec.ts
Defines typed dependency categories and parses aliases, registry ranges, non-registry ranges, and invalid package manifests.
Direct dependency health API
shared/types/dependency-analysis.ts, shared/schemas/dependency-analysis.ts, shared/utils/constants.ts, server/utils/dependency-analysis.ts, server/api/registry/direct-deps-health.post.ts, app/composables/npm/useDirectDependencyHealth.ts, test/nuxt/composables/use-direct-dependency-health.spec.ts
Adds validated batched health requests, package resolution, npm deprecation data, OSV vulnerability results, stale-request handling, and related tests.
Dependency statistics components
app/components/DepsStats/*, test/nuxt/a11y.spec.ts
Adds package upload, filtering and grouped dependency listing, selection states, package statistics, facet values, trends, version distribution, and accessibility coverage.
Tools pages and navigation
app/pages/tools/*, app/components/AppFooter.vue, app/composables/useCommandPaletteGlobalCommands.ts, i18n/locales/en.json, i18n/schema.json, nuxt.config.ts, server/middleware/canonical-redirects.global.ts, docs/content/2.guide/3.url-structure.md
Adds Tools routes, translated content, footer and command-palette navigation, prerendering, redirect exclusions, and URL documentation.

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant DependencyStatsPage
  participant PackageJsonParser
  participant DependencyList
  participant HealthComposable
  participant HealthApi
  Visitor->>DependencyStatsPage: Upload package.json
  DependencyStatsPage->>PackageJsonParser: Parse dependency text
  PackageJsonParser-->>DependencyStatsPage: Parsed dependencies
  DependencyStatsPage->>DependencyList: Render dependency groups
  DependencyList->>HealthComposable: Request visible dependency health
  HealthComposable->>HealthApi: POST dependency batch
  HealthApi-->>HealthComposable: Vulnerability and deprecation results
  HealthComposable-->>DependencyList: Update dependency indicators
  DependencyList-->>DependencyStatsPage: Select dependency
  DependencyStatsPage-->>Visitor: Render dependency statistics
Loading

Possibly related PRs

Suggested reviewers: ghostdevv


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is related to the new Tools section, but it is too vague to clearly convey the main change. Rename it to something like 'Add Tools section and dependency stats page'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description matches the changeset by describing the new Tools section and the deps-stats page.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
i18n/locales/en.json Source changed, localizations will be marked as outdated.
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
server/api/registry/direct-deps-health.post.ts (1)

1-15: 🧹 Nitpick | 🔵 Trivial

Consider rate limiting this endpoint.

Each request can trigger up to 50 npm registry fetches plus an OSV batch/detail query fan-out, with no visible per-caller rate limiting on this route. Since it's a public, unauthenticated endpoint, consider adding request throttling to avoid amplifying load onto the npm registry and OSV API.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/api/registry/direct-deps-health.post.ts` around lines 1 - 15, Add
request throttling to the unauthenticated `defineEventHandler` for the direct
dependency health endpoint, limiting each caller before invoking
`analyzeDirectDependencyHealth`. Reuse the project’s existing rate-limiting
mechanism and preserve the current validation and analysis behavior for requests
within the limit.
app/components/DepsStats/DependencyList.vue (1)

86-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Repeated getVulnerableInfo(dep) / getDeprecatedInfo(dep) calls per row.

Each is invoked up to 4 times in the template (v-if, :to, :class, :title, plus the sr-only span), each re-doing the object lookup and relying on repeated non-null assertions (!) instead of a single narrowed value.

Consider computing vulnerableInfo/deprecatedInfo once per row (e.g. via a small wrapper computed keyed by dep.packageName, or destructure once at the top of the row template) to avoid duplicated lookups and non-null assertions.

Also applies to: 234-253

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/DepsStats/DependencyList.vue` around lines 86 - 92, Update the
dependency row rendering around getVulnerableInfo and getDeprecatedInfo to
resolve each package’s vulnerability and deprecation data once per row, then
reuse those narrowed values for v-if, links, classes, titles, and the sr-only
content. Remove the repeated helper calls and non-null assertions while
preserving the existing row behavior.
app/pages/tools/index.vue (1)

6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use $t() consistently in page script setup.

Both pages bypass the project’s established Nuxt i18n global.

  • app/pages/tools/index.vue#L6-L6: remove useI18n() and use $t(...).
  • app/pages/tools/deps-stats.vue#L10-L10: remove useI18n() and use $t(...).

Based on learnings: pages must rely on auto-imported $t() in <script setup> rather than destructuring t from useI18n().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/pages/tools/index.vue` at line 6, Replace the destructured useI18n()
translation setup with the auto-imported $t() in app/pages/tools/index.vue at
lines 6-6 and app/pages/tools/deps-stats.vue at lines 10-10, updating each
page’s translation calls accordingly and removing the unused composable import.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/components/DepsStats/DependencyList.vue`:
- Around line 179-184: Remove the focus-visible utility classes from the
button’s class attribute in the DependencyList selection button, while
preserving all other classes and behavior. Rely on the existing global button
focus-visible styling in main.css.
- Around line 40-62: Update the dependency aggregation around registryDeps,
orderedRegistryNames, and their consumers so duplicate package names retain each
dependency row’s category/alias-specific range instead of overwriting by
packageName. Use a unique key that preserves each parsed dependency identity,
and ensure outdatedDeps, replacementDeps, and health/requestHealth are derived
and ordered from those distinct entries while continuing to exclude nonRegistry
dependencies.

In `@app/components/DepsStats/PackageJsonUpload.vue`:
- Around line 64-80: Update the template in DepsStatsPackageJsonUpload so the
error message guarded by error is rendered regardless of whether fileName is
set. Preserve the selected filename and clear-selection controls, while ensuring
parse errors remain visible for invalid uploads.

In `@server/api/registry/direct-deps-health.post.ts`:
- Around line 12-15: Update the default handler around
DirectDepsHealthBodySchema parsing to catch ValiError from v.parse and throw
createError with statusCode 400 and the first issue message; leave
analyzeDirectDependencyHealth unchanged for valid bodies.

In `@shared/schemas/dependency-analysis.ts`:
- Around line 5-13: Update DirectDepsHealthBodySchema so the v.record value
schema applies a maximum-length constraint to each dependency range string
before analyzeDirectDependencyHealth() processes it. Preserve the existing
dependency-count check and use the established schema-validation approach for
bounding string length.

---

Nitpick comments:
In `@app/components/DepsStats/DependencyList.vue`:
- Around line 86-92: Update the dependency row rendering around
getVulnerableInfo and getDeprecatedInfo to resolve each package’s vulnerability
and deprecation data once per row, then reuse those narrowed values for v-if,
links, classes, titles, and the sr-only content. Remove the repeated helper
calls and non-null assertions while preserving the existing row behavior.

In `@app/pages/tools/index.vue`:
- Line 6: Replace the destructured useI18n() translation setup with the
auto-imported $t() in app/pages/tools/index.vue at lines 6-6 and
app/pages/tools/deps-stats.vue at lines 10-10, updating each page’s translation
calls accordingly and removing the unused composable import.

In `@server/api/registry/direct-deps-health.post.ts`:
- Around line 1-15: Add request throttling to the unauthenticated
`defineEventHandler` for the direct dependency health endpoint, limiting each
caller before invoking `analyzeDirectDependencyHealth`. Reuse the project’s
existing rate-limiting mechanism and preserve the current validation and
analysis behavior for requests within the limit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 459228d1-5f75-4568-8fa9-06b7a9a9c268

📥 Commits

Reviewing files that changed from the base of the PR and between 0690eef and dfb92a7.

📒 Files selected for processing (23)
  • app/components/AppFooter.vue
  • app/components/DepsStats/DependencyList.vue
  • app/components/DepsStats/DependencyStats.vue
  • app/components/DepsStats/DependencyStatsPanel.vue
  • app/components/DepsStats/PackageJsonUpload.vue
  • app/composables/npm/useDirectDependencyHealth.ts
  • app/composables/useCommandPaletteGlobalCommands.ts
  • app/pages/tools/deps-stats.vue
  • app/pages/tools/index.vue
  • app/utils/parse-package-json-deps.ts
  • docs/content/2.guide/3.url-structure.md
  • i18n/locales/en.json
  • i18n/schema.json
  • nuxt.config.ts
  • server/api/registry/direct-deps-health.post.ts
  • server/middleware/canonical-redirects.global.ts
  • server/utils/dependency-analysis.ts
  • shared/schemas/dependency-analysis.ts
  • shared/types/dependency-analysis.ts
  • shared/utils/constants.ts
  • test/nuxt/a11y.spec.ts
  • test/nuxt/composables/use-direct-dependency-health.spec.ts
  • test/unit/app/utils/parse-package-json-deps.spec.ts

Comment thread app/components/DepsStats/DependencyList.vue
Comment thread app/components/DepsStats/DependencyList.vue
Comment thread app/components/DepsStats/PackageJsonUpload.vue
Comment on lines +12 to +15
export default defineEventHandler(async (event): Promise<DirectDependencyHealthResult> => {
const body = v.parse(DirectDepsHealthBodySchema, await readBody(event))
return await analyzeDirectDependencyHealth(body.dependencies)
})

@coderabbitai coderabbitai Bot Jul 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how other API routes handle valibot parse failures.
rg -n 'v\.parse\(' server/api -B3 -A5
fd 'error' server -t f

Repository: npmx-dev/npmx.dev

Length of output: 15774


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Error handler outline:"
ast-grep outline server/utils/error-handler.ts || true
echo
echo "Error handler content:"
cat -n server/utils/error-handler.ts

echo
echo "Other ValiError catch blocks:"
rg -n 'catch \(error\)|instanceof v\.ValiError|throw createError\(\{[\s\S]*?statusCode: 400' server/api -B2 -A6

echo
echo "Direct-deps-health relevant section:"
cat -n server/api/registry/direct-deps-health.post.ts

Repository: npmx-dev/npmx.dev

Length of output: 17114


Map valibot parse failures to 400 instead of 404.

This route does not catch ValiError; when it propagates, the shared handleApiError maps valibot errors to statusCode: 404. For invalid request bodies, catch the validation error locally and throw a createError({ statusCode: 400, message: error.issues[0].message }) to match the expected bad-request semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/api/registry/direct-deps-health.post.ts` around lines 12 - 15, Update
the default handler around DirectDepsHealthBodySchema parsing to catch ValiError
from v.parse and throw createError with statusCode 400 and the first issue
message; leave analyzeDirectDependencyHealth unchanged for valid bodies.

Source: Coding guidelines

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Share more about the source of the proposals

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Endpoint =='
cat -n server/api/registry/direct-deps-health.post.ts

echo
echo '== Error-handler outline =='
ast-grep outline server/utils/error-handler.ts --items all

echo
echo '== ValiError handling and validation patterns =='
rg -n -C 5 'ValiError|v\.parse\(|safeParse|statusCode:\s*400' server/utils/error-handler.ts server/api

Length of output: 40793


The Comment images show the npmx dark-theme Tools experience: two dependency-stat screens with a package.json input, dependency list, and package details for gray-matter and module-replacements; plus the /tools landing page with a deps stats card.

@alexdln, the proposal was based on static control-flow analysis in this PR—not an external advisory or a runtime report:

  1. This endpoint validates the request body with v.parse at server/api/registry/direct-deps-health.post.ts Line 13, with no local error handling.
  2. A failed Valibot parse propagates as a ValiError. The shared handleApiError implementation explicitly detects that type in server/utils/error-handler.ts Lines 31–35 and turns it into:
    statusCode: 404
    The source itself notes the rationale:

    TODO: throwing 404 rather than 400 as it's cacheable

  3. I compared that with existing endpoint-level validation behavior. For example, server/api/atproto/bluesky-author-profiles.get.ts catches v.ValiError and returns 400 for malformed client input.

So the proposal reflects the usual HTTP distinction that an invalid POST body is a 400 Bad Request, while the current shared fallback makes it a 404.

That said, the comment in the shared handler shows this is an intentional project-wide caching trade-off. If the intended policy is for malformed requests to use cacheable 404s, then this endpoint should follow that policy and my suggestion should be withdrawn rather than introducing a route-specific exception.

Comment thread shared/schemas/dependency-analysis.ts

@graphieros graphieros left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really awesome :)
I wonder if we could somehow make the charts smaller so everything could fit without having to scroll. Maybe just reducing the charts height config could work, which would require to add a height prop (or a more general options object). Right now a chartHeight computed (which I just noticed is a bit legacy since we don't use TrendsChart.vue inside modals anymore), could be based on this height prop, defaulting to decent values for mobile & desktop. (?)

Also the tools page should be easier to find, it's so good^^

Other things:

  • the uploaded package.json is not persisted when navigating (for example show more stats and back)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants