Skip to content

Commit 2c99239

Browse files
committed
feat(provenance): add api endpoint for provenance details
1 parent 0927569 commit 2c99239

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as v from 'valibot'
2+
import { PackageRouteParamsSchema } from '#shared/schemas/package'
3+
import type { NpmVersionDist } from '#shared/types'
4+
import { CACHE_MAX_AGE_ONE_HOUR, ERROR_PROVENANCE_FETCH_FAILED } from '#shared/utils/constants'
5+
import {
6+
parseAttestationToProvenanceDetails,
7+
type NpmAttestationsResponse,
8+
} from '#server/utils/provenance'
9+
10+
/**
11+
* GET /api/registry/provenance/:name/v/:version
12+
*
13+
* Returns parsed provenance details for a package version (build summary, source commit, build file, public ledger).
14+
* Version is required. Returns null when the version has no attestations or parsing fails.
15+
*/
16+
export default defineCachedEventHandler(
17+
async event => {
18+
const pkgParamSegments = getRouterParam(event, 'pkg')?.split('/') ?? []
19+
20+
const { rawPackageName, rawVersion } = parsePackageParams(pkgParamSegments)
21+
22+
if (!rawVersion) {
23+
throw createError({
24+
statusCode: 400,
25+
message: 'Version is required for provenance.',
26+
})
27+
}
28+
29+
try {
30+
const parsed = v.parse(PackageRouteParamsSchema, {
31+
packageName: rawPackageName,
32+
version: rawVersion,
33+
})
34+
const { packageName, version } = parsed
35+
if (!version) {
36+
throw createError({
37+
statusCode: 400,
38+
message: 'Version is required for provenance.',
39+
})
40+
}
41+
42+
const packument = await fetchNpmPackage(packageName)
43+
const versionData = packument.versions[version]
44+
const dist = versionData?.dist as NpmVersionDist | undefined
45+
const attestationsUrl = dist?.attestations?.url
46+
47+
if (!attestationsUrl) {
48+
return null
49+
}
50+
51+
const response = await $fetch<NpmAttestationsResponse>(attestationsUrl)
52+
const details = parseAttestationToProvenanceDetails(response)
53+
return details
54+
} catch (error: unknown) {
55+
handleApiError(error, {
56+
statusCode: 502,
57+
message: ERROR_PROVENANCE_FETCH_FAILED,
58+
})
59+
}
60+
},
61+
{
62+
maxAge: CACHE_MAX_AGE_ONE_HOUR,
63+
swr: true,
64+
getKey: event => {
65+
const pkg = getRouterParam(event, 'pkg') ?? ''
66+
return `provenance:v1:${pkg.replace(/\/+$/, '').trim()}`
67+
},
68+
},
69+
)

0 commit comments

Comments
 (0)