@@ -204,36 +204,54 @@ function matchOsvApi(urlString) {
204204}
205205
206206/**
207- * @param {string } urlString
208- * @returns {MockResponse | null }
207+ * Parse a package query string into name and specifier.
208+ * Handles scoped packages: "@scope/name@specifier" and "name@specifier".
209+ *
210+ * @param {string } query
211+ * @param {string } defaultSpecifier
212+ * @returns {{ name: string; specifier: string } }
209213 */
210- function matchFastNpmMeta ( urlString ) {
211- const url = new URL ( urlString )
212- let packageName = decodeURIComponent ( url . pathname . slice ( 1 ) )
213-
214- if ( ! packageName ) return null
215-
216- let specifier = 'latest'
217- if ( packageName . startsWith ( '@' ) ) {
218- const atIndex = packageName . indexOf ( '@' , 1 )
214+ function parsePackageQuery ( query , defaultSpecifier ) {
215+ let name = query
216+ let specifier = defaultSpecifier
217+ if ( name . startsWith ( '@' ) ) {
218+ const atIndex = name . indexOf ( '@' , 1 )
219219 if ( atIndex !== - 1 ) {
220- specifier = packageName . slice ( atIndex + 1 )
221- packageName = packageName . slice ( 0 , atIndex )
220+ specifier = name . slice ( atIndex + 1 )
221+ name = name . slice ( 0 , atIndex )
222222 }
223223 } else {
224- const atIndex = packageName . indexOf ( '@' )
224+ const atIndex = name . indexOf ( '@' )
225225 if ( atIndex !== - 1 ) {
226- specifier = packageName . slice ( atIndex + 1 )
227- packageName = packageName . slice ( 0 , atIndex )
226+ specifier = name . slice ( atIndex + 1 )
227+ name = name . slice ( 0 , atIndex )
228228 }
229229 }
230+ return { name, specifier }
231+ }
232+
233+ /**
234+ * Build a latest-version response for a single package (GET /:pkg endpoint).
235+ *
236+ * @param {string } query
237+ * @returns {object }
238+ */
239+ function resolveSingleLatest ( query ) {
240+ const { name, specifier } = parsePackageQuery ( query , 'latest' )
241+ const packument = readFixture ( packageToFixturePath ( name ) )
230242
231- const packument = readFixture ( packageToFixturePath ( packageName ) )
232- if ( ! packument ) return null
243+ if ( ! packument ) {
244+ return {
245+ name,
246+ specifier,
247+ version : '0.0.0' ,
248+ publishedAt : new Date ( ) . toISOString ( ) ,
249+ lastSynced : Date . now ( ) ,
250+ }
251+ }
233252
234253 const distTags = packument [ 'dist-tags' ]
235254 const versions = packument . versions
236- const time = packument . time
237255
238256 let version
239257 if ( specifier === 'latest' || ! specifier ) {
@@ -246,15 +264,72 @@ function matchFastNpmMeta(urlString) {
246264 version = distTags && distTags . latest
247265 }
248266
249- if ( ! version ) return null
267+ if ( ! version ) {
268+ return {
269+ name,
270+ specifier,
271+ version : '0.0.0' ,
272+ publishedAt : new Date ( ) . toISOString ( ) ,
273+ lastSynced : Date . now ( ) ,
274+ }
275+ }
250276
251- return json ( {
252- name : packageName ,
277+ return {
278+ name,
253279 specifier,
254280 version,
255- publishedAt : ( time && time [ version ] ) || new Date ( ) . toISOString ( ) ,
281+ publishedAt : ( packument . time && packument . time [ version ] ) || new Date ( ) . toISOString ( ) ,
256282 lastSynced : Date . now ( ) ,
257- } )
283+ }
284+ }
285+
286+ /**
287+ * Build a versions response for a single package (GET /versions/:pkg endpoint).
288+ *
289+ * @param {string } query
290+ * @returns {object }
291+ */
292+ function resolveSingleVersions ( query ) {
293+ const { name, specifier } = parsePackageQuery ( query , '*' )
294+ const packument = readFixture ( packageToFixturePath ( name ) )
295+
296+ if ( ! packument ) {
297+ return { name, error : `"https://registry.npmjs.org/${ name } ": 404 Not Found` }
298+ }
299+
300+ return {
301+ name,
302+ specifier,
303+ distTags : packument [ 'dist-tags' ] || { } ,
304+ versions : Object . keys ( packument . versions || { } ) ,
305+ time : packument . time || { } ,
306+ lastSynced : Date . now ( ) ,
307+ }
308+ }
309+
310+ /**
311+ * @param {string } urlString
312+ * @returns {MockResponse | null }
313+ */
314+ function matchFastNpmMeta ( urlString ) {
315+ const url = new URL ( urlString )
316+ let pathPart = decodeURIComponent ( url . pathname . slice ( 1 ) )
317+
318+ if ( ! pathPart ) return null
319+
320+ // /versions/ endpoint returns version lists (used by getVersionsBatch)
321+ const isVersions = pathPart . startsWith ( 'versions/' )
322+ if ( isVersions ) pathPart = pathPart . slice ( 'versions/' . length )
323+
324+ const resolveFn = isVersions ? resolveSingleVersions : resolveSingleLatest
325+
326+ // Batch requests: package1+package2+...
327+ if ( pathPart . includes ( '+' ) ) {
328+ const results = pathPart . split ( '+' ) . map ( resolveFn )
329+ return json ( results )
330+ }
331+
332+ return json ( resolveFn ( pathPart ) )
258333}
259334
260335/**
0 commit comments