Skip to content

Commit 8b0d074

Browse files
committed
fix
1 parent 1e1fe41 commit 8b0d074

3 files changed

Lines changed: 92 additions & 5 deletions

File tree

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {
2323
} from '#shared/utils/constants'
2424
import { parseRepoUrl } from '#shared/utils/git-providers'
2525
import { encodePackageName } from '#shared/utils/npm'
26+
import { flattenFileTree } from '#server/utils/import-resolver'
27+
import { getPackageFileTree } from '#server/utils/file-tree'
2628
import { getLatestVersion, getLatestVersionBatch } from 'fast-npm-meta'
2729

2830
interface AnalysisPackageJson extends ExtendedPackageJson {
@@ -50,18 +52,31 @@ export default defineCachedEventHandler(
5052
`${NPM_REGISTRY}/${encodedName}${versionSuffix}`,
5153
)
5254

53-
// Only check for @types package if the package doesn't ship its own types
5455
let typesPackage: TypesPackageInfo | undefined
56+
let files: Set<string> | undefined
57+
58+
// Only check for @types and files when the package doesn't ship its own types
5559
if (!hasBuiltInTypes(pkg)) {
5660
const typesPkgName = getTypesPackageName(packageName)
5761
typesPackage = await fetchTypesPackageInfo(typesPkgName)
62+
63+
const resolvedVersion = pkg.version ?? version ?? 'latest'
64+
try {
65+
const fileTreeResponse = await getPackageFileTree(packageName, resolvedVersion)
66+
files = flattenFileTree(fileTreeResponse.tree)
67+
} catch {
68+
// File tree fetch failed - skip implicit types check
69+
}
5870
}
5971

6072
// Check for associated create-* package (e.g., vite -> create-vite, next -> create-next-app)
6173
// Only show if the packages are actually associated (same maintainers or same org)
6274
const createPackage = await findAssociatedCreatePackage(packageName, pkg)
63-
64-
const analysis = analyzePackage(pkg, { typesPackage, createPackage })
75+
const analysis = analyzePackage(pkg, {
76+
typesPackage,
77+
createPackage,
78+
files,
79+
})
6580
const devDependencySuggestion = getDevDependencySuggestion(packageName, pkg.readme)
6681

6782
return {

shared/utils/package-analysis.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,19 @@ export function getCreateShortName(createPackageName: string): string {
219219
return createPackageName
220220
}
221221

222+
function hasImplicitTypesInFiles(files: Set<string>): boolean {
223+
return Array.from(files).some(
224+
p => p.endsWith('.d.ts') || p.endsWith('.d.mts') || p.endsWith('.d.cts'),
225+
)
226+
}
227+
222228
/**
223229
* Detect TypeScript types status for a package
224230
*/
225231
export function detectTypesStatus(
226232
pkg: ExtendedPackageJson,
227233
typesPackageInfo?: TypesPackageInfo,
234+
files?: Set<string>,
228235
): TypesStatus {
229236
// Check for built-in types
230237
if (pkg.types || pkg.typings) {
@@ -239,6 +246,12 @@ export function detectTypesStatus(
239246
}
240247
}
241248

249+
// Check for implicit types (e.g. .d.mts next to .mjs, TypeScript automatic lookup)
250+
// Collect paths from exports/main/module and check if declaration files exist
251+
if (files && hasImplicitTypesInFiles(files)) {
252+
return { kind: 'included' }
253+
}
254+
242255
// Check for @types package
243256
if (typesPackageInfo) {
244257
return {
@@ -289,6 +302,8 @@ export function getTypesPackageName(packageName: string): string {
289302
export interface AnalyzePackageOptions {
290303
typesPackage?: TypesPackageInfo
291304
createPackage?: CreatePackageInfo
305+
/** Flattened package file paths for implicit types detection (e.g. .d.mts next to .mjs) */
306+
files?: Set<string>
292307
}
293308

294309
/**
@@ -299,8 +314,7 @@ export function analyzePackage(
299314
options?: AnalyzePackageOptions,
300315
): PackageAnalysis {
301316
const moduleFormat = detectModuleFormat(pkg)
302-
303-
const types = detectTypesStatus(pkg, options?.typesPackage)
317+
const types = detectTypesStatus(pkg, options?.typesPackage, options?.files)
304318

305319
return {
306320
moduleFormat,

test/unit/shared/utils/package-analysis.spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,64 @@ describe('detectTypesStatus', () => {
166166
it('returns none when no types detected', () => {
167167
expect(detectTypesStatus({})).toEqual({ kind: 'none' })
168168
})
169+
170+
it('detects included types when declaration file exists in files', () => {
171+
expect(
172+
detectTypesStatus(
173+
{ type: 'module', exports: { '.': './dist/index.mjs' } },
174+
undefined,
175+
new Set(['dist/index.mjs', 'dist/index.d.mts']),
176+
),
177+
).toEqual({ kind: 'included' })
178+
})
179+
})
180+
181+
describe('detectTypesStatus implicit types (path derivation)', () => {
182+
it('derives .d.mts from .mjs in exports', () => {
183+
expect(
184+
detectTypesStatus(
185+
{ type: 'module', exports: { '.': './dist/index.mjs' } },
186+
undefined,
187+
new Set(['dist/index.d.mts']),
188+
),
189+
).toEqual({ kind: 'included' })
190+
})
191+
192+
it('derives .d.cts from .cjs in exports', () => {
193+
expect(
194+
detectTypesStatus(
195+
{ exports: { '.': { require: './dist/index.cjs' } } },
196+
undefined,
197+
new Set(['dist/index.d.cts']),
198+
),
199+
).toEqual({ kind: 'included' })
200+
})
201+
202+
it('derives .d.mts from main when type is module', () => {
203+
expect(
204+
detectTypesStatus(
205+
{ type: 'module', main: 'dist/index.mjs' },
206+
undefined,
207+
new Set(['dist/index.d.mts']),
208+
),
209+
).toEqual({ kind: 'included' })
210+
})
211+
})
212+
213+
describe('analyzePackage with files (implicit types)', () => {
214+
it('detects included types when declaration file exists in files', () => {
215+
const pkg = { type: 'module' as const, exports: { '.': './dist/index.mjs' } }
216+
const files = new Set(['dist/index.mjs', 'dist/index.d.mts'])
217+
const result = analyzePackage(pkg, { files })
218+
expect(result.types).toEqual({ kind: 'included' })
219+
})
220+
221+
it('returns none when declaration file does not exist in files', () => {
222+
const pkg = { type: 'module' as const, exports: { '.': './dist/index.mjs' } }
223+
const files = new Set(['dist/index.mjs'])
224+
const result = analyzePackage(pkg, { files })
225+
expect(result.types).toEqual({ kind: 'none' })
226+
})
169227
})
170228

171229
describe('getTypesPackageName', () => {

0 commit comments

Comments
 (0)