@@ -29,28 +29,41 @@ const MAX_SUBPATH_EXPORTS = 10
2929 *
3030 * This function fetches types for all subpath exports (e.g., `nuxt`, `nuxt/app`, `nuxt/kit`)
3131 * to provide comprehensive documentation for packages with multiple entry points.
32+ *
33+ * All errors are caught and result in empty nodes - docgen failures are graceful degradation
34+ * and should never cause error logging or wake up a maintainer.
3235 */
33- export async function getDocNodes ( packageName : string , version : string ) : Promise < DenoDocResult > {
34- // Get all types URLs from package exports
35- const typesUrls = await getAllTypesUrls ( packageName , version )
36+ export async function getDocNodes (
37+ packageName : string ,
38+ version : string ,
39+ exports ?: Record < string , unknown > ,
40+ ) : Promise < DenoDocResult > {
41+ try {
42+ // Get all types URLs from package exports (uses pre-fetched exports data)
43+ const typesUrls = await getAllTypesUrls ( packageName , version , exports )
3644
37- if ( typesUrls . length === 0 ) {
38- return { version : 1 , nodes : [ ] }
39- }
45+ if ( typesUrls . length === 0 ) {
46+ return { version : 1 , nodes : [ ] }
47+ }
4048
41- // Generate docs using @deno /doc WASM for all entry points
42- const result = await doc ( typesUrls , {
43- load : createLoader ( ) ,
44- resolve : createResolver ( ) ,
45- } )
49+ // Generate docs using @deno /doc WASM for all entry points
50+ const result = await doc ( typesUrls , {
51+ load : createLoader ( ) ,
52+ resolve : createResolver ( ) ,
53+ } )
4654
47- // Collect all nodes from all specifiers
48- const allNodes : DenoDocNode [ ] = [ ]
49- for ( const nodes of Object . values ( result ) ) {
50- allNodes . push ( ...( nodes as DenoDocNode [ ] ) )
51- }
55+ // Collect all nodes from all specifiers
56+ const allNodes : DenoDocNode [ ] = [ ]
57+ for ( const nodes of Object . values ( result ) ) {
58+ allNodes . push ( ...( nodes as DenoDocNode [ ] ) )
59+ }
5260
53- return { version : 1 , nodes : allNodes }
61+ return { version : 1 , nodes : allNodes }
62+ } catch {
63+ // Silent failure - all docgen errors are graceful degradation
64+ // This feature should never wake up a maintainer
65+ return { version : 1 , nodes : [ ] }
66+ }
5467}
5568
5669// =============================================================================
@@ -59,17 +72,14 @@ export async function getDocNodes(packageName: string, version: string): Promise
5972
6073/**
6174 * Get all TypeScript types URLs for a package, including subpath exports.
62- *
63- * 1. Fetches package.json from npm registry to discover exports
64- * 2. For each subpath with types, queries esm.sh for the types URL
65- * 3. Returns all discovered types URLs
6675 */
67- async function getAllTypesUrls ( packageName : string , version : string ) : Promise < string [ ] > {
68- // First, try the main entry point
76+ async function getAllTypesUrls (
77+ packageName : string ,
78+ version : string ,
79+ exports ?: Record < string , unknown > ,
80+ ) : Promise < string [ ] > {
6981 const mainTypesUrl = await getTypesUrl ( packageName , version )
70-
71- // Fetch package exports to discover subpaths
72- const subpathTypesUrls = await getSubpathTypesUrls ( packageName , version )
82+ const subpathTypesUrls = await getSubpathTypesUrlsFromExports ( packageName , version , exports )
7383
7484 // Combine and deduplicate
7585 const allUrls = new Set < string > ( )
@@ -82,59 +92,44 @@ async function getAllTypesUrls(packageName: string, version: string): Promise<st
8292}
8393
8494/**
85- * Fetch package.json exports and get types URLs for each subpath .
95+ * Extract types URLs from pre-fetched package exports .
8696 */
87- async function getSubpathTypesUrls ( packageName : string , version : string ) : Promise < string [ ] > {
88- const controller = new AbortController ( )
89- const timeoutId = setTimeout ( ( ) => controller . abort ( ) , FETCH_TIMEOUT_MS )
90-
91- try {
92- // Fetch package.json from npm registry
93- const response = await fetch ( `https://registry.npmjs.org/${ packageName } /${ version } ` , {
94- signal : controller . signal ,
95- } )
96- clearTimeout ( timeoutId )
97-
98- if ( ! response . ok ) return [ ]
99-
100- const pkgJson = await response . json ( )
101- const exports = pkgJson . exports
102-
103- // No exports field or simple string export
104- if ( ! exports || typeof exports !== 'object' ) return [ ]
105-
106- // Find subpaths with types
107- const subpathsWithTypes : string [ ] = [ ]
108- for ( const [ subpath , config ] of Object . entries ( exports ) ) {
109- // Skip the main entry (already handled) and non-object configs
110- if ( subpath === '.' || typeof config !== 'object' || config === null ) continue
111- // Skip package.json export
112- if ( subpath === './package.json' ) continue
113-
114- const exportConfig = config as Record < string , unknown >
115- if ( exportConfig . types && typeof exportConfig . types === 'string' ) {
116- subpathsWithTypes . push ( subpath )
117- }
97+ async function getSubpathTypesUrlsFromExports (
98+ packageName : string ,
99+ version : string ,
100+ exports ?: Record < string , unknown > ,
101+ ) : Promise < string [ ] > {
102+ // No exports field or simple string export
103+ if ( ! exports || typeof exports !== 'object' ) return [ ]
104+
105+ // Find subpaths with types
106+ const subpathsWithTypes : string [ ] = [ ]
107+ for ( const [ subpath , config ] of Object . entries ( exports ) ) {
108+ // Skip the main entry (already handled) and non-object configs
109+ if ( subpath === '.' || typeof config !== 'object' || config === null ) continue
110+ // Skip package.json export
111+ if ( subpath === './package.json' ) continue
112+
113+ const exportConfig = config as Record < string , unknown >
114+ if ( exportConfig . types && typeof exportConfig . types === 'string' ) {
115+ subpathsWithTypes . push ( subpath )
118116 }
117+ }
119118
120- // Limit to prevent runaway on huge packages
121- const limitedSubpaths = subpathsWithTypes . slice ( 0 , MAX_SUBPATH_EXPORTS )
119+ // Limit to prevent runaway on huge packages
120+ const limitedSubpaths = subpathsWithTypes . slice ( 0 , MAX_SUBPATH_EXPORTS )
122121
123- // Fetch types URLs for each subpath in parallel
124- const typesUrls = await Promise . all (
125- limitedSubpaths . map ( async subpath => {
126- // Convert ./app to /app for esm.sh URL
127- // esm.sh format: https://esm.sh/nuxt@3.15.4/app (not nuxt/app@3.15.4)
128- const esmSubpath = subpath . startsWith ( './' ) ? subpath . slice ( 1 ) : subpath
129- return getTypesUrlForSubpath ( packageName , version , esmSubpath )
130- } ) ,
131- )
122+ // Fetch types URLs for each subpath in parallel
123+ const typesUrls = await Promise . all (
124+ limitedSubpaths . map ( async subpath => {
125+ // Convert ./app to /app for esm.sh URL
126+ // esm.sh format: https://esm.sh/nuxt@3.15.4/app (not nuxt/app@3.15.4)
127+ const esmSubpath = subpath . startsWith ( './' ) ? subpath . slice ( 1 ) : subpath
128+ return getTypesUrlForSubpath ( packageName , version , esmSubpath )
129+ } ) ,
130+ )
132131
133- return typesUrls . filter ( ( url ) : url is string => url !== null )
134- } catch {
135- clearTimeout ( timeoutId )
136- return [ ]
137- }
132+ return typesUrls . filter ( ( url ) : url is string => url !== null )
138133}
139134
140135// =============================================================================
0 commit comments