Skip to content

feat: module replacements v3#2068

Draft
gameroman wants to merge 46 commits intonpmx-dev:mainfrom
gameroman:module-replacements-v3
Draft

feat: module replacements v3#2068
gameroman wants to merge 46 commits intonpmx-dev:mainfrom
gameroman:module-replacements-v3

Conversation

@gameroman
Copy link
Contributor

@gameroman gameroman commented Mar 13, 2026

Try module replacements v3

NOTE

AI disclosure: I used AI to help me write some of the code but verified everything manually

@vercel
Copy link

vercel bot commented Mar 13, 2026

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

Project Deployment Actions Updated (UTC)
docs.npmx.dev Ready Ready Preview, Comment Mar 18, 2026 5:11pm
npmx.dev Ready Ready Preview, Comment Mar 18, 2026 5:11pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
npmx-lunaria Ignored Ignored Mar 18, 2026 5:11pm

Request Review

@codecov
Copy link

codecov bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 64.00000% with 18 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/components/Package/Replacement.vue 60.71% 11 Missing ⚠️
app/utils/resolve-replacement-url.ts 66.66% 4 Missing ⚠️
app/composables/useModuleReplacement.ts 0.00% 2 Missing ⚠️
app/components/Compare/ReplacementSuggestion.vue 83.33% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@gameroman
Copy link
Contributor Author

@coderabbitai How can I add a e2e test that /package/strip-ansi shows a replacement suggestion with a link to https://e18e.dev/docs/replacements/strip-ansi ?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and read all e2e test files
fd -t f -e ts -e tsx . --search-path e2e --exec echo "=== {} ===" \; --exec cat {}

echo "---"
# Also look for any playwright config
fd -t f "playwright.config*" --exec cat {}

Length of output: 1278


🏁 Script executed:

#!/bin/bash
# Find and read all e2e test files
fd -t f -e ts -e tsx . --search-path test/e2e --exec echo "=== {} ===" \; --exec cat {}

Length of output: 72445


🏁 Script executed:

#!/bin/bash
# Check mock-routes fixture
fd -t f "mock-routes.cjs" --exec cat {}

echo "==="
# Check test/fixtures directory structure
fd -t f . --search-path test/fixtures | head -50

echo "==="
# Check if there's a strip-ansi fixture
fd -t f . --search-path test/fixtures | xargs grep -l "strip-ansi" 2>/dev/null

echo "==="
# Look at the replacements API file more carefully
fd -t f "*.get.ts" --search-path server/api/replacements --exec cat {}

echo "==="
# Check how gzip-size replacement fixture might look
fd -t f . --search-path test/fixtures | xargs grep -l "gzip\|replacement" 2>/dev/null | head -5

Length of output: 18535


🏁 Script executed:

#!/bin/bash
# Read the server API file
cat "server/api/replacements/[...pkg].get.ts"

echo "==="
# Read a minimal packument fixture as template
cat test/fixtures/npm-registry/packuments/is-odd.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
# Show just the top-level keys and dist-tags
print('Top-level keys:', list(data.keys()))
print('dist-tags:', data.get('dist-tags'))
versions = list(data.get('versions', {}).keys())
print('versions count:', len(versions))
print('first version keys:', list(data['versions'][versions[0]].keys()) if versions else [])
"

echo "==="
# Also check what the Replacement.vue learn-more link looks like
fd -t f "Replacement.vue" --search-path app --exec cat {}

echo "==="
# Check how replacement data is fetched in the package page
fd -t f "useModuleReplacement*" --search-path app --exec cat {}
rg -n "api/replacements" --type ts --type vue -C 2

Length of output: 5106


@gameroman Here's how to add the test. You need two things:


1. Add an npm registry packument fixture for strip-ansi

Without this, the mock intercepts the npm registry call and returns 404, making the package page show "not found" before the replacement suggestion ever loads.

Create test/fixtures/npm-registry/packuments/strip-ansi.json:

{
  "_id": "strip-ansi",
  "name": "strip-ansi",
  "description": "Strip ANSI escape codes from a string",
  "dist-tags": {
    "latest": "7.1.0"
  },
  "versions": {
    "7.1.0": {
      "name": "strip-ansi",
      "version": "7.1.0",
      "description": "Strip ANSI escape codes from a string",
      "license": "MIT",
      "main": "index.js",
      "engines": { "node": ">=12" },
      "dependencies": { "ansi-regex": "^6.0.1" },
      "_id": "strip-ansi@7.1.0",
      "dist": {
        "tarball": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
        "shasum": "d81d4723a5abe40f9b371d00d96e566d998d8db8"
      }
    }
  },
  "time": {
    "7.1.0": "2022-10-01T00:00:00.000Z"
  },
  "license": "MIT",
  "readme": "Strip ANSI escape codes from a string",
  "keywords": ["strip", "ansi", "escape codes", "cli"]
}

2. Write the test

Create test/e2e/package-replacements.spec.ts:

import { expect, test } from './test-utils'

test.describe('Package Replacement Suggestions', () => {
  test('/package/strip-ansi shows a replacement suggestion with a link to e18e docs', async ({
    page,
    goto,
  }) => {
    await goto('/package/strip-ansi', { waitUntil: 'hydration' })

    // Wait for the package page to load
    await expect(page.locator('h1')).toContainText('strip-ansi', { timeout: 15_000 })

    // The replacement component renders a "Learn more" anchor driven by resolveReplacementUrl.
    // For strip-ansi the url.type is 'e18e', so the href resolves to:
    // https://e18e.dev/docs/replacements/strip-ansi
    //
    // useLazyFetch is used, so the suggestion arrives after hydration — use a generous timeout.
    const replacementLink = page.locator(
      'a[href="https://e18e.dev/docs/replacements/strip-ansi"]',
    )
    await expect(replacementLink).toBeVisible({ timeout: 15_000 })
  })
})

Why this works

  • The internal /api/replacements/strip-ansi Nuxt server route is not intercepted by the external-API mock system — it runs normally and reads from the module-replacements package on disk.
  • Replacement.vue unconditionally renders <a :href="externalUrl"> at the bottom when externalUrl is truthy, so page.locator('a[href="..."]') is the right selector.
  • The useLazyFetch in useModuleReplacement means the suggestion loads asynchronously after SSR, hence the 15-second timeout on the assertion.

🧠 Learnings used
Learnt from: CR
Repo: npmx-dev/npmx.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-03T10:09:38.777Z
Learning: Applies to **/e2e/**/*.{test,spec}.{ts,tsx} : Write end-to-end tests using Playwright and `nuxt/test-utils`

Learnt from: CR
Repo: npmx-dev/npmx.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-03T10:09:38.777Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Write unit tests for core functionality using `vitest`

@danielroe danielroe requested review from 43081j March 18, 2026 16:51
@danielroe danielroe added this pull request to the merge queue Mar 18, 2026
@gameroman gameroman closed this Mar 18, 2026
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to a manual request Mar 18, 2026
@gameroman gameroman reopened this Mar 18, 2026
@gameroman gameroman marked this pull request as draft March 18, 2026 16:54
@gameroman
Copy link
Contributor Author

gameroman commented Mar 18, 2026

Waiting for stable release of module-replacements v3 for now

@43081j
Copy link
Contributor

43081j commented Mar 18, 2026

I'll aim to publish that today or tomorrow 👍

Then will review this myself too

@joaopedrodcf
Copy link

Hyped by this PR 🙏🙏

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.

4 participants