Skip to content

Commit d4bcedf

Browse files
committed
fix: accept + in package versions
1 parent 47f6b33 commit d4bcedf

4 files changed

Lines changed: 58 additions & 6 deletions

File tree

CONTRIBUTING.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,59 @@ The connector will check your npm authentication, generate a connection token, a
110110
- We care about good types – never cast things to `any` 💪
111111
- Validate rather than just assert
112112

113+
### Server API patterns
114+
115+
#### Input validation with Valibot
116+
117+
Use Valibot schemas from `#shared/schemas/` to validate API inputs. This ensures type safety and provides consistent error messages:
118+
119+
```typescript
120+
import * as v from 'valibot'
121+
import { PackageRouteParamsSchema } from '#shared/schemas/package'
122+
123+
// In your handler:
124+
const { packageName, version } = v.parse(PackageRouteParamsSchema, {
125+
packageName: rawPackageName,
126+
version: rawVersion,
127+
})
128+
```
129+
130+
#### Error handling with `handleApiError`
131+
132+
Use the `handleApiError` utility for consistent error handling in API routes. It re-throws H3 errors (like 404s) and wraps other errors with a fallback message:
133+
134+
```typescript
135+
import { ERROR_NPM_FETCH_FAILED } from '#shared/utils/constants'
136+
137+
try {
138+
// API logic...
139+
} catch (error: unknown) {
140+
handleApiError(error, {
141+
statusCode: 502,
142+
message: ERROR_NPM_FETCH_FAILED,
143+
})
144+
}
145+
```
146+
147+
#### URL parameter parsing with `parsePackageParams`
148+
149+
Use `parsePackageParams` to extract package name and version from URL segments:
150+
151+
```typescript
152+
const pkgParamSegments = getRouterParam(event, 'pkg')?.split('/') ?? []
153+
const { rawPackageName, rawVersion } = parsePackageParams(pkgParamSegments)
154+
```
155+
156+
This handles patterns like `/pkg`, `/pkg/v/1.0.0`, `/@scope/pkg`, and `/@scope/pkg/v/1.0.0`.
157+
158+
#### Constants
159+
160+
Define error messages and other string constants in `#shared/utils/constants.ts` to ensure consistency across the codebase:
161+
162+
```typescript
163+
export const ERROR_NPM_FETCH_FAILED = 'Failed to fetch package from npm registry.'
164+
```
165+
113166
### Import order
114167

115168
1. Type imports first (`import type { ... }`)

server/api/registry/readme/[...pkg].get.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as v from 'valibot'
22
import { PackageRouteParamsSchema } from '#shared/schemas/package'
33
import {
44
CACHE_MAX_AGE_ONE_HOUR,
5-
ERROR_README_FETCH_FAILED,
5+
NPM_MISSING_README_SENTINEL,
66
ERROR_NPM_FETCH_FAILED,
77
} from '#shared/utils/constants'
88

@@ -72,7 +72,7 @@ export default defineCachedEventHandler(
7272
}
7373

7474
// If no README in packument, try fetching from jsdelivr (package tarball)
75-
if (!readmeContent || readmeContent === ERROR_README_FETCH_FAILED) {
75+
if (!readmeContent || readmeContent === NPM_MISSING_README_SENTINEL) {
7676
readmeContent = (await fetchReadmeFromJsdelivr(packageName, version)) ?? undefined
7777
}
7878

shared/schemas/package.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ export const PackageNameSchema = v.pipe(
1616

1717
/**
1818
* Enforces a SemVer-like pattern to prevent directory traversal or complex injection attacks
19-
* includes: alphanumeric, dots, underscores, and dashes
19+
* includes: alphanumeric, dots, underscores, dashes, and plus signs (for build metadata)
2020
*/
2121
export const VersionSchema = v.pipe(
2222
v.string(),
2323
v.nonEmpty('Version is required'),
24-
v.regex(/^[a-z0-9._-]+$/i, 'Invalid version format'),
24+
v.regex(/^[a-z0-9._+-]+$/i, 'Invalid version format'),
2525
)
2626

2727
/**

shared/utils/constants.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ export const CACHE_MAX_AGE_ONE_YEAR = 60 * 60 * 24 * 365
66
// API Strings
77
export const NPM_REGISTRY = 'https://registry.npmjs.org'
88
export const ERROR_PACKAGE_ANALYSIS_FAILED = 'Failed to analyze package.'
9-
export const ERROR_PACKAGE_VERSION_FAILED = 'Version is required.'
109
export const ERROR_PACKAGE_VERSION_AND_FILE_FAILED = 'Version and file path are required.'
1110
export const ERROR_PACKAGE_REQUIREMENTS_FAILED =
1211
'Package name, version, and file path are required.'
1312
export const ERROR_FILE_LIST_FETCH_FAILED = 'Failed to fetch file list.'
1413
export const ERROR_CALC_INSTALL_SIZE_FAILED = 'Failed to calculate install size.'
15-
export const ERROR_README_FETCH_FAILED = 'ERROR: No README data found!'
14+
export const NPM_MISSING_README_SENTINEL = 'ERROR: No README data found!'
1615
export const ERROR_JSR_FETCH_FAILED = 'Failed to fetch package from JSR registry.'
1716
export const ERROR_NPM_FETCH_FAILED = 'Failed to fetch package from npm registry.'

0 commit comments

Comments
 (0)