Skip to content

Commit 51f9cfe

Browse files
committed
fix: remove redundant req in docgen, refactor to use packument data
1 parent e19834d commit 51f9cfe

File tree

3 files changed

+76
-76
lines changed

3 files changed

+76
-76
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@ export default defineCachedEventHandler(
2525
throw createError({ statusCode: 404, message: 'No latest version found' })
2626
}
2727

28+
// Extract exports from the already-fetched packument to avoid redundant fetch
29+
const versionData = packument.versions?.[version]
30+
const exports = versionData?.exports as Record<string, unknown> | undefined
31+
2832
let generated
2933
try {
30-
generated = await generateDocsWithDeno(packageName, version)
34+
generated = await generateDocsWithDeno(packageName, version, exports)
3135
} catch (error) {
3236
console.error(`Doc generation failed for ${packageName}@${version}:`, error)
3337
return {

server/utils/docs/client.ts

Lines changed: 69 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -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
// =============================================================================

server/utils/docs/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ import { renderDocNodes, renderToc } from './render'
3434
export async function generateDocsWithDeno(
3535
packageName: string,
3636
version: string,
37+
exports?: Record<string, unknown>,
3738
): Promise<DocsGenerationResult | null> {
3839
// Get doc nodes using @deno/doc WASM
39-
const result = await getDocNodes(packageName, version)
40+
const result = await getDocNodes(packageName, version, exports)
4041

4142
if (!result.nodes || result.nodes.length === 0) {
4243
return null

0 commit comments

Comments
 (0)