From 4f085063b84d1498acb7e6b286ecafdc46555267 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:13:45 +0000 Subject: [PATCH 01/37] fix: correctly detect type info in badge --- .../api/registry/badge/[type]/[...pkg].get.ts | 71 +++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 5b4033e642..1f11361fb0 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -373,11 +373,72 @@ const badgeStrategies = { }, 'types': async (pkgData: globalThis.Packument) => { - const latest = getLatestVersion(pkgData) - const versionData = latest ? pkgData.versions?.[latest] : undefined - const hasTypes = !!(versionData?.types || versionData?.typings) - const value = hasTypes ? 'included' : 'missing' - const color = hasTypes ? COLORS.blue : COLORS.slate + const versionData = pkgData.versions?.latest + + if (!versionData) { + return { label: 'types', value: 'unknown', color: COLORS.slate } + } + + const pkgJson: ExtendedPackageJson = { + name: pkgData.name, + version: 'latest', + types: versionData.types, + typings: versionData.typings, + exports: versionData.exports, + main: versionData.main, + module: versionData.module, + type: versionData.type, + } + + let typesStatus: TypesStatus = { kind: 'none' } + let detailColor = COLORS.blue + + if (hasBuiltInTypes(pkgJson)) { + typesStatus = { kind: 'included' } + } else { + try { + const [fileTreeResult, typesMetaResult] = await Promise.allSettled([ + getPackageFileTree(pkgData.name, latest), + getLatestVersion(getTypesPackageName(pkgData.name), { metadata: true, throw: false }), + ]) + + let files: Set | undefined + if (fileTreeResult.status === 'fulfilled') { + files = flattenFileTree(fileTreeResult.value.tree) + } + + let typesPkg: TypesPackageInfo | undefined + if (typesMetaResult.status === 'fulfilled' && !('error' in typesMetaResult.value)) { + typesPkg = { + packageName: getTypesPackageName(pkgData.name), + deprecated: typesMetaResult.value.deprecated, + } + } + + typesStatus = detectTypesStatus(pkgJson, typesPkg, files) + } catch {} + } + + let value: 'included' | '@types' | 'missing' + let color: string + + switch (typesStatus.kind) { + case 'included': { + value = 'included' + color = COLORS.blue + break + } + case '@types': { + value = '@types' + color = COLORS.blue + break + } + default: { + value = 'missing' + color = COLORS.slate + break + } + } return { label: 'types', value, color } }, From cdb8b16eb5d3574ed2c600bfceacea3be2b3ec0c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:24:36 +0000 Subject: [PATCH 02/37] Revert "fix: correctly detect type info in badge" This reverts commit 31e5fed0ceef7b5f8efae1f543c81a31e29f2c79. --- .../api/registry/badge/[type]/[...pkg].get.ts | 71 ++----------------- 1 file changed, 5 insertions(+), 66 deletions(-) diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 1f11361fb0..5b4033e642 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -373,72 +373,11 @@ const badgeStrategies = { }, 'types': async (pkgData: globalThis.Packument) => { - const versionData = pkgData.versions?.latest - - if (!versionData) { - return { label: 'types', value: 'unknown', color: COLORS.slate } - } - - const pkgJson: ExtendedPackageJson = { - name: pkgData.name, - version: 'latest', - types: versionData.types, - typings: versionData.typings, - exports: versionData.exports, - main: versionData.main, - module: versionData.module, - type: versionData.type, - } - - let typesStatus: TypesStatus = { kind: 'none' } - let detailColor = COLORS.blue - - if (hasBuiltInTypes(pkgJson)) { - typesStatus = { kind: 'included' } - } else { - try { - const [fileTreeResult, typesMetaResult] = await Promise.allSettled([ - getPackageFileTree(pkgData.name, latest), - getLatestVersion(getTypesPackageName(pkgData.name), { metadata: true, throw: false }), - ]) - - let files: Set | undefined - if (fileTreeResult.status === 'fulfilled') { - files = flattenFileTree(fileTreeResult.value.tree) - } - - let typesPkg: TypesPackageInfo | undefined - if (typesMetaResult.status === 'fulfilled' && !('error' in typesMetaResult.value)) { - typesPkg = { - packageName: getTypesPackageName(pkgData.name), - deprecated: typesMetaResult.value.deprecated, - } - } - - typesStatus = detectTypesStatus(pkgJson, typesPkg, files) - } catch {} - } - - let value: 'included' | '@types' | 'missing' - let color: string - - switch (typesStatus.kind) { - case 'included': { - value = 'included' - color = COLORS.blue - break - } - case '@types': { - value = '@types' - color = COLORS.blue - break - } - default: { - value = 'missing' - color = COLORS.slate - break - } - } + const latest = getLatestVersion(pkgData) + const versionData = latest ? pkgData.versions?.[latest] : undefined + const hasTypes = !!(versionData?.types || versionData?.typings) + const value = hasTypes ? 'included' : 'missing' + const color = hasTypes ? COLORS.blue : COLORS.slate return { label: 'types', value, color } }, From 42a0f2270510b0f5ed376a665b3b250b4b9b23aa Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:33:19 +0000 Subject: [PATCH 03/37] feat: ai sloppity slop --- server/api/registry/analysis/[...pkg].get.ts | 73 +++++++++++--------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/server/api/registry/analysis/[...pkg].get.ts b/server/api/registry/analysis/[...pkg].get.ts index 940bc25925..10e3b3fbd0 100644 --- a/server/api/registry/analysis/[...pkg].get.ts +++ b/server/api/registry/analysis/[...pkg].get.ts @@ -44,38 +44,7 @@ export default defineCachedEventHandler( packageName: decodeURIComponent(rawPackageName), version: rawVersion, }) - - // Fetch package data - const encodedName = encodePackageName(packageName) - const versionSuffix = version ? `/${version}` : '/latest' - const pkg = await $fetch( - `${NPM_REGISTRY}/${encodedName}${versionSuffix}`, - ) - - let typesPackage: TypesPackageInfo | undefined - let files: Set | undefined - - // Only check for @types and files when the package doesn't ship its own types - if (!hasBuiltInTypes(pkg)) { - const typesPkgName = getTypesPackageName(packageName) - const resolvedVersion = pkg.version ?? version ?? 'latest' - - // Fetch @types info and file tree in parallel — they are independent - const [typesResult, fileTreeResult] = await Promise.allSettled([ - fetchTypesPackageInfo(typesPkgName), - getPackageFileTree(packageName, resolvedVersion), - ]) - - if (typesResult.status === 'fulfilled') { - typesPackage = typesResult.value - } - if (fileTreeResult.status === 'fulfilled') { - files = flattenFileTree(fileTreeResult.value.tree) - } - } - - // Check for associated create-* package (e.g., vite -> create-vite, next -> create-next-app) - // Only show if the packages are actually associated (same maintainers or same org) + const { pkg, typesPackage, files } = await fetchPackageWithTypesAndFiles(packageName, version) const createPackage = await findAssociatedCreatePackage(packageName, pkg) const analysis = analyzePackage(pkg, { typesPackage, @@ -107,6 +76,46 @@ export default defineCachedEventHandler( }, ) +export async function fetchPackageWithTypesAndFiles( + packageName: string, + version?: string, +): Promise<{ + pkg: AnalysisPackageJson + typesPackage?: TypesPackageInfo + files?: Set +}> { + // Fetch main package data + const encodedName = encodePackageName(packageName) + const versionSuffix = version ? `/${version}` : '/latest' + + const pkg = await $fetch(`${NPM_REGISTRY}/${encodedName}${versionSuffix}`) + + let typesPackage: TypesPackageInfo | undefined + let files: Set | undefined + + // Only attempt to fetch @types + file tree when the package doesn't ship its own types + if (!hasBuiltInTypes(pkg)) { + const typesPkgName = getTypesPackageName(packageName) + const resolvedVersion = pkg.version ?? version ?? 'latest' + + // Fetch both in parallel — they're independent + const [typesResult, fileTreeResult] = await Promise.allSettled([ + fetchTypesPackageInfo(typesPkgName), + getPackageFileTree(packageName, resolvedVersion), + ]) + + if (typesResult.status === 'fulfilled') { + typesPackage = typesResult.value + } + + if (fileTreeResult.status === 'fulfilled') { + files = flattenFileTree(fileTreeResult.value.tree) + } + } + + return { pkg, typesPackage, files } +} + /** * Fetch @types package info including deprecation status using fast-npm-meta. * Returns undefined if the package doesn't exist. From 715a3d774185d7e474537aa9bd85078a05ffacef Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:40:53 +0000 Subject: [PATCH 04/37] feat: more ai slop --- .../api/registry/badge/[type]/[...pkg].get.ts | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 5b4033e642..57357ed2ca 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -6,6 +6,7 @@ import { PackageRouteParamsSchema } from '#shared/schemas/package' import { CACHE_MAX_AGE_ONE_HOUR, ERROR_NPM_FETCH_FAILED } from '#shared/utils/constants' import { fetchNpmPackage } from '#server/utils/npm' import { assertValidPackageName } from '#shared/utils/npm' +import { detectTypesStatus } from '#server/utils/package-analysis' import { handleApiError } from '#server/utils/error-handler' const NPM_DOWNLOADS_API = 'https://api.npmjs.org/downloads/point' @@ -372,12 +373,39 @@ const badgeStrategies = { return { label: 'node', value: nodeVersion, color: COLORS.yellow } }, - 'types': async (pkgData: globalThis.Packument) => { - const latest = getLatestVersion(pkgData) - const versionData = latest ? pkgData.versions?.[latest] : undefined - const hasTypes = !!(versionData?.types || versionData?.typings) - const value = hasTypes ? 'included' : 'missing' - const color = hasTypes ? COLORS.blue : COLORS.slate + 'types': async (pkgData: globalThis.Packument, requestedVersion?: string) => { + const { pkg, typesPackage, files } = await fetchPackageWithTypesAndFiles( + pkgData.name, + requestedVersion, + ) + + const typesStatus = detectTypesStatus(pkg, typesPackage, files) + + let value: string + let color: string + + switch (typesStatus.kind) { + case 'included': + value = 'included' + color = COLORS.blue + break + + case '@types': + value = '@types' + color = COLORS.purple + if (typesStatus.deprecated) { + value += ' (deprecated)' + color = COLORS.red + } + break + + case 'none': + default: + value = 'missing' + color = COLORS.slate + break + } + return { label: 'types', value, color } }, From 39e2967f801da70b3cce7c5ea280e46b390555fd Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:42:22 +0000 Subject: [PATCH 05/37] Update [...pkg].get.ts --- server/api/registry/badge/[type]/[...pkg].get.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 57357ed2ca..dd418692a2 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -6,7 +6,7 @@ import { PackageRouteParamsSchema } from '#shared/schemas/package' import { CACHE_MAX_AGE_ONE_HOUR, ERROR_NPM_FETCH_FAILED } from '#shared/utils/constants' import { fetchNpmPackage } from '#server/utils/npm' import { assertValidPackageName } from '#shared/utils/npm' -import { detectTypesStatus } from '#server/utils/package-analysis' +import { detectTypesStatus } from '#shared/utils/package-analysis' import { handleApiError } from '#server/utils/error-handler' const NPM_DOWNLOADS_API = 'https://api.npmjs.org/downloads/point' From 3bc3166d74db8c1546742ab8d2499d977c3c210a Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:44:20 +0000 Subject: [PATCH 06/37] fix --- server/api/registry/analysis/[...pkg].get.ts | 41 +------------------ .../api/registry/badge/[type]/[...pkg].get.ts | 2 +- shared/utils/package-analysis.ts | 40 ++++++++++++++++++ 3 files changed, 42 insertions(+), 41 deletions(-) diff --git a/server/api/registry/analysis/[...pkg].get.ts b/server/api/registry/analysis/[...pkg].get.ts index 10e3b3fbd0..6e23404353 100644 --- a/server/api/registry/analysis/[...pkg].get.ts +++ b/server/api/registry/analysis/[...pkg].get.ts @@ -11,6 +11,7 @@ import { getTypesPackageName, getCreatePackageName, hasBuiltInTypes, + fetchPackageWithTypesAndFiles, } from '#shared/utils/package-analysis' import { getDevDependencySuggestion, @@ -76,46 +77,6 @@ export default defineCachedEventHandler( }, ) -export async function fetchPackageWithTypesAndFiles( - packageName: string, - version?: string, -): Promise<{ - pkg: AnalysisPackageJson - typesPackage?: TypesPackageInfo - files?: Set -}> { - // Fetch main package data - const encodedName = encodePackageName(packageName) - const versionSuffix = version ? `/${version}` : '/latest' - - const pkg = await $fetch(`${NPM_REGISTRY}/${encodedName}${versionSuffix}`) - - let typesPackage: TypesPackageInfo | undefined - let files: Set | undefined - - // Only attempt to fetch @types + file tree when the package doesn't ship its own types - if (!hasBuiltInTypes(pkg)) { - const typesPkgName = getTypesPackageName(packageName) - const resolvedVersion = pkg.version ?? version ?? 'latest' - - // Fetch both in parallel — they're independent - const [typesResult, fileTreeResult] = await Promise.allSettled([ - fetchTypesPackageInfo(typesPkgName), - getPackageFileTree(packageName, resolvedVersion), - ]) - - if (typesResult.status === 'fulfilled') { - typesPackage = typesResult.value - } - - if (fileTreeResult.status === 'fulfilled') { - files = flattenFileTree(fileTreeResult.value.tree) - } - } - - return { pkg, typesPackage, files } -} - /** * Fetch @types package info including deprecation status using fast-npm-meta. * Returns undefined if the package doesn't exist. diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index dd418692a2..65fe134f37 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -6,7 +6,7 @@ import { PackageRouteParamsSchema } from '#shared/schemas/package' import { CACHE_MAX_AGE_ONE_HOUR, ERROR_NPM_FETCH_FAILED } from '#shared/utils/constants' import { fetchNpmPackage } from '#server/utils/npm' import { assertValidPackageName } from '#shared/utils/npm' -import { detectTypesStatus } from '#shared/utils/package-analysis' +import { detectTypesStatus, fetchPackageWithTypesAndFiles } from '#shared/utils/package-analysis' import { handleApiError } from '#server/utils/error-handler' const NPM_DOWNLOADS_API = 'https://api.npmjs.org/downloads/point' diff --git a/shared/utils/package-analysis.ts b/shared/utils/package-analysis.ts index 57ed391d82..a4b17a330f 100644 --- a/shared/utils/package-analysis.ts +++ b/shared/utils/package-analysis.ts @@ -106,6 +106,46 @@ interface ExportsAnalysis { hasTypes: boolean } +export async function fetchPackageWithTypesAndFiles( + packageName: string, + version?: string, +): Promise<{ + pkg: AnalysisPackageJson + typesPackage?: TypesPackageInfo + files?: Set +}> { + // Fetch main package data + const encodedName = encodePackageName(packageName) + const versionSuffix = version ? `/${version}` : '/latest' + + const pkg = await $fetch(`${NPM_REGISTRY}/${encodedName}${versionSuffix}`) + + let typesPackage: TypesPackageInfo | undefined + let files: Set | undefined + + // Only attempt to fetch @types + file tree when the package doesn't ship its own types + if (!hasBuiltInTypes(pkg)) { + const typesPkgName = getTypesPackageName(packageName) + const resolvedVersion = pkg.version ?? version ?? 'latest' + + // Fetch both in parallel — they're independent + const [typesResult, fileTreeResult] = await Promise.allSettled([ + fetchTypesPackageInfo(typesPkgName), + getPackageFileTree(packageName, resolvedVersion), + ]) + + if (typesResult.status === 'fulfilled') { + typesPackage = typesResult.value + } + + if (fileTreeResult.status === 'fulfilled') { + files = flattenFileTree(fileTreeResult.value.tree) + } + } + + return { pkg, typesPackage, files } +} + /** * Recursively analyze exports field for module format indicators */ From 7b62d5c9bba1b53f1008067c3b421eb0d3af8715 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:47:04 +0000 Subject: [PATCH 07/37] fix --- server/api/registry/analysis/[...pkg].get.ts | 15 --------------- shared/utils/package-analysis.ts | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/server/api/registry/analysis/[...pkg].get.ts b/server/api/registry/analysis/[...pkg].get.ts index 6e23404353..8d381dbe71 100644 --- a/server/api/registry/analysis/[...pkg].get.ts +++ b/server/api/registry/analysis/[...pkg].get.ts @@ -77,21 +77,6 @@ export default defineCachedEventHandler( }, ) -/** - * Fetch @types package info including deprecation status using fast-npm-meta. - * Returns undefined if the package doesn't exist. - */ -async function fetchTypesPackageInfo(packageName: string): Promise { - const result = await getLatestVersion(packageName, { metadata: true, throw: false }) - if ('error' in result) { - return undefined - } - return { - packageName, - deprecated: result.deprecated, - } -} - /** Package metadata needed for association validation */ interface PackageWithMeta { maintainers?: Array<{ name: string }> diff --git a/shared/utils/package-analysis.ts b/shared/utils/package-analysis.ts index a4b17a330f..49fd3299e9 100644 --- a/shared/utils/package-analysis.ts +++ b/shared/utils/package-analysis.ts @@ -106,6 +106,21 @@ interface ExportsAnalysis { hasTypes: boolean } +/** + * Fetch @types package info including deprecation status using fast-npm-meta. + * Returns undefined if the package doesn't exist. + */ +async function fetchTypesPackageInfo(packageName: string): Promise { + const result = await getLatestVersion(packageName, { metadata: true, throw: false }) + if ('error' in result) { + return undefined + } + return { + packageName, + deprecated: result.deprecated, + } +} + export async function fetchPackageWithTypesAndFiles( packageName: string, version?: string, From c8a67371df427e863cd8b43c26a27393b69526f4 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:47:31 +0000 Subject: [PATCH 08/37] Update package-analysis.ts --- shared/utils/package-analysis.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shared/utils/package-analysis.ts b/shared/utils/package-analysis.ts index 49fd3299e9..28dde824d9 100644 --- a/shared/utils/package-analysis.ts +++ b/shared/utils/package-analysis.ts @@ -106,6 +106,10 @@ interface ExportsAnalysis { hasTypes: boolean } +interface AnalysisPackageJson extends ExtendedPackageJson { + readme?: string +} + /** * Fetch @types package info including deprecation status using fast-npm-meta. * Returns undefined if the package doesn't exist. From dc6d15de95f45e54bb940a82484f422787f2ebb9 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:48:46 +0000 Subject: [PATCH 09/37] Update [...pkg].get.ts --- server/api/registry/analysis/[...pkg].get.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/api/registry/analysis/[...pkg].get.ts b/server/api/registry/analysis/[...pkg].get.ts index 8d381dbe71..12952dd4da 100644 --- a/server/api/registry/analysis/[...pkg].get.ts +++ b/server/api/registry/analysis/[...pkg].get.ts @@ -28,10 +28,6 @@ import { flattenFileTree } from '#server/utils/import-resolver' import { getPackageFileTree } from '#server/utils/file-tree' import { getLatestVersion, getLatestVersionBatch } from 'fast-npm-meta' -interface AnalysisPackageJson extends ExtendedPackageJson { - readme?: string -} - export default defineCachedEventHandler( async event => { // Parse package name and optional version from path From b93882bcbf779881ca793385300a99024474f481 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:49:18 +0000 Subject: [PATCH 10/37] fix --- server/api/registry/analysis/[...pkg].get.ts | 2 +- shared/utils/package-analysis.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/server/api/registry/analysis/[...pkg].get.ts b/server/api/registry/analysis/[...pkg].get.ts index 12952dd4da..fa9f603952 100644 --- a/server/api/registry/analysis/[...pkg].get.ts +++ b/server/api/registry/analysis/[...pkg].get.ts @@ -26,7 +26,7 @@ import { parseRepoUrl } from '#shared/utils/git-providers' import { encodePackageName } from '#shared/utils/npm' import { flattenFileTree } from '#server/utils/import-resolver' import { getPackageFileTree } from '#server/utils/file-tree' -import { getLatestVersion, getLatestVersionBatch } from 'fast-npm-meta' +import { getLatestVersionBatch } from 'fast-npm-meta' export default defineCachedEventHandler( async event => { diff --git a/shared/utils/package-analysis.ts b/shared/utils/package-analysis.ts index 28dde824d9..eedc781a62 100644 --- a/shared/utils/package-analysis.ts +++ b/shared/utils/package-analysis.ts @@ -2,6 +2,8 @@ * Package analysis utilities for detecting module format and TypeScript support */ +import { getLatestVersion } from 'fast-npm-meta' + export type ModuleFormat = 'esm' | 'cjs' | 'dual' | 'unknown' export type TypesStatus = From 4eb0b004093b30cbe77ba90ab546411571e59c1a Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:50:27 +0000 Subject: [PATCH 11/37] fix --- server/api/registry/analysis/[...pkg].get.ts | 4 ---- shared/utils/package-analysis.ts | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/server/api/registry/analysis/[...pkg].get.ts b/server/api/registry/analysis/[...pkg].get.ts index fa9f603952..5ed3520f01 100644 --- a/server/api/registry/analysis/[...pkg].get.ts +++ b/server/api/registry/analysis/[...pkg].get.ts @@ -3,14 +3,11 @@ import { PackageRouteParamsSchema } from '#shared/schemas/package' import type { PackageAnalysis, ExtendedPackageJson, - TypesPackageInfo, CreatePackageInfo, } from '#shared/utils/package-analysis' import { analyzePackage, - getTypesPackageName, getCreatePackageName, - hasBuiltInTypes, fetchPackageWithTypesAndFiles, } from '#shared/utils/package-analysis' import { @@ -24,7 +21,6 @@ import { } from '#shared/utils/constants' import { parseRepoUrl } from '#shared/utils/git-providers' import { encodePackageName } from '#shared/utils/npm' -import { flattenFileTree } from '#server/utils/import-resolver' import { getPackageFileTree } from '#server/utils/file-tree' import { getLatestVersionBatch } from 'fast-npm-meta' diff --git a/shared/utils/package-analysis.ts b/shared/utils/package-analysis.ts index eedc781a62..0a7587c9f4 100644 --- a/shared/utils/package-analysis.ts +++ b/shared/utils/package-analysis.ts @@ -3,6 +3,7 @@ */ import { getLatestVersion } from 'fast-npm-meta' +import { flattenFileTree } from '#server/utils/import-resolver' export type ModuleFormat = 'esm' | 'cjs' | 'dual' | 'unknown' From 9c87ae29c6294423ae6d25ab8c924c21e636bfae Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:52:05 +0000 Subject: [PATCH 12/37] Update [...pkg].get.ts --- server/api/registry/analysis/[...pkg].get.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/api/registry/analysis/[...pkg].get.ts b/server/api/registry/analysis/[...pkg].get.ts index 5ed3520f01..95ea7041e8 100644 --- a/server/api/registry/analysis/[...pkg].get.ts +++ b/server/api/registry/analysis/[...pkg].get.ts @@ -21,7 +21,6 @@ import { } from '#shared/utils/constants' import { parseRepoUrl } from '#shared/utils/git-providers' import { encodePackageName } from '#shared/utils/npm' -import { getPackageFileTree } from '#server/utils/file-tree' import { getLatestVersionBatch } from 'fast-npm-meta' export default defineCachedEventHandler( From 8626a610313ae603527983aee1fca034e115565e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 16:57:28 +0000 Subject: [PATCH 13/37] wip --- server/api/registry/analysis/[...pkg].get.ts | 2 +- .../api/registry/badge/[type]/[...pkg].get.ts | 2 +- server/utils/file-tree.ts | 43 +++++++++++++++++++ shared/utils/package-analysis.ts | 41 ------------------ 4 files changed, 45 insertions(+), 43 deletions(-) diff --git a/server/api/registry/analysis/[...pkg].get.ts b/server/api/registry/analysis/[...pkg].get.ts index 95ea7041e8..9093f6de13 100644 --- a/server/api/registry/analysis/[...pkg].get.ts +++ b/server/api/registry/analysis/[...pkg].get.ts @@ -8,7 +8,6 @@ import type { import { analyzePackage, getCreatePackageName, - fetchPackageWithTypesAndFiles, } from '#shared/utils/package-analysis' import { getDevDependencySuggestion, @@ -21,6 +20,7 @@ import { } from '#shared/utils/constants' import { parseRepoUrl } from '#shared/utils/git-providers' import { encodePackageName } from '#shared/utils/npm' +import { fetchPackageWithTypesAndFiles } from '#server/utils/file-tree' import { getLatestVersionBatch } from 'fast-npm-meta' export default defineCachedEventHandler( diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 65fe134f37..1397f58e23 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -6,7 +6,7 @@ import { PackageRouteParamsSchema } from '#shared/schemas/package' import { CACHE_MAX_AGE_ONE_HOUR, ERROR_NPM_FETCH_FAILED } from '#shared/utils/constants' import { fetchNpmPackage } from '#server/utils/npm' import { assertValidPackageName } from '#shared/utils/npm' -import { detectTypesStatus, fetchPackageWithTypesAndFiles } from '#shared/utils/package-analysis' +import { fetchPackageWithTypesAndFiles } from '#server/utils/file-tree' import { handleApiError } from '#server/utils/error-handler' const NPM_DOWNLOADS_API = 'https://api.npmjs.org/downloads/point' diff --git a/server/utils/file-tree.ts b/server/utils/file-tree.ts index 76bbf259ea..8f04ad5bae 100644 --- a/server/utils/file-tree.ts +++ b/server/utils/file-tree.ts @@ -1,3 +1,5 @@ +import { flattenFileTree } from '#server/utils/import-resolver' + /** * Fetch the file tree from jsDelivr API. * Returns a nested tree structure of all files in the package. @@ -83,3 +85,44 @@ export async function getPackageFileTree( tree, } } + +export async function fetchPackageWithTypesAndFiles( + packageName: string, + version?: string, +): Promise<{ + pkg: AnalysisPackageJson + typesPackage?: TypesPackageInfo + files?: Set +}> { + // Fetch main package data + const encodedName = encodePackageName(packageName) + const versionSuffix = version ? `/${version}` : '/latest' + + const pkg = await $fetch(`${NPM_REGISTRY}/${encodedName}${versionSuffix}`) + + let typesPackage: TypesPackageInfo | undefined + let files: Set | undefined + + // Only attempt to fetch @types + file tree when the package doesn't ship its own types + if (!hasBuiltInTypes(pkg)) { + const typesPkgName = getTypesPackageName(packageName) + const resolvedVersion = pkg.version ?? version ?? 'latest' + + // Fetch both in parallel — they're independent + const [typesResult, fileTreeResult] = await Promise.allSettled([ + fetchTypesPackageInfo(typesPkgName), + getPackageFileTree(packageName, resolvedVersion), + ]) + + if (typesResult.status === 'fulfilled') { + typesPackage = typesResult.value + } + + if (fileTreeResult.status === 'fulfilled') { + files = flattenFileTree(fileTreeResult.value.tree) + } + } + + return { pkg, typesPackage, files } +} + diff --git a/shared/utils/package-analysis.ts b/shared/utils/package-analysis.ts index 0a7587c9f4..89522e5094 100644 --- a/shared/utils/package-analysis.ts +++ b/shared/utils/package-analysis.ts @@ -3,7 +3,6 @@ */ import { getLatestVersion } from 'fast-npm-meta' -import { flattenFileTree } from '#server/utils/import-resolver' export type ModuleFormat = 'esm' | 'cjs' | 'dual' | 'unknown' @@ -128,46 +127,6 @@ async function fetchTypesPackageInfo(packageName: string): Promise -}> { - // Fetch main package data - const encodedName = encodePackageName(packageName) - const versionSuffix = version ? `/${version}` : '/latest' - - const pkg = await $fetch(`${NPM_REGISTRY}/${encodedName}${versionSuffix}`) - - let typesPackage: TypesPackageInfo | undefined - let files: Set | undefined - - // Only attempt to fetch @types + file tree when the package doesn't ship its own types - if (!hasBuiltInTypes(pkg)) { - const typesPkgName = getTypesPackageName(packageName) - const resolvedVersion = pkg.version ?? version ?? 'latest' - - // Fetch both in parallel — they're independent - const [typesResult, fileTreeResult] = await Promise.allSettled([ - fetchTypesPackageInfo(typesPkgName), - getPackageFileTree(packageName, resolvedVersion), - ]) - - if (typesResult.status === 'fulfilled') { - typesPackage = typesResult.value - } - - if (fileTreeResult.status === 'fulfilled') { - files = flattenFileTree(fileTreeResult.value.tree) - } - } - - return { pkg, typesPackage, files } -} - /** * Recursively analyze exports field for module format indicators */ From c6fc74e7b278830acea29e4080233dc3063e6f9b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 17:02:13 +0000 Subject: [PATCH 14/37] wip --- server/utils/file-tree.ts | 20 ++++++++++++++++++++ shared/utils/package-analysis.ts | 19 ------------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/server/utils/file-tree.ts b/server/utils/file-tree.ts index 8f04ad5bae..1de8f52989 100644 --- a/server/utils/file-tree.ts +++ b/server/utils/file-tree.ts @@ -1,4 +1,5 @@ import { flattenFileTree } from '#server/utils/import-resolver' +import type { ExtendedPackageJson, TypesPackageInfo } from '#shared/utils/package-analysis' /** * Fetch the file tree from jsDelivr API. @@ -86,6 +87,25 @@ export async function getPackageFileTree( } } +/** + * Fetch @types package info including deprecation status using fast-npm-meta. + * Returns undefined if the package doesn't exist. + */ +async function fetchTypesPackageInfo(packageName: string): Promise { + const result = await getLatestVersion(packageName, { metadata: true, throw: false }) + if ('error' in result) { + return undefined + } + return { + packageName, + deprecated: result.deprecated, + } +} + +interface AnalysisPackageJson extends ExtendedPackageJson { + readme?: string +} + export async function fetchPackageWithTypesAndFiles( packageName: string, version?: string, diff --git a/shared/utils/package-analysis.ts b/shared/utils/package-analysis.ts index 89522e5094..6d77615616 100644 --- a/shared/utils/package-analysis.ts +++ b/shared/utils/package-analysis.ts @@ -108,25 +108,6 @@ interface ExportsAnalysis { hasTypes: boolean } -interface AnalysisPackageJson extends ExtendedPackageJson { - readme?: string -} - -/** - * Fetch @types package info including deprecation status using fast-npm-meta. - * Returns undefined if the package doesn't exist. - */ -async function fetchTypesPackageInfo(packageName: string): Promise { - const result = await getLatestVersion(packageName, { metadata: true, throw: false }) - if ('error' in result) { - return undefined - } - return { - packageName, - deprecated: result.deprecated, - } -} - /** * Recursively analyze exports field for module format indicators */ From 4550a9e6135dbed9d66db64ad3eb875e16741180 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 17:05:09 +0000 Subject: [PATCH 15/37] fix --- server/utils/file-tree.ts | 1 + shared/utils/package-analysis.ts | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/server/utils/file-tree.ts b/server/utils/file-tree.ts index 1de8f52989..cdd6a520cc 100644 --- a/server/utils/file-tree.ts +++ b/server/utils/file-tree.ts @@ -1,3 +1,4 @@ +import { getLatestVersion } from 'fast-npm-meta' import { flattenFileTree } from '#server/utils/import-resolver' import type { ExtendedPackageJson, TypesPackageInfo } from '#shared/utils/package-analysis' diff --git a/shared/utils/package-analysis.ts b/shared/utils/package-analysis.ts index 6d77615616..57ed391d82 100644 --- a/shared/utils/package-analysis.ts +++ b/shared/utils/package-analysis.ts @@ -2,8 +2,6 @@ * Package analysis utilities for detecting module format and TypeScript support */ -import { getLatestVersion } from 'fast-npm-meta' - export type ModuleFormat = 'esm' | 'cjs' | 'dual' | 'unknown' export type TypesStatus = From 578142663eea1abab33114c6b11714ec60c750fa Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:06:40 +0000 Subject: [PATCH 16/37] [autofix.ci] apply automated fixes --- server/api/registry/analysis/[...pkg].get.ts | 5 +---- server/utils/file-tree.ts | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/server/api/registry/analysis/[...pkg].get.ts b/server/api/registry/analysis/[...pkg].get.ts index 9093f6de13..3c23102dfd 100644 --- a/server/api/registry/analysis/[...pkg].get.ts +++ b/server/api/registry/analysis/[...pkg].get.ts @@ -5,10 +5,7 @@ import type { ExtendedPackageJson, CreatePackageInfo, } from '#shared/utils/package-analysis' -import { - analyzePackage, - getCreatePackageName, -} from '#shared/utils/package-analysis' +import { analyzePackage, getCreatePackageName } from '#shared/utils/package-analysis' import { getDevDependencySuggestion, type DevDependencySuggestion, diff --git a/server/utils/file-tree.ts b/server/utils/file-tree.ts index cdd6a520cc..50e21d439c 100644 --- a/server/utils/file-tree.ts +++ b/server/utils/file-tree.ts @@ -146,4 +146,3 @@ export async function fetchPackageWithTypesAndFiles( return { pkg, typesPackage, files } } - From 33a2c90a5acb656e6d37936f278d87a20900d2a0 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 17:58:26 +0000 Subject: [PATCH 17/37] Update [...pkg].get.ts --- server/api/registry/badge/[type]/[...pkg].get.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 1397f58e23..50e57ef9f8 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -374,6 +374,10 @@ const badgeStrategies = { }, 'types': async (pkgData: globalThis.Packument, requestedVersion?: string) => { + if (hasBuiltInTypes(pkgData)) { + return { label: 'types', value: 'included', color: COLORS.blue } + } + const { pkg, typesPackage, files } = await fetchPackageWithTypesAndFiles( pkgData.name, requestedVersion, From b419292df66d3c1c0dedde3c146ee327c8b26849 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 18:13:16 +0000 Subject: [PATCH 18/37] Update [...pkg].get.ts --- server/api/registry/badge/[type]/[...pkg].get.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 50e57ef9f8..1de5d4fa83 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -374,8 +374,10 @@ const badgeStrategies = { }, 'types': async (pkgData: globalThis.Packument, requestedVersion?: string) => { - if (hasBuiltInTypes(pkgData)) { - return { label: 'types', value: 'included', color: COLORS.blue } + const versionData = requestedVersion ? pkgData.versions?.[requestedVersion] : undefined + + if (versionData && hasBuiltInTypes(versionData)) { + return { label: 'types', value: 'included', color: COLORS.blue } } const { pkg, typesPackage, files } = await fetchPackageWithTypesAndFiles( From 79ded64a7e831e7b1723b229c38c9528d4abcbb3 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 18:00:40 +0000 Subject: [PATCH 19/37] [autofix.ci] apply automated fixes --- server/api/registry/badge/[type]/[...pkg].get.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 1de5d4fa83..62eb5cbfe9 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -379,7 +379,7 @@ const badgeStrategies = { if (versionData && hasBuiltInTypes(versionData)) { return { label: 'types', value: 'included', color: COLORS.blue } } - + const { pkg, typesPackage, files } = await fetchPackageWithTypesAndFiles( pkgData.name, requestedVersion, From 2377c3120d061bb1d460ec5e9f406bba181b3e29 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 18:15:42 +0000 Subject: [PATCH 20/37] [autofix.ci] apply automated fixes (attempt 2/3) --- server/api/registry/badge/[type]/[...pkg].get.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 62eb5cbfe9..66a9a02f1b 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -375,9 +375,9 @@ const badgeStrategies = { 'types': async (pkgData: globalThis.Packument, requestedVersion?: string) => { const versionData = requestedVersion ? pkgData.versions?.[requestedVersion] : undefined - + if (versionData && hasBuiltInTypes(versionData)) { - return { label: 'types', value: 'included', color: COLORS.blue } + return { label: 'types', value: 'included', color: COLORS.blue } } const { pkg, typesPackage, files } = await fetchPackageWithTypesAndFiles( From 751f137ef4df490bdae2f0cd285899ddcea1b10e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 18:18:34 +0000 Subject: [PATCH 21/37] Update [...pkg].get.ts --- server/api/registry/badge/[type]/[...pkg].get.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 66a9a02f1b..660d84c3b3 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -374,7 +374,8 @@ const badgeStrategies = { }, 'types': async (pkgData: globalThis.Packument, requestedVersion?: string) => { - const versionData = requestedVersion ? pkgData.versions?.[requestedVersion] : undefined + const targetVersion = requestedVersion ?? getLatestVersion(pkgData) + const versionData = targetVersion ? pkgData.versions?.[targetVersion] : undefined if (versionData && hasBuiltInTypes(versionData)) { return { label: 'types', value: 'included', color: COLORS.blue } From 04b30cc79d096fdd1e72fce8f3e755f12e5e20ba Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 18:32:15 +0000 Subject: [PATCH 22/37] wip --- server/api/registry/badge/[type]/[...pkg].get.ts | 2 +- shared/utils/package-analysis.ts | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 660d84c3b3..e94a61c37c 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -383,7 +383,7 @@ const badgeStrategies = { const { pkg, typesPackage, files } = await fetchPackageWithTypesAndFiles( pkgData.name, - requestedVersion, + targetVersion, ) const typesStatus = detectTypesStatus(pkg, typesPackage, files) diff --git a/shared/utils/package-analysis.ts b/shared/utils/package-analysis.ts index 57ed391d82..00009c5615 100644 --- a/shared/utils/package-analysis.ts +++ b/shared/utils/package-analysis.ts @@ -319,19 +319,10 @@ export function detectTypesStatus( typesPackageInfo?: TypesPackageInfo, files?: Set, ): TypesStatus { - // Check for built-in types - if (pkg.types || pkg.typings) { + if (hasBuiltInTypes(pkg)) { return { kind: 'included' } } - // Check exports field for types - if (pkg.exports) { - const exportInfo = analyzeExports(pkg.exports) - if (exportInfo.hasTypes) { - return { kind: 'included' } - } - } - // Check for implicit types by deriving expected declaration file paths from // entry points (e.g. .d.mts for .mjs) and checking if they exist in the package if (files && hasImplicitTypesForEntryPoints(pkg, files)) { From dc849ad94aa21b61f9d889c512a0544f4ca1287f Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 18:55:44 +0000 Subject: [PATCH 23/37] Update badge.spec.ts --- test/e2e/badge.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/badge.spec.ts b/test/e2e/badge.spec.ts index 4cf8a33d37..e20f6d7702 100644 --- a/test/e2e/badge.spec.ts +++ b/test/e2e/badge.spec.ts @@ -56,7 +56,7 @@ test.describe('badge API', () => { }) test('explicit version badge renders successfully', async ({ page, baseURL }) => { - const url = toLocalUrl(baseURL, `/api/registry/badge/${type}/nuxt/v/3.12.0`) + const url = toLocalUrl(baseURL, `/api/registry/badge/${type}/nuxt/v/3.21.0`) const { response, body } = await fetchBadge(page, url) expect(response.status()).toBe(200) From d6fc700bfe627d74d1558cc3f369442138faa183 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 19:00:17 +0000 Subject: [PATCH 24/37] Update badge.spec.ts --- test/e2e/badge.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/badge.spec.ts b/test/e2e/badge.spec.ts index e20f6d7702..ef08852072 100644 --- a/test/e2e/badge.spec.ts +++ b/test/e2e/badge.spec.ts @@ -61,7 +61,7 @@ test.describe('badge API', () => { expect(response.status()).toBe(200) if (type === 'version') { - expect(body).toContain('v3.12.0') + expect(body).toContain('v3.21.0') } }) From ff2c385f27096e0836bc00a8344c5719e559c5a1 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 20:49:47 +0000 Subject: [PATCH 25/37] test: add types badge e2e test --- test/e2e/badge.spec.ts | 7 + test/fixtures/mock-routes.cjs | 11 + .../packuments/@types/is-odd.json | 341 ++++++++++++++++++ 3 files changed, 359 insertions(+) create mode 100644 test/fixtures/npm-registry/packuments/@types/is-odd.json diff --git a/test/e2e/badge.spec.ts b/test/e2e/badge.spec.ts index ef08852072..4c6555e606 100644 --- a/test/e2e/badge.spec.ts +++ b/test/e2e/badge.spec.ts @@ -100,6 +100,13 @@ test.describe('badge API', () => { expect(body).toContain('active') }) + + test('types badge shows @types badge', async ({ page, baseURL }) => { + const url = toLocalUrl(baseURL, '/api/registry/badge/types/is-odd') + const { body } = await fetchBadge(page, url) + + expect(body).toContain('@types') + }) }) test('custom labelColor parameter is applied to SVG', async ({ page, baseURL }) => { diff --git a/test/fixtures/mock-routes.cjs b/test/fixtures/mock-routes.cjs index bd5527246b..b9aba20fc0 100644 --- a/test/fixtures/mock-routes.cjs +++ b/test/fixtures/mock-routes.cjs @@ -422,6 +422,17 @@ function matchJsdelivrDataApi(urlString) { const packageMatch = pathname.match(/^\/v1\/packages\/npm\/(.+)$/) if (packageMatch && packageMatch[1]) { const parsed = parseScopedPackage(packageMatch[1]) + + const fixture = readFixture(`jsdelivr/${parsed.name}.json`) + if (fixture) { + return json({ + type: 'npm', + name: parsed.name, + version: parsed.version || 'latest', + ...fixture, + }) + } + return json({ type: 'npm', name: parsed.name, diff --git a/test/fixtures/npm-registry/packuments/@types/is-odd.json b/test/fixtures/npm-registry/packuments/@types/is-odd.json new file mode 100644 index 0000000000..327bb2817f --- /dev/null +++ b/test/fixtures/npm-registry/packuments/@types/is-odd.json @@ -0,0 +1,341 @@ +{ + "_id": "@types/is-odd", + "_rev": "357-6aea66bbc40695119f00465c0fa53de3", + "name": "@types/is-odd", + "dist-tags": { + "ts2.0": "3.0.0", + "ts2.1": "3.0.0", + "ts2.2": "3.0.0", + "ts2.3": "3.0.0", + "ts2.4": "3.0.0", + "ts2.5": "3.0.0", + "ts2.6": "3.0.0", + "ts2.7": "3.0.0", + "ts2.8": "3.0.0", + "ts2.9": "3.0.0", + "ts3.0": "3.0.0", + "ts3.1": "3.0.0", + "ts3.2": "3.0.0", + "ts3.3": "3.0.0", + "ts3.4": "3.0.0", + "ts3.5": "3.0.0", + "ts3.6": "3.0.0", + "ts3.7": "3.0.0", + "ts3.8": "3.0.1", + "ts3.9": "3.0.1", + "ts4.0": "3.0.1", + "ts4.1": "3.0.1", + "ts4.2": "3.0.1", + "ts4.3": "3.0.1", + "ts4.4": "3.0.1", + "ts5.8": "3.0.4", + "ts5.7": "3.0.4", + "latest": "3.0.4", + "ts4.5": "3.0.4", + "ts4.6": "3.0.4", + "ts4.7": "3.0.4", + "ts4.8": "3.0.4", + "ts4.9": "3.0.4", + "ts5.0": "3.0.4", + "ts5.1": "3.0.4", + "ts5.2": "3.0.4", + "ts5.3": "3.0.4", + "ts5.4": "3.0.4", + "ts5.5": "3.0.4", + "ts5.9": "3.0.4", + "ts5.6": "3.0.4", + "ts6.0": "3.0.4" + }, + "versions": { + "3.0.0": { + "name": "@types/is-odd", + "version": "3.0.0", + "license": "MIT", + "_id": "@types/is-odd@3.0.0", + "maintainers": [ + { + "name": "types", + "email": "ts-npm-types@microsoft.com" + } + ], + "contributors": [ + { + "url": "https://github.com/adamzerella", + "name": " Adam Zerella", + "githubUsername": "adamzerella" + } + ], + "dist": { + "shasum": "861b26fa31e656354bb05d63254091f734c461b5", + "tarball": "https://registry.npmjs.org/@types/is-odd/-/is-odd-3.0.0.tgz", + "fileCount": 4, + "integrity": "sha512-XfoBBgqc9DYRWo0V6clOC9cDy9KYELqgWwyulwCkv8/6Hlnd25XbDXGGnWYqOr3GUYIwqp4/gqm7/RtCZvqOJA==", + "signatures": [ + { + "sig": "MEUCIQDCb3RyeOCBQWvvgk/slsNiLZrw352GCFOXn7BvVT+33gIgfYjvWpa8EI/H67e8ndZkZLz93oN/dRVTLqnnm1d/1bM=", + "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA" + } + ], + "unpackedSize": 2712, + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJccLErCRA9TVsSAnZWagAAQhcP/2Ab+nx4IOo4BGdIySnD\nBONPypFkgSzUa29B81x0SYuFLk9M6lgVhcHLM6s62/HlTDMgT6MHFJdaIHcg\n9xvm/ROaBeKxKcm/iP2yS8OzMZ4GTzNc8t/r1w1GPrUGw5d/3GuEPktSlA3o\nUoVg9tc+QIMog46UGmcXARX6Hv81UGFqHUiV0JgRWQNLvTlUyXFpcpE1esyB\njumZDFuH+E9Bcj/k+DYy+G12lKhMHmTwLOMBN5YI9Cl3dNgnujcFbpKXCf3s\nYBKxjrWblz1l3eT1hk24KRhpOnUm262Y/v1axSeA2l3xxO/feg7MWf9V0fHZ\ndVDa4QBK+w2yt+0rNLJ0I0EqOxk7lMnxLRbbbBRSf4iwiwrzOVAxqwCCG2MW\nRj6ZAXf0/girV4/tXxFZX6maZVWXRiEEKRVWOFuHPAozp6Sdl8dpB8IXjKn6\n4vbGfDqqN2VMLPAfTtevEaZJDM+1/cnpcJSYRFbuzxxG1a9XU75hWNMzOvDZ\n/YhAmtTxNrNNux9DDVtifg0VoIBbIMUZ90/alhaczf0P2yvA5pJ2ktPHumSh\ne0RaiSu70C+ye2MHraEV9/uBoq+U1/ct6XPt1iYE1vQqtc+jf3nBazSDumpA\n2UXV4kmbHCUgg1a/a7xQH7lRgip+Sx776sv3ljRWmb2QUdlzI1gbipsUIZAA\n08K4\r\n=/LEL\r\n-----END PGP SIGNATURE-----\r\n" + }, + "main": "", + "types": "index", + "scripts": {}, + "_npmUser": { + "name": "types", + "email": "ts-npm-types@microsoft.com" + }, + "repository": { + "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "type": "git", + "directory": "types/is-odd" + }, + "description": "TypeScript definitions for is-odd", + "directories": {}, + "dependencies": {}, + "_hasShrinkwrap": false, + "typeScriptVersion": "2.0", + "_npmOperationalInternal": { + "tmp": "tmp/is-odd_3.0.0_1550889259065_0.09125334321631584", + "host": "s3://npm-registry-packages" + }, + "typesPublisherContentHash": "1dc04627cea88eece89c95a0aed7905067a849634ee4f4bc84b23fa135d0dd02" + }, + "3.0.1": { + "name": "@types/is-odd", + "version": "3.0.1", + "license": "MIT", + "_id": "@types/is-odd@3.0.1", + "maintainers": [ + { + "name": "types", + "email": "ts-npm-types@microsoft.com" + } + ], + "contributors": [ + { + "url": "https://github.com/DefinitelyTyped", + "name": "DefinitelyTyped", + "githubUsername": "DefinitelyTyped" + } + ], + "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/is-odd", + "dist": { + "shasum": "b43a16362855c86db3c4df59e36b6eb3e44f555b", + "tarball": "https://registry.npmjs.org/@types/is-odd/-/is-odd-3.0.1.tgz", + "fileCount": 4, + "integrity": "sha512-mY+bL1FNKoF6xEN87ayz2Xk1O5L5L4v0KKheja7bWcqTD3je1w0nYzDVJ3W/bTtzkHJuqEb0UAFANtF5cI2wBg==", + "signatures": [ + { + "sig": "MEUCIQCIF54WCx1KAczN/AuO1k/pC1b6pj4o1a6FVbyunJ51BgIgcqi0fG9vJU1hms3h9IUwrrMJVbBMNlH+G4vPbyDsUf4=", + "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA" + } + ], + "unpackedSize": 3272, + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhxQ2wCRA9TVsSAnZWagAAPhsP/3HOZ/uL7BMK+rZAXIdE\n0wy77RIsTo/+ZCWlJ3MNPWocIBi+eVfFmRkEIxnD3u9+EMOjZl10Q3rKlX/D\n6Zu4PDIBIUGU0p6WqdOqAMxHIGbehid+DG8RbgdcZw72QDCztPzPEwYDd3lE\nuaANy3qdPDdN5knlN0QvrJuiU9I++bCgEBWIXuiGTddeQOQXbitEBP7D7Q0E\n+PRYWlGXUjb3YiJgrZJfemEUfvntoqgOa1mPsM9crfo4+tzb5q8H4AEUhpd3\nZyYNxRZKsEN1MpA/i8KjwdO4P2zFofFgvmw/umU2+7/ACD86lYwpII/N26VB\nBlWaZXOPKxsX1uHZSmSI2Z1d6uE6PVSNP9R2fq7KuMWOkhnTb/U39uYBlJHE\nW0SCmMKIz/+TQP83sIH7LKBZmp4hft6UxIoL2LkOTStp7ibAw+rCbPA18NPl\nfMdcCQuHoY2bNHYa87/0ecOFke8JKLkI3DY1AwK+6h9WIX9cOV4MJXl/hXhG\nQiL7NIZ25XfWbqBTfrCtHInxd0e8X1hvR7OytDsyQ6ruAmZqWqNWIjhcLSYL\nGS9wahiPysJcRBxvKiYAfhd8wwUmi/ObbczqygNJqNMcoUZLzNd+AHZSnmlv\nYgU6p6lDs4aOeAcG7moR/+0Pe+OpZwnE0ngkQGLisG/YVayO+cBHLorBkQvc\nBTFo\r\n=oVZZ\r\n-----END PGP SIGNATURE-----\r\n" + }, + "main": "", + "types": "index.d.ts", + "scripts": {}, + "_npmUser": { + "name": "types", + "email": "ts-npm-types@microsoft.com" + }, + "repository": { + "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "type": "git", + "directory": "types/is-odd" + }, + "description": "TypeScript definitions for is-odd", + "directories": {}, + "dependencies": {}, + "_hasShrinkwrap": false, + "typeScriptVersion": "3.8", + "_npmOperationalInternal": { + "tmp": "tmp/is-odd_3.0.1_1640304047958_0.02408279906546351", + "host": "s3://npm-registry-packages" + }, + "typesPublisherContentHash": "cfeed0da2d9c42717cef03273f341ce6f4edd16f2adf2bacbf13bc82214bfca7" + }, + "3.0.2": { + "name": "@types/is-odd", + "version": "3.0.2", + "license": "MIT", + "_id": "@types/is-odd@3.0.2", + "maintainers": [ + { + "name": "types", + "email": "ts-npm-types@microsoft.com" + } + ], + "contributors": [ + { + "url": "https://github.com/DefinitelyTyped", + "name": "DefinitelyTyped", + "githubUsername": "DefinitelyTyped" + } + ], + "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/is-odd", + "dist": { + "shasum": "064ccdfef0cba1f8597e2ec0eba0802fc6aba743", + "tarball": "https://registry.npmjs.org/@types/is-odd/-/is-odd-3.0.2.tgz", + "fileCount": 5, + "integrity": "sha512-a/mE50FokBkPY86oocj/oorgDaYjT+l2EGSVLwbAUFwjOpFFDlbqHW5I9FS0cISLxmuDRr1J26znAy88UkHUbg==", + "signatures": [ + { + "sig": "MEUCIC7Z/Qq4ZJJjg5ibpVZWQu1VD8LS/arVBl5awUSBOtRqAiEA20t3jhV+JVYMlUF3fwMl8KneYzDO9+HRYTJ9mQ7WXPY=", + "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA" + } + ], + "unpackedSize": 3276 + }, + "main": "", + "types": "index.d.ts", + "scripts": {}, + "_npmUser": { + "name": "types", + "email": "ts-npm-types@microsoft.com" + }, + "repository": { + "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "type": "git", + "directory": "types/is-odd" + }, + "description": "TypeScript definitions for is-odd", + "directories": {}, + "dependencies": {}, + "_hasShrinkwrap": false, + "typeScriptVersion": "4.5", + "_npmOperationalInternal": { + "tmp": "tmp/is-odd_3.0.2_1695740874873_0.09491697275033473", + "host": "s3://npm-registry-packages" + }, + "typesPublisherContentHash": "f9b12a308df62402e8e0f8a8b33f699ed9f75f869afce468cfeafb9f8714df4e" + }, + "3.0.3": { + "name": "@types/is-odd", + "version": "3.0.3", + "license": "MIT", + "_id": "@types/is-odd@3.0.3", + "maintainers": [ + { + "name": "types", + "email": "ts-npm-types@microsoft.com" + } + ], + "contributors": [], + "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/is-odd", + "dist": { + "shasum": "a60696d7d28b6795522cc757f4bbf0d0250bfa59", + "tarball": "https://registry.npmjs.org/@types/is-odd/-/is-odd-3.0.3.tgz", + "fileCount": 5, + "integrity": "sha512-YdKKQ4CTGEfvdO+MWIPj/DLA3RZMJ9yOJOyLmq0nmN2FrKg6WQ5zynhlmjP7vnfYBTHNp/E9BJmBDsvw0/on/A==", + "signatures": [ + { + "sig": "MEQCIGg7zdX3mlxczMdanHMrScYDLSnyDf6TL9b0DLRRkoWVAiBZYASSD5iCM3xRTS767+Gw41jQnDkIeK0rCHuy1SjgIw==", + "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA" + } + ], + "unpackedSize": 2576 + }, + "main": "", + "types": "index.d.ts", + "scripts": {}, + "_npmUser": { + "name": "types", + "email": "ts-npm-types@microsoft.com" + }, + "repository": { + "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "type": "git", + "directory": "types/is-odd" + }, + "description": "TypeScript definitions for is-odd", + "directories": {}, + "dependencies": {}, + "_hasShrinkwrap": false, + "typeScriptVersion": "4.5", + "_npmOperationalInternal": { + "tmp": "tmp/is-odd_3.0.3_1697606685662_0.7307845211597903", + "host": "s3://npm-registry-packages" + }, + "typesPublisherContentHash": "f0888f66f4772459e29e716f6016cdaacfc7731573b71c46d23ded2554bd7a73" + }, + "3.0.4": { + "name": "@types/is-odd", + "version": "3.0.4", + "license": "MIT", + "_id": "@types/is-odd@3.0.4", + "maintainers": [ + { + "name": "types", + "email": "ts-npm-types@microsoft.com" + } + ], + "contributors": [], + "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/is-odd", + "dist": { + "shasum": "281d2e0ff78f04b319e40f83b2c4cfae5377622a", + "tarball": "https://registry.npmjs.org/@types/is-odd/-/is-odd-3.0.4.tgz", + "fileCount": 5, + "integrity": "sha512-BdjXVZXuHVqdXNi9gEbwf/W/k2lIhXzqnmygz4BFRTdpgFpJsn1v7AfB3DaLHj04O9hKiqQvnxu5AeogyptDjQ==", + "signatures": [ + { + "sig": "MEUCIHLi2pVt8NsZM0434wTXqtu7F2kqqL9X7uySIbQ/XW7qAiEAzjn/2YlFKI02nQ2vaBNq0OtctmuDAdly4lY0V4eguIc=", + "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA" + } + ], + "unpackedSize": 2576 + }, + "main": "", + "types": "index.d.ts", + "scripts": {}, + "_npmUser": { + "name": "types", + "email": "ts-npm-types@microsoft.com" + }, + "repository": { + "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "type": "git", + "directory": "types/is-odd" + }, + "description": "TypeScript definitions for is-odd", + "directories": {}, + "dependencies": {}, + "_hasShrinkwrap": false, + "typeScriptVersion": "4.5", + "_npmOperationalInternal": { + "tmp": "tmp/is-odd_3.0.4_1699344348594_0.45442272513797977", + "host": "s3://npm-registry-packages" + }, + "typesPublisherContentHash": "4f85cab04d848065af4c299b2e5770379553a5d4500593c40f39e06931124f91" + } + }, + "time": { + "created": "2019-02-23T02:34:18.884Z", + "modified": "2025-08-03T07:01:33.304Z", + "3.0.0": "2019-02-23T02:34:19.212Z", + "3.0.1": "2021-12-24T00:00:48.118Z", + "3.0.2": "2023-09-26T15:07:55.020Z", + "3.0.3": "2023-10-18T05:24:45.883Z", + "3.0.4": "2023-11-07T08:05:48.848Z" + }, + "license": "MIT", + "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/is-odd", + "repository": { + "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git", + "type": "git", + "directory": "types/is-odd" + }, + "description": "TypeScript definitions for is-odd", + "contributors": [], + "maintainers": [ + { + "name": "types", + "email": "ts-npm-types@microsoft.com" + } + ], + "readme": "[object Object]", + "readmeFilename": "" +} From 85a9b256a4461550bd923afc42f28f84b62539ed Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 21:17:47 +0000 Subject: [PATCH 26/37] test: add another types badge e2e test --- test/e2e/badge.spec.ts | 12 +++ .../jsdelivr/nano-stringify-object.json | 7 ++ .../packuments/nano-stringify-object.json | 91 +++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 test/fixtures/jsdelivr/nano-stringify-object.json create mode 100644 test/fixtures/npm-registry/packuments/nano-stringify-object.json diff --git a/test/e2e/badge.spec.ts b/test/e2e/badge.spec.ts index 4c6555e606..651de114bf 100644 --- a/test/e2e/badge.spec.ts +++ b/test/e2e/badge.spec.ts @@ -106,6 +106,18 @@ test.describe('badge API', () => { const { body } = await fetchBadge(page, url) expect(body).toContain('@types') + expect(body).not.toContain('missing') + }) + + test('types badge shows included badge when types not declared explictly', async ({ + page, + baseURL, + }) => { + const url = toLocalUrl(baseURL, '/api/registry/badge/types/nano-stringify-object') + const { body } = await fetchBadge(page, url) + + expect(body).toContain('included') + expect(body).not.toContain('missing') }) }) diff --git a/test/fixtures/jsdelivr/nano-stringify-object.json b/test/fixtures/jsdelivr/nano-stringify-object.json new file mode 100644 index 0000000000..6493db5f28 --- /dev/null +++ b/test/fixtures/jsdelivr/nano-stringify-object.json @@ -0,0 +1,7 @@ +{ + "files": [, + { "name": "dist/index.d.ts", "hash": "ghi", "size": 1200 }, + { "name": "dist/index.js", "hash": "def", "size": 5700 }, + { "name": "package.json", "hash": "abc", "size": 600 } + ] +} diff --git a/test/fixtures/npm-registry/packuments/nano-stringify-object.json b/test/fixtures/npm-registry/packuments/nano-stringify-object.json new file mode 100644 index 0000000000..490c496c0f --- /dev/null +++ b/test/fixtures/npm-registry/packuments/nano-stringify-object.json @@ -0,0 +1,91 @@ +{ + "_id": "nano-stringify-object", + "name": "nano-stringify-object", + "dist-tags": { + "latest": "0.0.0" + }, + "versions": { + "0.0.0": { + "name": "nano-stringify-object", + "version": "0.0.0", + "license": "MIT", + "author": "gameroman", + "repository": { + "type": "git", + "url": "git+https://github.com/gameroman-npm/nano-stringify-object.git" + }, + "type": "module", + "exports": { + ".": "./dist/index.mjs", + "./package.json": "./package.json" + }, + "scripts": { + "format": "oxfmt", + "lint": "oxlint", + "test": "bun test", + "build": "tsdown", + "prepublishOnly": "bun run build" + }, + "devDependencies": { + "oxfmt": "^0.38.0", + "oxlint": "^1.50.0", + "tsdown": "^0.21.2", + "typescript": "^5.9.3" + }, + "_id": "nano-stringify-object@0.0.0", + "_integrity": "sha512-s4XcjhMpc1u7HfHHimUAT0dtNNYFiOunlNPGUv5N9BoX4X1Lwklq4ckdFDPo+50/exciNsf0ggH4s8Sj6CufOA==", + "_nodeVersion": "24.3.0", + "_npmVersion": "10.8.3", + "shasum": "dbb692872dc94d6fd0adc93b66c8e09c67f78260", + "dist": { + "integrity": "sha512-s4XcjhMpc1u7HfHHimUAT0dtNNYFiOunlNPGUv5N9BoX4X1Lwklq4ckdFDPo+50/exciNsf0ggH4s8Sj6CufOA==", + "shasum": "dbb692872dc94d6fd0adc93b66c8e09c67f78260", + "tarball": "https://registry.npmjs.org/nano-stringify-object/-/nano-stringify-object-0.0.0.tgz", + "fileCount": 5, + "unpackedSize": 8783, + "signatures": [ + { + "keyid": "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U", + "sig": "MEQCIF396neosjaDQP6JgESc3D/CpgE5VZik5CqXY/F68gUnAiAmtw8VnyNnYMNisW4K0S75AJicujUpd0u8g4kQ35t0YA==" + } + ] + }, + "_npmUser": { + "name": "gameroman", + "email": "dev@rman.dev" + }, + "directories": {}, + "maintainers": [ + { + "name": "gameroman", + "email": "dev@rman.dev" + } + ], + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages-npm-production", + "tmp": "tmp/nano-stringify-object_0.0.0_1773271904176_0.4735360871124257" + }, + "_hasShrinkwrap": false + } + }, + "time": { + "created": "2026-03-11T23:31:44.175Z", + "0.0.0": "2026-03-11T23:31:44.334Z", + "modified": "2026-03-11T23:31:44.554Z" + }, + "maintainers": [ + { + "name": "gameroman", + "email": "dev@rman.dev" + } + ], + "repository": { + "type": "git", + "url": "git+https://github.com/gameroman-npm/nano-stringify-object.git" + }, + "author": "gameroman", + "license": "MIT", + "readme": "", + "readmeFilename": "", + "_rev": "1-d7e9fda2cb18e6ea7d359fdc679eea56" +} From e350ac77b8d8f25aa4d4fd8e21e7b0179813db36 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 21:19:15 +0000 Subject: [PATCH 27/37] [autofix.ci] apply automated fixes --- test/fixtures/jsdelivr/nano-stringify-object.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/fixtures/jsdelivr/nano-stringify-object.json b/test/fixtures/jsdelivr/nano-stringify-object.json index 6493db5f28..9cae55923f 100644 --- a/test/fixtures/jsdelivr/nano-stringify-object.json +++ b/test/fixtures/jsdelivr/nano-stringify-object.json @@ -1,5 +1,6 @@ { - "files": [, + "files": [ + , { "name": "dist/index.d.ts", "hash": "ghi", "size": 1200 }, { "name": "dist/index.js", "hash": "def", "size": 5700 }, { "name": "package.json", "hash": "abc", "size": 600 } From a9c3974d6e1dd088974ecd867f2ff1bb99a1fb44 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 21:32:34 +0000 Subject: [PATCH 28/37] Update nano-stringify-object.json --- test/fixtures/jsdelivr/nano-stringify-object.json | 1 - 1 file changed, 1 deletion(-) diff --git a/test/fixtures/jsdelivr/nano-stringify-object.json b/test/fixtures/jsdelivr/nano-stringify-object.json index 9cae55923f..091620a91f 100644 --- a/test/fixtures/jsdelivr/nano-stringify-object.json +++ b/test/fixtures/jsdelivr/nano-stringify-object.json @@ -1,6 +1,5 @@ { "files": [ - , { "name": "dist/index.d.ts", "hash": "ghi", "size": 1200 }, { "name": "dist/index.js", "hash": "def", "size": 5700 }, { "name": "package.json", "hash": "abc", "size": 600 } From ab75cdf5b0e5aca1a3415cea07c63170776c5946 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 21:41:00 +0000 Subject: [PATCH 29/37] update fixture --- .../packuments/nano-stringify-object.json | 91 ------------------- .../nano-stringify-object/latest.json | 64 +++++++++++++ 2 files changed, 64 insertions(+), 91 deletions(-) delete mode 100644 test/fixtures/npm-registry/packuments/nano-stringify-object.json create mode 100644 test/fixtures/npm-registry/packuments/nano-stringify-object/latest.json diff --git a/test/fixtures/npm-registry/packuments/nano-stringify-object.json b/test/fixtures/npm-registry/packuments/nano-stringify-object.json deleted file mode 100644 index 490c496c0f..0000000000 --- a/test/fixtures/npm-registry/packuments/nano-stringify-object.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "_id": "nano-stringify-object", - "name": "nano-stringify-object", - "dist-tags": { - "latest": "0.0.0" - }, - "versions": { - "0.0.0": { - "name": "nano-stringify-object", - "version": "0.0.0", - "license": "MIT", - "author": "gameroman", - "repository": { - "type": "git", - "url": "git+https://github.com/gameroman-npm/nano-stringify-object.git" - }, - "type": "module", - "exports": { - ".": "./dist/index.mjs", - "./package.json": "./package.json" - }, - "scripts": { - "format": "oxfmt", - "lint": "oxlint", - "test": "bun test", - "build": "tsdown", - "prepublishOnly": "bun run build" - }, - "devDependencies": { - "oxfmt": "^0.38.0", - "oxlint": "^1.50.0", - "tsdown": "^0.21.2", - "typescript": "^5.9.3" - }, - "_id": "nano-stringify-object@0.0.0", - "_integrity": "sha512-s4XcjhMpc1u7HfHHimUAT0dtNNYFiOunlNPGUv5N9BoX4X1Lwklq4ckdFDPo+50/exciNsf0ggH4s8Sj6CufOA==", - "_nodeVersion": "24.3.0", - "_npmVersion": "10.8.3", - "shasum": "dbb692872dc94d6fd0adc93b66c8e09c67f78260", - "dist": { - "integrity": "sha512-s4XcjhMpc1u7HfHHimUAT0dtNNYFiOunlNPGUv5N9BoX4X1Lwklq4ckdFDPo+50/exciNsf0ggH4s8Sj6CufOA==", - "shasum": "dbb692872dc94d6fd0adc93b66c8e09c67f78260", - "tarball": "https://registry.npmjs.org/nano-stringify-object/-/nano-stringify-object-0.0.0.tgz", - "fileCount": 5, - "unpackedSize": 8783, - "signatures": [ - { - "keyid": "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U", - "sig": "MEQCIF396neosjaDQP6JgESc3D/CpgE5VZik5CqXY/F68gUnAiAmtw8VnyNnYMNisW4K0S75AJicujUpd0u8g4kQ35t0YA==" - } - ] - }, - "_npmUser": { - "name": "gameroman", - "email": "dev@rman.dev" - }, - "directories": {}, - "maintainers": [ - { - "name": "gameroman", - "email": "dev@rman.dev" - } - ], - "_npmOperationalInternal": { - "host": "s3://npm-registry-packages-npm-production", - "tmp": "tmp/nano-stringify-object_0.0.0_1773271904176_0.4735360871124257" - }, - "_hasShrinkwrap": false - } - }, - "time": { - "created": "2026-03-11T23:31:44.175Z", - "0.0.0": "2026-03-11T23:31:44.334Z", - "modified": "2026-03-11T23:31:44.554Z" - }, - "maintainers": [ - { - "name": "gameroman", - "email": "dev@rman.dev" - } - ], - "repository": { - "type": "git", - "url": "git+https://github.com/gameroman-npm/nano-stringify-object.git" - }, - "author": "gameroman", - "license": "MIT", - "readme": "", - "readmeFilename": "", - "_rev": "1-d7e9fda2cb18e6ea7d359fdc679eea56" -} diff --git a/test/fixtures/npm-registry/packuments/nano-stringify-object/latest.json b/test/fixtures/npm-registry/packuments/nano-stringify-object/latest.json new file mode 100644 index 0000000000..114141744c --- /dev/null +++ b/test/fixtures/npm-registry/packuments/nano-stringify-object/latest.json @@ -0,0 +1,64 @@ +{ + "name": "nano-stringify-object", + "version": "0.0.0", + "license": "MIT", + "author": "gameroman", + "repository": { + "type": "git", + "url": "git+https://github.com/gameroman-npm/nano-stringify-object.git" + }, + "type": "module", + "exports": { + ".": "./dist/index.mjs", + "./package.json": "./package.json" + }, + "scripts": { + "format": "oxfmt", + "lint": "oxlint", + "test": "bun test", + "build": "tsdown", + "prepublishOnly": "bun run build" + }, + "devDependencies": { + "oxfmt": "^0.38.0", + "oxlint": "^1.50.0", + "tsdown": "^0.21.2", + "typescript": "^5.9.3" + }, + "_id": "nano-stringify-object@0.0.0", + "_integrity": "sha512-s4XcjhMpc1u7HfHHimUAT0dtNNYFiOunlNPGUv5N9BoX4X1Lwklq4ckdFDPo+50/exciNsf0ggH4s8Sj6CufOA==", + "_nodeVersion": "24.3.0", + "_npmVersion": "10.8.3", + "shasum": "dbb692872dc94d6fd0adc93b66c8e09c67f78260", + "dist": { + "integrity": "sha512-s4XcjhMpc1u7HfHHimUAT0dtNNYFiOunlNPGUv5N9BoX4X1Lwklq4ckdFDPo+50/exciNsf0ggH4s8Sj6CufOA==", + "shasum": "dbb692872dc94d6fd0adc93b66c8e09c67f78260", + "tarball": "https://registry.npmjs.org/nano-stringify-object/-/nano-stringify-object-0.0.0.tgz", + "fileCount": 5, + "unpackedSize": 8783, + "signatures": [ + { + "keyid": "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U", + "sig": "MEQCIF396neosjaDQP6JgESc3D/CpgE5VZik5CqXY/F68gUnAiAmtw8VnyNnYMNisW4K0S75AJicujUpd0u8g4kQ35t0YA==" + } + ] + }, + "_npmUser": { + "name": "gameroman", + "email": "dev@rman.dev" + }, + "directories": { + + }, + "maintainers": [ + { + "name": "gameroman", + "email": "dev@rman.dev" + } + ], + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages-npm-production", + "tmp": "tmp/nano-stringify-object_0.0.0_1773271904176_0.4735360871124257" + }, + "_hasShrinkwrap": false +} From 2ecefd9f49e8fd6ae567e388904dcba2c6798ab9 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 21:42:27 +0000 Subject: [PATCH 30/37] [autofix.ci] apply automated fixes --- .../npm-registry/packuments/nano-stringify-object/latest.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/fixtures/npm-registry/packuments/nano-stringify-object/latest.json b/test/fixtures/npm-registry/packuments/nano-stringify-object/latest.json index 114141744c..4a2051866f 100644 --- a/test/fixtures/npm-registry/packuments/nano-stringify-object/latest.json +++ b/test/fixtures/npm-registry/packuments/nano-stringify-object/latest.json @@ -47,9 +47,7 @@ "name": "gameroman", "email": "dev@rman.dev" }, - "directories": { - - }, + "directories": {}, "maintainers": [ { "name": "gameroman", From 21eca0ec4a6f8aea15be5102011d5c79318064e2 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 21:52:27 +0000 Subject: [PATCH 31/37] fix --- .../latest.json => nano-stringify-object.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/fixtures/npm-registry/packuments/{nano-stringify-object/latest.json => nano-stringify-object.json} (100%) diff --git a/test/fixtures/npm-registry/packuments/nano-stringify-object/latest.json b/test/fixtures/npm-registry/packuments/nano-stringify-object.json similarity index 100% rename from test/fixtures/npm-registry/packuments/nano-stringify-object/latest.json rename to test/fixtures/npm-registry/packuments/nano-stringify-object.json From 27e560d4492e7a13e835df16b2159eaf52431f83 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 21:59:53 +0000 Subject: [PATCH 32/37] fix: do something weird --- .../packuments/nano-stringify-object.json | 83 ++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/test/fixtures/npm-registry/packuments/nano-stringify-object.json b/test/fixtures/npm-registry/packuments/nano-stringify-object.json index 4a2051866f..cca55cc149 100644 --- a/test/fixtures/npm-registry/packuments/nano-stringify-object.json +++ b/test/fixtures/npm-registry/packuments/nano-stringify-object.json @@ -25,7 +25,7 @@ "tsdown": "^0.21.2", "typescript": "^5.9.3" }, - "_id": "nano-stringify-object@0.0.0", + "_id": "nano-stringify-object", "_integrity": "sha512-s4XcjhMpc1u7HfHHimUAT0dtNNYFiOunlNPGUv5N9BoX4X1Lwklq4ckdFDPo+50/exciNsf0ggH4s8Sj6CufOA==", "_nodeVersion": "24.3.0", "_npmVersion": "10.8.3", @@ -49,6 +49,10 @@ }, "directories": {}, "maintainers": [ + { + "name": "gameroman", + "email": "dev@rman.dev" + }, { "name": "gameroman", "email": "dev@rman.dev" @@ -58,5 +62,80 @@ "host": "s3://npm-registry-packages-npm-production", "tmp": "tmp/nano-stringify-object_0.0.0_1773271904176_0.4735360871124257" }, - "_hasShrinkwrap": false + "_hasShrinkwrap": false, + "dist-tags": { + "latest": "0.0.0" + }, + "versions": { + "0.0.0": { + "name": "nano-stringify-object", + "version": "0.0.0", + "license": "MIT", + "author": "gameroman", + "repository": { + "type": "git", + "url": "git+https://github.com/gameroman-npm/nano-stringify-object.git" + }, + "type": "module", + "exports": { + ".": "./dist/index.mjs", + "./package.json": "./package.json" + }, + "scripts": { + "format": "oxfmt", + "lint": "oxlint", + "test": "bun test", + "build": "tsdown", + "prepublishOnly": "bun run build" + }, + "devDependencies": { + "oxfmt": "^0.38.0", + "oxlint": "^1.50.0", + "tsdown": "^0.21.2", + "typescript": "^5.9.3" + }, + "_id": "nano-stringify-object@0.0.0", + "_integrity": "sha512-s4XcjhMpc1u7HfHHimUAT0dtNNYFiOunlNPGUv5N9BoX4X1Lwklq4ckdFDPo+50/exciNsf0ggH4s8Sj6CufOA==", + "_nodeVersion": "24.3.0", + "_npmVersion": "10.8.3", + "shasum": "dbb692872dc94d6fd0adc93b66c8e09c67f78260", + "dist": { + "integrity": "sha512-s4XcjhMpc1u7HfHHimUAT0dtNNYFiOunlNPGUv5N9BoX4X1Lwklq4ckdFDPo+50/exciNsf0ggH4s8Sj6CufOA==", + "shasum": "dbb692872dc94d6fd0adc93b66c8e09c67f78260", + "tarball": "https://registry.npmjs.org/nano-stringify-object/-/nano-stringify-object-0.0.0.tgz", + "fileCount": 5, + "unpackedSize": 8783, + "signatures": [ + { + "keyid": "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U", + "sig": "MEQCIF396neosjaDQP6JgESc3D/CpgE5VZik5CqXY/F68gUnAiAmtw8VnyNnYMNisW4K0S75AJicujUpd0u8g4kQ35t0YA==" + } + ] + }, + "_npmUser": { + "name": "gameroman", + "email": "dev@rman.dev" + }, + "directories": {}, + "maintainers": [ + { + "name": "gameroman", + "email": "dev@rman.dev" + } + ], + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages-npm-production", + "tmp": "tmp/nano-stringify-object_0.0.0_1773271904176_0.4735360871124257" + }, + "_hasShrinkwrap": false + } + }, + "time": { + "created": "2026-03-11T23:31:44.175Z", + "0.0.0": "2026-03-11T23:31:44.334Z", + "modified": "2026-03-11T23:31:44.554Z" + }, + "readme": "", + "readmeFilename": "", + "_rev": "1-d7e9fda2cb18e6ea7d359fdc679eea56" } From 1e742e64c8ecb9ffd5b0be2bd657fa2be58c4612 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 22:28:40 +0000 Subject: [PATCH 33/37] fix: use a proper fixture --- .../jsdelivr/nano-stringify-object.json | 49 +++++++++++++++++-- test/fixtures/mock-routes.cjs | 7 +-- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/test/fixtures/jsdelivr/nano-stringify-object.json b/test/fixtures/jsdelivr/nano-stringify-object.json index 091620a91f..88617d6be4 100644 --- a/test/fixtures/jsdelivr/nano-stringify-object.json +++ b/test/fixtures/jsdelivr/nano-stringify-object.json @@ -1,7 +1,48 @@ { + "type": "npm", + "name": "nano-stringify-object", + "version": "0.0.0", + "default": null, "files": [ - { "name": "dist/index.d.ts", "hash": "ghi", "size": 1200 }, - { "name": "dist/index.js", "hash": "def", "size": 5700 }, - { "name": "package.json", "hash": "abc", "size": 600 } - ] + { + "type": "directory", + "name": "dist", + "files": [ + { + "type": "file", + "name": "index.d.mts", + "hash": "miLdJwbJG1quJbUdHplSpmv+/7F8rHZM+kVXXRdz9W0=", + "size": 1192 + }, + { + "type": "file", + "name": "index.mjs", + "hash": "jznEg84ts4a5VCzdpfQViYnTBWoREZeolfbHUVtAymA=", + "size": 5731 + } + ] + }, + { + "type": "file", + "name": "LICENSE", + "hash": "GxzKussvQB30SBOW+2S33DnejHW8yBDc3jxk/WDXyzM=", + "size": 1221 + }, + { + "type": "file", + "name": "package.json", + "hash": "1Z382Cx0/ZycKM2TUBZlLWHt/RB/z00gOfwkt8WyPG4=", + "size": 639 + }, + { + "type": "file", + "name": "README.md", + "hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", + "size": 0 + } + ], + "links": { + "stats": "https://data.jsdelivr.com/v1/stats/packages/npm/nano-stringify-object@0.0.0", + "entrypoints": "https://data.jsdelivr.com/v1/packages/npm/nano-stringify-object@0.0.0/entrypoints" + } } diff --git a/test/fixtures/mock-routes.cjs b/test/fixtures/mock-routes.cjs index b9aba20fc0..41edafcc6f 100644 --- a/test/fixtures/mock-routes.cjs +++ b/test/fixtures/mock-routes.cjs @@ -425,12 +425,7 @@ function matchJsdelivrDataApi(urlString) { const fixture = readFixture(`jsdelivr/${parsed.name}.json`) if (fixture) { - return json({ - type: 'npm', - name: parsed.name, - version: parsed.version || 'latest', - ...fixture, - }) + return json(fixture) } return json({ From 8000756cb96d0b8db1894b9b40bcee8c91a6a720 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 22:39:33 +0000 Subject: [PATCH 34/37] Update cache.ts --- modules/runtime/server/cache.ts | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/modules/runtime/server/cache.ts b/modules/runtime/server/cache.ts index 9e35b46b1a..703649fc95 100644 --- a/modules/runtime/server/cache.ts +++ b/modules/runtime/server/cache.ts @@ -571,6 +571,46 @@ function logUnmockedRequest(type: string, detail: string, url: string): void { ) } +async function handleJsdelivrDataApi( + url: string, + storage: ReturnType, +): Promise { + let urlObj: URL + try { + urlObj = new URL(url) + } catch { + return null + } + + if (urlObj.host !== 'data.jsdelivr.com') return null + + const packageMatch = decodeURIComponent(urlObj.pathname).match(/^\/v1\/packages\/npm\/(.+)$/) + if (!packageMatch?.[1]) return null + + const parsed = parseScopedPackageWithVersion(packageMatch[1]) + + // Try per-package fixture first + const fixturePath = `jsdelivr:${parsed.name.replace(/\//g, ':')}.json` + const fixture = await storage.getItem(fixturePath) + if (fixture) { + return { data: fixture } + } + + // Fall back to generic stub (no declaration files) + return { + data: { + type: 'npm', + name: parsed.name, + version: parsed.version || 'latest', + files: [ + { name: 'package.json', hash: 'abc123', size: 1000 }, + { name: 'index.js', hash: 'def456', size: 500 }, + { name: 'README.md', hash: 'ghi789', size: 2000 }, + ], + }, + } +} + /** * Shared fixture-backed fetch implementation. * This is used by both cachedFetch and the global $fetch override. @@ -593,6 +633,12 @@ async function fetchFromFixtures( return { data: fastNpmMetaResult.data as T, isStale: false, cachedAt: Date.now() } } + const jsdelivrResult = await handleJsdelivrDataApi(url, storage) + if (jsdelivrResult) { + if (VERBOSE) process.stdout.write(`[test-fixtures] jsDelivr Data API: ${url}\n`) + return { data: jsdelivrResult.data as T, isStale: false, cachedAt: Date.now() } + } + // Check for GitHub API const githubResult = await handleGitHubApi(url, storage) if (githubResult) { From 27cbf5cc9ebada69ee5e1d00f0495df6a8d012ac Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 22:42:34 +0000 Subject: [PATCH 35/37] Update cache.ts --- modules/runtime/server/cache.ts | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/modules/runtime/server/cache.ts b/modules/runtime/server/cache.ts index 703649fc95..66de7c6585 100644 --- a/modules/runtime/server/cache.ts +++ b/modules/runtime/server/cache.ts @@ -28,6 +28,7 @@ const FIXTURE_PATHS = { esmTypes: 'esm-sh:types', githubContributors: 'github:contributors.json', githubContributorsStats: 'github:contributors-stats.json', + jsdelivr: 'jsdelivr', } as const type FixtureType = keyof typeof FIXTURE_PATHS @@ -163,33 +164,6 @@ function getMockForUrl(url: string): MockResult | null { } } - // jsdelivr CDN - return 404 for README files, etc. - if (host === 'cdn.jsdelivr.net') { - // Return null data which will cause a 404 - README files are optional - return { data: null } - } - - // jsdelivr data API - return mock file listing - if (host === 'data.jsdelivr.com') { - const packageMatch = decodeURIComponent(pathname).match(/^\/v1\/packages\/npm\/(.+)$/) - if (packageMatch?.[1]) { - const pkgWithVersion = packageMatch[1] - const parsed = parseScopedPackageWithVersion(pkgWithVersion) - return { - data: { - type: 'npm', - name: parsed.name, - version: parsed.version || 'latest', - files: [ - { name: 'package.json', hash: 'abc123', size: 1000 }, - { name: 'index.js', hash: 'def456', size: 500 }, - { name: 'README.md', hash: 'ghi789', size: 2000 }, - ], - }, - } - } - } - // Gravatar API - return 404 (avatars not needed in tests) if (host === 'www.gravatar.com') { return { data: null } @@ -590,7 +564,7 @@ async function handleJsdelivrDataApi( const parsed = parseScopedPackageWithVersion(packageMatch[1]) // Try per-package fixture first - const fixturePath = `jsdelivr:${parsed.name.replace(/\//g, ':')}.json` + const fixturePath = getFixturePath('jsdelivr', parsed.name) const fixture = await storage.getItem(fixturePath) if (fixture) { return { data: fixture } From d5c01ff68d3f1e85fd271cf8dcdd741a89a6fdf9 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 22:51:19 +0000 Subject: [PATCH 36/37] Update badge.spec.ts --- test/e2e/badge.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/badge.spec.ts b/test/e2e/badge.spec.ts index 651de114bf..5e57d6cb47 100644 --- a/test/e2e/badge.spec.ts +++ b/test/e2e/badge.spec.ts @@ -109,7 +109,7 @@ test.describe('badge API', () => { expect(body).not.toContain('missing') }) - test('types badge shows included badge when types not declared explictly', async ({ + test('types badge shows included badge when types not declared explicitly', async ({ page, baseURL, }) => { From f636f052a50c9f522c5249593f579a4b450c14fa Mon Sep 17 00:00:00 2001 From: "Willow (GHOST)" Date: Fri, 20 Mar 2026 19:07:33 -0700 Subject: [PATCH 37/37] refactor: url.parse --- modules/runtime/server/cache.ts | 40 +++++++++------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/modules/runtime/server/cache.ts b/modules/runtime/server/cache.ts index 66de7c6585..800e17a007 100644 --- a/modules/runtime/server/cache.ts +++ b/modules/runtime/server/cache.ts @@ -102,12 +102,8 @@ function parseScopedPackageWithVersion(input: string): { name: string; version?: } function getMockForUrl(url: string): MockResult | null { - let urlObj: URL - try { - urlObj = new URL(url) - } catch { - return null - } + const urlObj = URL.parse(url) + if (!urlObj) return null const { host, pathname, searchParams } = urlObj @@ -359,12 +355,8 @@ async function handleFastNpmMeta( url: string, storage: ReturnType, ): Promise { - let urlObj: URL - try { - urlObj = new URL(url) - } catch { - return null - } + const urlObj = URL.parse(url) + if (!urlObj) return null const { host, pathname, searchParams } = urlObj @@ -404,12 +396,8 @@ async function handleGitHubApi( url: string, storage: ReturnType, ): Promise { - let urlObj: URL - try { - urlObj = new URL(url) - } catch { - return null - } + const urlObj = URL.parse(url) + if (!urlObj) return null const { host, pathname } = urlObj @@ -460,12 +448,8 @@ interface FixtureMatchWithVersion extends FixtureMatch { } function matchUrlToFixture(url: string): FixtureMatchWithVersion | null { - let urlObj: URL - try { - urlObj = new URL(url) - } catch { - return null - } + const urlObj = URL.parse(url) + if (!urlObj) return null const { host, pathname, searchParams } = urlObj @@ -549,12 +533,8 @@ async function handleJsdelivrDataApi( url: string, storage: ReturnType, ): Promise { - let urlObj: URL - try { - urlObj = new URL(url) - } catch { - return null - } + const urlObj = URL.parse(url) + if (!urlObj) return null if (urlObj.host !== 'data.jsdelivr.com') return null