Skip to content

Conversation

@BYK
Copy link
Contributor

@BYK BYK commented Jan 27, 2026

Summary

Add support for getting package information in Markdown format, useful for AI/LLM consumption and command-line tools.

Access Methods

  • URL suffix: /<package>.md (e.g., /vue.md, /@nuxt/kit.md)
  • Accept header: Accept: text/markdown

Output Includes

  • Package metadata (name, version, license, last updated)
  • Stats table with weekly downloads (with sparkline trend) and dependency count
  • Install command
  • Links (npm, repository, homepage, issues)
  • Keywords and maintainers
  • Full README content

Security Hardening

  • URL validation for homepage/bugs links (prevents javascript: protocol injection)
  • README size limit (500KB) to prevent resource exhaustion
  • Path exclusion alignment between Vercel rewrites and middleware

@vercel
Copy link

vercel bot commented Jan 27, 2026

@BYK is attempting to deploy a commit to the danielroe Team on Vercel.

A member of the Team first needs to authorize it.

@BYK BYK marked this pull request as ready for review January 27, 2026 00:41
@danielroe danielroe requested a review from atinux January 27, 2026 00:51
@vercel
Copy link

vercel bot commented Jan 27, 2026

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

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Feb 9, 2026 5:18pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Feb 9, 2026 5:18pm
npmx-lunaria Ignored Ignored Feb 9, 2026 5:18pm

Request Review

@danielroe
Copy link
Member

danielroe commented Jan 28, 2026

