forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import { getLatestVersion } from 'fast-npm-meta'
2import { createError } from 'h3'
3import validatePackageName from 'validate-npm-package-name'
4
5const NPM_USERNAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i
6const NPM_USERNAME_MAX_LENGTH = 50
7
8/**
9 * Encode package name for URL usage.
10 * Scoped packages need special handling (@scope/name → @scope%2Fname)
11 */
12export function encodePackageName(name: string): string {
13 if (name.startsWith('@')) {
14 return `@${encodeURIComponent(name.slice(1))}`
15 }
16 return encodeURIComponent(name)
17}
18
19/**
20 * Fetch the latest version of a package using fast-npm-meta API.
21 * This is a lightweight alternative to fetching the full packument.
22 *
23 * @param name Package name
24 * @returns Latest version string or null if not found
25 * @see https://github.com/antfu/fast-npm-meta
26 */
27export async function fetchLatestVersion(name: string): Promise<string | null> {
28 try {
29 const meta = await getLatestVersion(name)
30 return meta.version
31 } catch {
32 return null
33 }
34}
35
36/**
37 * Validate an npm package name and throw an HTTP error if invalid.
38 * Uses validate-npm-package-name to check against npm naming rules.
39 */
40export function assertValidPackageName(name: string): void {
41 const result = validatePackageName(name)
42 if (!result.validForNewPackages && !result.validForOldPackages) {
43 const errors = [...(result.errors ?? []), ...(result.warnings ?? [])]
44 throw createError({
45 // TODO: throwing 404 rather than 400 as it's cacheable
46 statusCode: 404,
47 message: `Invalid package name: ${errors[0] ?? 'unknown error'}`,
48 })
49 }
50}
51
52/**
53 * Validate an npm username and throw an HTTP error if invalid.
54 * Uses a regular expression to check against npm naming rules.
55 * @public
56 */
57export function assertValidUsername(username: string): void {
58 if (!username || username.length > NPM_USERNAME_MAX_LENGTH || !NPM_USERNAME_RE.test(username)) {
59 throw createError({
60 // TODO: throwing 404 rather than 400 as it's cacheable
61 statusCode: 404,
62 message: `Invalid username: ${username}`,
63 })
64 }
65}