Skip to content

fix: correctly parse package name for scoped packages#2135

Merged
MatteoGabriele merged 6 commits intonpmx-dev:mainfrom
gameroman:fix-compare-page-scoped-package
Mar 19, 2026
Merged

fix: correctly parse package name for scoped packages#2135
MatteoGabriele merged 6 commits intonpmx-dev:mainfrom
gameroman:fix-compare-page-scoped-package

Conversation

@gameroman
Copy link
Contributor

@gameroman gameroman commented Mar 18, 2026

🔗 Linked issue

Fixes #2141

📚 Description

Fixes fetching data on compare page with scoped packages

Before

image

After

image

@codecov
Copy link

codecov bot commented Mar 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@vercel
Copy link

vercel bot commented Mar 18, 2026

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

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Mar 19, 2026 1:43pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 19, 2026 1:43pm
npmx-lunaria Ignored Ignored Mar 19, 2026 1:43pm

Request Review

@gameroman gameroman changed the title fix: correctly parse package name for scoped packages [WIP] fix: correctly parse package name for scoped packages Mar 18, 2026
@gameroman gameroman marked this pull request as ready for review March 18, 2026 18:34
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f5328aac-9282-443e-a16d-0f4f7cab6f70

📥 Commits

Reviewing files that changed from the base of the PR and between 8613899 and 7142d6d.

📒 Files selected for processing (1)
  • test/e2e/interactions.spec.ts

📝 Walkthrough

Walkthrough

Decodes the route packageName parameter with decodeURIComponent before parsing in two API handlers (server/api/registry/analysis/[...pkg].get.ts and server/api/registry/install-size/[...pkg].get.ts). Adds unit tests for parsePackageParams covering unscoped and scoped package names with and without versions. Adds two Playwright e2e tests that exercise the install-size and analysis registry endpoints for a scoped package (@nuxt/kit) and verify JSON responses. No other control flow, error handling, or exported API signatures were changed.

Possibly related PRs

Suggested labels

needs review

Suggested reviewers

  • danielroe
  • serhalp
  • alexdln
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly relates to fixing scoped package handling on the compare page, with before/after screenshots demonstrating the issue and solution.
Linked Issues check ✅ Passed Code changes successfully address issue #2141 by applying decodeURIComponent to package names in both install-size and analysis API routes, with supporting unit and E2E tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing scoped package name decoding: two API route handlers, unit tests for parsePackageParams, and E2E tests for the compare page.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
server/api/registry/analysis/[...pkg].get.ts (1)

38-46: Consider extracting route package-param parsing into a shared helper.

This block is now duplicated across endpoints; centralising it would lower future drift risk when parsing/decoding rules change.

test/unit/server/utils/parse-package-params.spec.ts (1)

4-53: Useful parser coverage; add one regression test for encoded route input.

These tests validate segment parsing well, but the bug fix is in handler decoding. Please add a handler-level test with %40scope%2Fname so the original regression is fully guarded.