ultimately it might make sense to share some logic with the client (see #169) so we can click 'copy as markdown'

...although that button could also fetch the markdown from the server endpoint, which might be better from a JS bundle size

so... just linking so you're aware

@BYK
Copy link
Contributor Author

BYK commented Jan 30, 2026

@atinux @okineadev are you waiting on me for something? (just making sure I'm not the blocker here)

@atinux
Copy link
Contributor

atinux commented Jan 30, 2026

Sorry I was quite busy.

I suggest to not use a middleware here but instead having a server route with /raw prefix similar to how we have here: https://github.com/unjs/undocs/blob/main/app/server/routes/raw/%5B...slug%5D.md.get.ts

Then like on https://github.com/unjs/undocs/blob/main/app/modules/md-rewrite.ts, we can add the rewrite in the generated vercel.json to it happens at the CDN level

Would you be up to update your PR with this approach @BYK ?

BYK added 3 commits January 31, 2026 02:04
Add support for getting package information in Markdown format via:
- URL suffix: /package-name.md
- Accept header: text/markdown

Includes security hardening:
- URL validation for homepage/bugs links (prevents javascript: injection)
- README size limit (500KB) to prevent DoS
- Path exclusion alignment between Vercel rewrites and middleware
Address review feedback from @atinux:
- Replace middleware with server route at /raw/[...slug].md
- Update vercel.json rewrites to point to /raw/:path.md
- Add curl user-agent rewrite support for CLI tools
- Enable CDN-level caching for markdown responses

Also fix maintainer URLs to use npmx.dev instead of npmjs.com
@BYK
Copy link
Contributor Author

BYK commented Jan 31, 2026

@atinux no worries. Updated :)

@codecov
Copy link

codecov bot commented Feb 4, 2026

Codecov Report

❌ Patch coverage is 0% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/pages/package/[...package].vue 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds server-side support to serve package information as Markdown via a new /raw/** route and a server route handler that parses slugs, resolves package and version data, fetches README with a jsDelivr fallback, obtains weekly and 12‑week download stats and repository info, and returns generated Markdown with Content-Type text/markdown and ISR-aligned caching (60s). Adds server-side markdown generation utilities (sparkline, formatting, URL normalisation, README handling), an ISR route rule for /raw/** in nuxt.config, a markdown alternate link on the package page, Vercel rewrites for markdown/curl requests, unit tests for markdown utilities, and a codecov ignore update.

Suggested reviewers

  • danielroe
  • ghostdevv
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description clearly describes the changeset: adding Markdown format support for package information with specific access methods, output content, and security hardening measures.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

Actionable comments posted: 3

🧹 Nitpick comments (3)
server/routes/raw/[...slug].md.get.ts (1)

130-235: Consider splitting the handler into smaller helpers.

This handler is well over the 50-line guideline; extracting readme, downloads, and version resolution into helpers would improve readability and testability.

As per coding guidelines: Keep functions focused and manageable (generally under 50 lines).

server/utils/markdown.ts (2)

8-27: Guard the sparkline index access.

Clamping or fallbacking the array access keeps the indexing explicitly safe.

🛠️ Safer index access
-      const index = Math.round(normalized * (SPARKLINE_CHARS.length - 1))
-      return SPARKLINE_CHARS[index]
+      const index = Math.round(normalized * (SPARKLINE_CHARS.length - 1))
+      return SPARKLINE_CHARS[index] ?? SPARKLINE_CHARS[SPARKLINE_CHARS.length - 1]
As per coding guidelines: Ensure you write strictly type-safe code, for example by ensuring you always check when accessing an array value by index.

115-279: Consider extracting sections to reduce function length.

generatePackageMarkdown is very long; splitting into section builders (stats, links, readme) would improve readability and maintenance.

As per coding guidelines: Keep functions focused and manageable (generally under 50 lines).

@BYK
Copy link
Contributor Author

BYK commented Feb 4, 2026

Addressed the sparkline feedback - now showing both numeric trend data and visual sparkline:

Downloads (weekly): 1,234,567 [1.2M 1.3M 1.4M 1.5M] ▁▂▃▅

This provides both LLM-friendly numeric data and human-readable visual trend.

- Fix Cache-Control mismatch: change from 3600s to 60s to match ISR TTL
- Add URL validation after normalizeGitUrl() to skip non-HTTP URLs
- Fix installSize check: use !== undefined instead of truthiness
- Enhance sparkline with numeric trend values for LLM readability
- Add comprehensive unit tests for markdown utils (97.97% coverage)
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

🧹 Nitpick comments (1)
server/routes/raw/[...slug].md.get.ts (1)

31-44: Guard jsDelivr README fetches with a size cap.

The 500KB limit is applied after the body is fully downloaded, so very large READMEs still consume memory and bandwidth. Consider checking content-length (when present) and truncating/short‑circuiting early.

🛠️ Example improvement
   for (const filename of readmeFilenames) {
     try {
       const url = `https://cdn.jsdelivr.net/npm/${packageName}${versionSuffix}/${filename}`
+      const maxReadmeBytes = 500 * 1024
       const response = await fetch(url)
       if (response.ok) {
-        return await response.text()
+        const length = Number(response.headers.get('content-length') ?? 0)
+        if (length && length > maxReadmeBytes) {
+          continue
+        }
+        const text = await response.text()
+        return text.length > maxReadmeBytes ? text.slice(0, maxReadmeBytes) : text
       }
     } catch {
       // Try next filename
     }
   }

- Add index clamping in generateSparkline() to prevent floating-point edge cases
- Update getRepositoryUrl() to handle string repository values (common in npm metadata)
- Add unit tests for string repository handling
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: 1

🧹 Nitpick comments (5)
server/utils/markdown.ts (5)

31-45: Consider sharing formatting utilities with the client.

These functions duplicate logic from app/utils/formatters.ts. Per the PR discussion, danielroe suggested sharing logic to enable features like "copy as markdown" on the client. Consider moving these to a shared location (e.g., shared/utils/formatters.ts) to maintain consistency and reduce duplication.

Note: The implementations differ slightly (formatCompactNumber here uses Intl.NumberFormat whilst the client version uses manual thresholds), so unification would require choosing one approach.


47-49: Consider expanding the escaped character set.

The function escapes common Markdown formatting characters but omits others that could cause rendering issues in certain contexts (e.g., # at line start, >, | in tables). Since descriptions are placed in blockquotes, the current set may suffice, but be aware that multi-line descriptions containing these characters could render unexpectedly.


268-277: Consider a type guard for cleaner maintainer handling.

The type assertion on line 270 works but could be replaced with a type guard for better type safety and readability.

♻️ Optional refactor with type guard
+interface NpmMaintainer {
+  name?: string
+  email?: string
+  username?: string
+}
+
+function hasUsername(m: unknown): m is NpmMaintainer {
+  return typeof m === 'object' && m !== null && 'username' in m
+}
+
 // In the maintainers loop:
-      // npm API returns username but `@npm/types` Contact doesn't include it
-      const username = (maintainer as { username?: string }).username
-      const name = maintainer.name || username || 'Unknown'
+      const username = hasUsername(maintainer) ? maintainer.username : undefined
+      const name = maintainer.name || username || 'Unknown'

132-299: Consider breaking down generatePackageMarkdown into smaller helpers.

At ~167 lines, this function exceeds the 50-line guideline. The function is logically organised with clear section comments, but extracting helpers (e.g., buildMetaSection, buildStatsTable, buildLinksSection) would improve testability and maintainability.

As per coding guidelines, Keep functions focused and manageable (generally under 50 lines).


51-58: Consider refactoring to use or align with the shared normalizeGitUrl utility.

The codebase has a more comprehensive normalizeGitUrl function in shared/utils/git-providers.ts (lines 295-319) that handles a broader range of Git URL formats:

  • Proper URL parsing for ssh:// and git:// protocols using the URL constructor
  • Generic SCP-style URL handling (git@host:path) for any host, not just GitHub
  • Error handling with try-catch
  • Input validation (empty string checks)

The current implementation in server/utils/markdown.ts (lines 51-58) is GitHub-specific and lacks error handling. It cannot normalise non-GitHub SSH or SCP-style URLs correctly.

Note: The shared utility returns string | null rather than string. If refactoring to use it, the calling code at line 82 must handle the nullable return value appropriately.

- Remove unused repoInfo parameter from PackageMarkdownOptions interface
- Exclude app/pages and app/layouts from codecov patch coverage
  (these are tested by Playwright browser tests, not unit tests)
@BYK
Copy link
Contributor Author

BYK commented Feb 4, 2026

@atinux ready for another look?

Comment on lines +4 to +15
"rewrites": [
{
"source": "/:path((?!api|_nuxt|_v|__nuxt|search|code|raw/).*)",
"has": [{ "type": "header", "key": "accept", "value": "(.*?)text/markdown(.*)" }],
"destination": "/raw/:path.md"
},
{
"source": "/:path((?!api|_nuxt|_v|__nuxt|search|code|raw/).*)",
"has": [{ "type": "header", "key": "user-agent", "value": "curl/.*" }],
"destination": "/raw/:path.md"
}
],
Copy link
Contributor

Choose a reason for hiding this comment

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

We also need support for package/* paths

$ curl -L https://npmxdev-git-fork-byk-byk-featmd-poetry.vercel.app/package/vite
A server error has occurred

FUNCTION_INVOCATION_FAILED

arn1::r7r5t-1770279708442-3169599c59e0

$ curl -L https://npmxdev-git-fork-byk-byk-featmd-poetry.vercel.app/vite
# vite

> Native-ESM powered web dev build tool

...

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, are you happy to add this alias @BYK ?

@ghostdevv
Copy link
Contributor

ghostdevv commented Feb 6, 2026

ah! we do now have a copy as markdown button on the frontend #1020 - if you rebase you could make any change you need to use your new logic? It would be nice if that button also fetched it dynamically

const username = (maintainer as { username?: string }).username
const name = maintainer.name || username || 'Unknown'
if (username) {
lines.push(`- [${name}](https://npmx.dev/~${username})`)
Copy link
Contributor

Choose a reason for hiding this comment

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

this may not be correct as maintainers is a user supplied field

@okineadev
Copy link
Contributor

I think we should also take a look at #1382
It would be nice to somehow integrate the agent instruction fetching functionality as well

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.

5 participants