As per coding guidelines **/*.{test,spec}.{ts,tsx}: Write unit tests for core functionality using vitest.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a276c004-28ce-491f-b50c-2f60a4d5f279

📥 Commits

Reviewing files that changed from the base of the PR and between 77c4570 and 2c0f0cb.

📒 Files selected for processing (3)
  • server/api/registry/analysis/[...pkg].get.ts
  • server/api/registry/install-size/[...pkg].get.ts
  • test/unit/server/utils/parse-package-params.spec.ts

@MatteoGabriele
Copy link
Member

@gameroman, could you provide a more detailed description of what this PR aims to fix? The issue you mentioned links to a ticket on your fork, and there are only two links. It's a bit cryptic, even for future reference, and it would be better documented/tracked in this repository.

@gameroman
Copy link
Contributor Author

@gameroman, could you provide a more detailed description of what this PR aims to fix? The issue you mentioned links to a ticket on your fork, and there are only two links. It's a bit cryptic, even for future reference, and it would be better documented/tracked in this repository.

Updated

@MatteoGabriele
Copy link
Member

Judging by the title, I expected to see some artifacts in the stats, but they look the same unless I'm missing something.

Screenshot 2026-03-18 at 22 28 11

@gameroman
Copy link
Contributor Author

gameroman commented Mar 18, 2026

@MatteoGabriele
Copy link
Member

@MatteoGabriele
Copy link
Member

btw I like the addition. I was wondering if it wouldn't make sense to include it directly within the parsePackageParams utility and add tests for when the string is actually encoded? What do you think?

@gameroman
Copy link
Contributor Author

gameroman commented Mar 19, 2026

@gameroman, where did you find this link? 🤔 https://npmx-qljid94b8-npmx.vercel.app/compare?packages=@angular/core,vue

{F79A169F-FC57-4EAA-8403-FD573C98B44C}

I was wondering if it wouldn't make sense to include it directly within the parsePackageParams utility and add tests for when the string is actually encoded? What do you think?

Yes, I think that makes sense, will check where this util is used at the moment

@MatteoGabriele
Copy link
Member

@gameroman Oh wow, that's where that URL is from. I always click on the preview link in the Vercel bot comment. It's strange that those are two different links and give two different results. I wonder why 🤔

Yes, I think that makes, will check where this util is used at the moment

Thanks a lot 🙏

@gameroman
Copy link
Contributor Author

@coderabbitai what test should I add to make sure there is no regressions in the future?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me look at the relevant files to give you precise test recommendations.


🏁 Script executed:

#!/bin/bash
# Find parsePackageParams utility
fd "parse-package-params" --type f

# Find the test spec file
fd "parse-package-params.spec" --type f

Length of output: 140


🏁 Script executed:

#!/bin/bash
cat server/utils/parse-package-params.ts
echo "---"
cat test/unit/server/utils/parse-package-params.spec.ts
echo "---"
cat server/api/registry/install-size/\[...pkg\].get.ts
echo "---"
cat server/api/registry/analysis/\[...pkg\].get.ts

Length of output: 12244


@gameroman, based on the code and MatteoGabriele's suggestion to move decodeURIComponent into parsePackageParams, here are the tests to add to test/unit/server/utils/parse-package-params.spec.ts.

The core regression risk is a scoped package URL where the / inside the scope is percent-encoded (e.g. @angular%2Fcore). This can arrive as a single segment instead of being split, so the function must decode it to recover @angular/core. There are two realistic encoding variants:

describe('encoded package names', () => {
  it('parses fully encoded scoped package name without version', () => {
    // e.g. URL: /api/registry/install-size/%40angular%2Fcore
    // Nitro does not split on %2F, so the whole thing arrives as one segment
    const segments = ['%40angular%2Fcore']
    const result = parsePackageParams(segments)
    expect(result).toEqual({
      rawPackageName: '@angular/core',
      rawVersion: undefined,
    })
  })

  it('parses fully encoded scoped package name with version', () => {
    // e.g. URL: /api/registry/install-size/%40angular%2Fcore/v/17.0.0
    const segments = ['%40angular%2Fcore', 'v', '17.0.0']
    const result = parsePackageParams(segments)
    expect(result).toEqual({
      rawPackageName: '@angular/core',
      rawVersion: '17.0.0',
    })
  })

  it('parses partially encoded scoped package name (only slash encoded)', () => {
    // e.g. URL: /api/registry/install-size/@angular%2Fcore
    // @ is not encoded, but the inner slash is, so still one segment
    const segments = ['@angular%2Fcore']
    const result = parsePackageParams(segments)
    expect(result).toEqual({
      rawPackageName: '@angular/core',
      rawVersion: undefined,
    })
  })

  it('parses partially encoded scoped package name with version', () => {
    const segments = ['@angular%2Fcore', 'v', '17.0.0']
    const result = parsePackageParams(segments)
    expect(result).toEqual({
      rawPackageName: '@angular/core',
      rawVersion: '17.0.0',
    })
  })
})

These tests will only pass once decodeURIComponent is moved into parsePackageParams (as MatteoGabriele suggested), and they'll catch any future regression where the decode is accidentally removed or bypassed. After that refactor, the two API handlers can also drop their own decodeURIComponent(rawPackageName) calls and just use rawPackageName directly.

@gameroman
Copy link
Contributor Author

@coderabbitai I am thinking more of an e2e test since I currently modified server/api/registry/analysis/[...pkg].get.ts and server/api/registry/install-size/[...pkg].get.ts

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me check the existing E2E test structure to give you precise recommendations.


🏁 Script executed:

#!/bin/bash
# Find E2E test files and framework config
fd . test/ --type f | head -60
echo "---"
# Check test config files
fd "vitest|playwright|cypress|jest" --type f | head -20
echo "---"
# Check package.json for test tooling
cat package.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps({k: d[k] for k in ['scripts','devDependencies','dependencies'] if k in d}, indent=2))"
echo "---"
# Read the two modified API files
cat "server/api/registry/install-size/[...pkg].get.ts"
echo "---"
cat "server/api/registry/analysis/[...pkg].get.ts"

Length of output: 18034


Let me check the existing E2E test patterns and mock setup:


🏁 Script executed:

#!/bin/bash
# Read mock-routes to understand how API mocking works
cat test/fixtures/mock-routes.cjs
echo "==="
# Read an existing E2E spec for context
cat test/e2e/interactions.spec.ts
echo "==="
cat test/e2e/test-utils.ts
echo "==="
cat test/e2e/global-setup.ts
echo "==="
# Check if there are any existing API-level tests (not just UI)
cat playwright.config.ts
echo "==="
# Check connector spec for API patterns
cat test/e2e/connector.spec.ts

Length of output: 49280


@gameroman, great call on going with an E2E test — it gives the highest confidence because it exercises the full request path (URL → Nitro router → parsePackageParamsdecodeURIComponent → schema validation → downstream fetch).

Here's the approach that fits best with the existing test patterns in this project:

Strategy: Use page.waitForResponse() to intercept the browser's call to the internal /api/registry/... routes while navigating to the compare page. This verifies that:

  1. The correct (encoded) URL is generated for a scoped package
  2. The server successfully decodes it and returns 200 (not 422/500 from a failed parse)

Add these tests to test/e2e/interactions.spec.ts inside the existing Compare Page describe block:

test.describe('Compare Page', () => {
  // ... existing tests ...

  test('loads install-size data for a scoped package', async ({ page, goto }) => {
    // Intercept the internal API call the browser makes for install-size.
    // The browser will request /api/registry/install-size/@nuxt%2Fkit (encoded slash).
    // Before the fix this would fail to parse and return an error; after it returns 200.
    const installSizeResponse = page.waitForResponse(
      res =>
        res.url().includes('/api/registry/install-size/') &&
        res.url().includes('nuxt') &&
        res.request().method() === 'GET',
      { timeout: 20_000 },
    )

    await goto('/compare?packages=@nuxt/kit,vue', { waitUntil: 'hydration' })

    const response = await installSizeResponse
    expect(response.status()).toBe(200)

    const body = await response.json()
    // The API should return a valid install size object, not an error
    expect(body).toHaveProperty('installSize')
  })

  test('loads analysis data for a scoped package', async ({ page, goto }) => {
    const analysisResponse = page.waitForResponse(
      res =>
        res.url().includes('/api/registry/analysis/') &&
        res.url().includes('nuxt') &&
        res.request().method() === 'GET',
      { timeout: 20_000 },
    )

    await goto('/compare?packages=@nuxt/kit,vue', { waitUntil: 'hydration' })

    const response = await analysisResponse
    expect(response.status()).toBe(200)

    const body = await response.json()
    expect(body).toHaveProperty('package', '@nuxt/kit')
  })

  test('compare grid shows data (not all dashes) for a scoped package', async ({
    page,
    goto,
  }) => {
    await goto('/compare?packages=@nuxt/kit,vue', { waitUntil: 'hydration' })

    const grid = page.locator('.comparison-grid')
    await expect(grid).toBeVisible({ timeout: 20_000 })

    // Package size row should have a value for the scoped package column,
    // not a dash, which would indicate the API call failed before the fix.
    const packageSizeRow = grid.locator('[data-facet="packageSize"]')
    await expect(packageSizeRow).toBeVisible({ timeout: 15_000 })
    await expect(packageSizeRow.locator('.comparison-cell').first()).not.toContainText('-')
  })
})

A few notes:

  • @nuxt/kit already has fixtures in test/fixtures/npm-registry/packuments/@nuxt/kit.json and test/fixtures/npm-api/downloads/@nuxt/kit.json, so the browser-side mocks for the downstream npm calls are covered.
  • The data-facet attribute selectors in the third test should be adjusted to match the actual attribute names used in your comparison grid component — check the component to confirm.
  • If the installSize / package response shape differs from what the handler returns (based on the PackageAnalysisResponse type), adjust the toHaveProperty assertions accordingly.
  • These tests will only pass after the decodeURIComponent fix is in place (or moved into parsePackageParams as MatteoGabriele suggested), making them reliable regression guards.

@gameroman
Copy link
Contributor Author

Yes, I think that makes sense, will check where this util is used at the moment

Thanks a lot 🙏

@MatteoGabriele I've checked and the util is used several places and some might not need the decoding logic so I'm thinking I'll do that in a follow-up PR, if that's okay?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/e2e/interactions.spec.ts (1)

68-71: Consider asserting the package property for consistency.

The analysis test (lines 87-88) asserts expect(body).toHaveProperty('package', '@nuxt/kit') to verify the scoped package name was correctly parsed. Adding the same assertion here would strengthen the test and maintain consistency between the two API tests.

💡 Suggested improvement
     const body = await response.json()
     // The API should return a valid install size object, not an error
     expect(body).toHaveProperty('installSize')
+    expect(body).toHaveProperty('package', '@nuxt/kit')
   })

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f1074fe2-be46-4ba6-bb41-4e829387b301

📥 Commits

Reviewing files that changed from the base of the PR and between 2c0f0cb and 35e613f.

📒 Files selected for processing (1)
  • test/e2e/interactions.spec.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c130c980-4e69-41a3-8abc-68e1367f94dc

📥 Commits

Reviewing files that changed from the base of the PR and between 35e613f and 8613899.

📒 Files selected for processing (1)
  • test/e2e/interactions.spec.ts

@gameroman
Copy link
Contributor Author

Ready to be merged

Copy link
Member

@MatteoGabriele MatteoGabriele left a comment

Choose a reason for hiding this comment

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

LGTM ✨
Thanks for the great work and for being so responsive to feedback!

@MatteoGabriele MatteoGabriele added this pull request to the merge queue Mar 19, 2026
Merged via the queue into npmx-dev:main with commit 230b7c7 Mar 19, 2026
20 checks passed
@gameroman gameroman deleted the fix-compare-page-scoped-package branch March 19, 2026 20:47
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.

stats on compare page fail to load for scoped packages

2 participants