1- import { NextRequest , NextResponse } from 'next/server' ;
2- import { nodeForPath , getDocsRootNode } from 'sentry-docs/docTree' ;
3- import { getFileBySlugWithCache } from 'sentry-docs/mdx' ;
4- import { isDeveloperDocs } from 'sentry-docs/isDeveloperDocs' ;
5- import matter from 'gray-matter' ;
61import fs from 'fs' ;
72import path from 'path' ;
83
4+ import matter from 'gray-matter' ;
5+ import { NextRequest , NextResponse } from 'next/server' ;
6+
7+ import { getDocsRootNode , nodeForPath } from 'sentry-docs/docTree' ;
8+ import { isDeveloperDocs } from 'sentry-docs/isDeveloperDocs' ;
9+ import { getFileBySlugWithCache } from 'sentry-docs/mdx' ;
10+
911export async function GET (
1012 request : NextRequest ,
11- { params } : { params : Promise < { path : string [ ] } > }
13+ { params} : { params : Promise < { path : string [ ] } > }
1214) {
1315 try {
1416 const resolvedParams = await params ;
1517 const pathSegments = resolvedParams . path || [ ] ;
16-
18+
1719 // Get the document tree
1820 const rootNode = await getDocsRootNode ( ) ;
19-
21+
2022 if ( pathSegments . length === 0 && ! isDeveloperDocs ) {
2123 // Handle home page - try to get the actual index content
2224 try {
@@ -25,7 +27,11 @@ export async function GET(
2527 const rawContent = fs . readFileSync ( indexPath , 'utf8' ) ;
2628 const parsed = matter ( rawContent ) ;
2729 const cleanContent = await cleanupMarkdown ( parsed . content , pathSegments ) ;
28- return createResponse ( parsed . data . title || 'Welcome to Sentry Documentation' , cleanContent , '/' ) ;
30+ return createResponse (
31+ parsed . data . title || 'Welcome to Sentry Documentation' ,
32+ cleanContent ,
33+ '/'
34+ ) ;
2935 }
3036 } catch ( e ) {
3137 // Fallback for home page
@@ -54,12 +60,20 @@ Sentry helps developers monitor and fix crashes in real time. The platform suppo
5460 if ( isDeveloperDocs ) {
5561 // Handle developer docs
5662 try {
57- const doc = await getFileBySlugWithCache ( `develop-docs/${ pathSegments . join ( '/' ) || '' } ` ) ;
58-
63+ const doc = await getFileBySlugWithCache (
64+ `develop-docs/${ pathSegments . join ( '/' ) || '' } `
65+ ) ;
66+
5967 // Try to get raw content from file system
6068 const possiblePaths = [
61- path . join ( process . cwd ( ) , `develop-docs/${ pathSegments . join ( '/' ) || 'index' } .mdx` ) ,
62- path . join ( process . cwd ( ) , `develop-docs/${ pathSegments . join ( '/' ) || 'index' } .md` ) ,
69+ path . join (
70+ process . cwd ( ) ,
71+ `develop-docs/${ pathSegments . join ( '/' ) || 'index' } .mdx`
72+ ) ,
73+ path . join (
74+ process . cwd ( ) ,
75+ `develop-docs/${ pathSegments . join ( '/' ) || 'index' } .md`
76+ ) ,
6377 path . join ( process . cwd ( ) , `develop-docs/${ pathSegments . join ( '/' ) } /index.mdx` ) ,
6478 path . join ( process . cwd ( ) , `develop-docs/${ pathSegments . join ( '/' ) } /index.md` ) ,
6579 ] ;
@@ -74,9 +88,12 @@ Sentry helps developers monitor and fix crashes in real time. The platform suppo
7488 }
7589 }
7690
77- pageTitle = frontMatter . title || doc . frontMatter . title || `Developer Documentation: ${ pathSegments . join ( ' / ' ) } ` ;
91+ pageTitle =
92+ frontMatter . title ||
93+ doc . frontMatter . title ||
94+ `Developer Documentation: ${ pathSegments . join ( ' / ' ) } ` ;
7895 } catch ( e ) {
79- return new NextResponse ( 'Page not found' , { status : 404 } ) ;
96+ return new NextResponse ( 'Page not found' , { status : 404 } ) ;
8097 }
8198 } else if ( pathSegments [ 0 ] === 'api' && pathSegments . length > 1 ) {
8299 // Handle API docs - these are generated from OpenAPI specs
@@ -92,9 +109,9 @@ For complete API reference with examples and detailed parameters, please visit t
92109 } else {
93110 // Handle regular docs
94111 const pageNode = nodeForPath ( rootNode , pathSegments ) ;
95-
112+
96113 if ( ! pageNode ) {
97- return new NextResponse ( 'Page not found' , { status : 404 } ) ;
114+ return new NextResponse ( 'Page not found' , { status : 404 } ) ;
98115 }
99116
100117 try {
@@ -110,29 +127,29 @@ For complete API reference with examples and detailed parameters, please visit t
110127 // Check if it's a platform guide that might use common files
111128 if ( pageNode . path . includes ( 'platforms/' ) ) {
112129 const pathParts = pageNode . path . split ( '/' ) ;
113-
130+
114131 // For paths like platforms/javascript/guides/react/tracing
115132 // Check platforms/javascript/common/tracing
116133 if ( pathParts . length >= 5 && pathParts [ 2 ] === 'guides' ) {
117134 const platform = pathParts [ 1 ] ; // e.g., 'javascript'
118135 const commonPath = `platforms/${ platform } /common` ;
119136 const remainingPath = pathParts . slice ( 4 ) . join ( '/' ) ; // e.g., 'tracing'
120-
137+
121138 possiblePaths . push (
122139 path . join ( process . cwd ( ) , 'docs' , commonPath , remainingPath + '.mdx' ) ,
123140 path . join ( process . cwd ( ) , 'docs' , commonPath , remainingPath + '.md' ) ,
124141 path . join ( process . cwd ( ) , 'docs' , commonPath , remainingPath , 'index.mdx' ) ,
125142 path . join ( process . cwd ( ) , 'docs' , commonPath , remainingPath , 'index.md' )
126143 ) ;
127144 }
128-
145+
129146 // For paths like platforms/javascript/tracing (direct platform paths)
130147 // Check platforms/javascript/common/tracing
131148 else if ( pathParts . length >= 3 ) {
132149 const platform = pathParts [ 1 ] ; // e.g., 'javascript'
133150 const commonPath = `platforms/${ platform } /common` ;
134151 const remainingPath = pathParts . slice ( 2 ) . join ( '/' ) ; // e.g., 'tracing'
135-
152+
136153 possiblePaths . push (
137154 path . join ( process . cwd ( ) , 'docs' , commonPath , remainingPath + '.mdx' ) ,
138155 path . join ( process . cwd ( ) , 'docs' , commonPath , remainingPath + '.md' ) ,
@@ -153,11 +170,15 @@ For complete API reference with examples and detailed parameters, please visit t
153170 const parsed = matter ( rawContent ) ;
154171 pageContent = parsed . content ;
155172 frontMatter = parsed . data ;
156- pageTitle = frontMatter . title || pageNode . frontmatter . title || `Documentation: ${ pathSegments . join ( ' / ' ) } ` ;
173+ pageTitle =
174+ frontMatter . title ||
175+ pageNode . frontmatter . title ||
176+ `Documentation: ${ pathSegments . join ( ' / ' ) } ` ;
157177 } else {
158178 // Fallback - try to get processed content
159179 const doc = await getFileBySlugWithCache ( `docs/${ pageNode . path } ` ) ;
160- pageTitle = doc . frontMatter . title || `Documentation: ${ pathSegments . join ( ' / ' ) } ` ;
180+ pageTitle =
181+ doc . frontMatter . title || `Documentation: ${ pathSegments . join ( ' / ' ) } ` ;
161182 pageContent = `# ${ pageTitle }
162183
163184This page exists in the documentation tree but the source markdown content could not be accessed directly.
@@ -168,22 +189,25 @@ The content may be dynamically generated or processed through the MDX pipeline.
168189For the complete content with full formatting, code examples, and interactive elements, please visit the original page.` ;
169190 }
170191 } catch ( e ) {
171- return new NextResponse ( 'Error processing page' , { status : 500 } ) ;
192+ return new NextResponse ( 'Error processing page' , { status : 500 } ) ;
172193 }
173194 }
174195
175196 // Clean up the markdown content
176197 const cleanContent = await cleanupMarkdown ( pageContent , pathSegments ) ;
177-
178- return createResponse ( pageTitle , cleanContent , `/${ pathSegments . join ( '/' ) } ` ) ;
179198
199+ return createResponse ( pageTitle , cleanContent , `/${ pathSegments . join ( '/' ) } ` ) ;
180200 } catch ( error ) {
181201 console . error ( 'Error generating llms.txt:' , error ) ;
182- return new NextResponse ( 'Internal server error' , { status : 500 } ) ;
202+ return new NextResponse ( 'Internal server error' , { status : 500 } ) ;
183203 }
184204}
185205
186- function createResponse ( title : string , content : string , originalPath : string ) : NextResponse {
206+ function createResponse (
207+ title : string ,
208+ content : string ,
209+ originalPath : string
210+ ) : NextResponse {
187211 const markdownOutput = `# ${ title }
188212
189213${ content }
@@ -203,108 +227,119 @@ ${content}
203227 } ) ;
204228}
205229
206- async function cleanupMarkdown ( content : string , pathSegments : string [ ] = [ ] ) : Promise < string > {
230+ async function cleanupMarkdown (
231+ content : string ,
232+ pathSegments : string [ ] = [ ]
233+ ) : Promise < string > {
207234 let cleaned = content ;
208-
235+
209236 // First, try to resolve PlatformContent includes with actual content
210237 cleaned = await resolvePlatformIncludes ( cleaned , pathSegments ) ;
211-
238+
212239 // Preserve existing code blocks by temporarily replacing them
213240 const codeBlocks : string [ ] = [ ] ;
214241 const codeBlockPlaceholder = '___CODE_BLOCK_PLACEHOLDER___' ;
215-
242+
216243 // Extract code blocks to preserve them
217- cleaned = cleaned . replace ( / ` ` ` [ \s \S ] * ?` ` ` / g, ( match ) => {
244+ cleaned = cleaned . replace ( / ` ` ` [ \s \S ] * ?` ` ` / g, match => {
218245 codeBlocks . push ( match ) ;
219246 return `${ codeBlockPlaceholder } ${ codeBlocks . length - 1 } ` ;
220247 } ) ;
221-
248+
222249 // First pass: Extract content from specific platform components while preserving inner text
223250 cleaned = cleaned
224251 // Extract content from Alert components
225252 . replace ( / < A l e r t [ ^ > ] * > ( [ \s \S ] * ?) < \/ A l e r t > / g, '\n> **Note:** $1\n' )
226-
253+
227254 // Extract content from PlatformSection components - preserve inner content
228255 . replace ( / < P l a t f o r m S e c t i o n [ ^ > ] * > ( [ \s \S ] * ?) < \/ P l a t f o r m S e c t i o n > / g, '$1' )
229-
230- // Extract content from PlatformContent components - preserve inner content
256+
257+ // Extract content from PlatformContent components - preserve inner content
231258 . replace ( / < P l a t f o r m C o n t e n t [ ^ > ] * > ( [ \s \S ] * ?) < \/ P l a t f o r m C o n t e n t > / g, '$1' )
232-
259+
233260 // Extract content from PlatformCategorySection components - preserve inner content
234261 . replace ( / < P l a t f o r m C a t e g o r y S e c t i o n [ ^ > ] * > ( [ \s \S ] * ?) < \/ P l a t f o r m C a t e g o r y S e c t i o n > / g, '$1' )
235-
262+
236263 // Handle PlatformIdentifier components - extract name attribute or use placeholder
237264 . replace ( / < P l a t f o r m I d e n t i f i e r [ ^ > ] * n a m e = " ( [ ^ " ] * ) " [ ^ > ] * \/ > / g, '`$1`' )
238265 . replace ( / < P l a t f o r m I d e n t i f i e r [ ^ > ] * \/ > / g, '`[PLATFORM_IDENTIFIER]`' )
239-
266+
240267 // Handle PlatformLink components - preserve link text and convert to markdown links when possible
241- . replace ( / < P l a t f o r m L i n k [ ^ > ] * t o = " ( [ ^ " ] * ) " [ ^ > ] * > ( [ \s \S ] * ?) < \/ P l a t f o r m L i n k > / g, '[$2]($1)' )
268+ . replace (
269+ / < P l a t f o r m L i n k [ ^ > ] * t o = " ( [ ^ " ] * ) " [ ^ > ] * > ( [ \s \S ] * ?) < \/ P l a t f o r m L i n k > / g,
270+ '[$2]($1)'
271+ )
242272 . replace ( / < P l a t f o r m L i n k [ ^ > ] * > ( [ \s \S ] * ?) < \/ P l a t f o r m L i n k > / g, '$1' ) ;
243-
273+
244274 // Multiple passes to handle any remaining nested components
245275 for ( let i = 0 ; i < 3 ; i ++ ) {
246276 cleaned = cleaned
247277 // Remove any remaining JSX components but try to preserve inner content first
248278 . replace ( / < ( [ A - Z ] [ a - z A - Z 0 - 9 ] * ) [ ^ > ] * > ( [ \s \S ] * ?) < \/ \1> / g, '$2' )
249-
279+
250280 // Remove any remaining self-closing JSX components
251281 . replace ( / < [ A - Z ] [ a - z A - Z 0 - 9 ] * [ ^ > ] * \/ > / g, '' )
252-
282+
253283 // Remove JSX expressions
254284 . replace ( / \{ [ ^ } ] * \} / g, '' )
255-
285+
256286 // Remove any remaining opening/closing JSX tags
257287 . replace ( / < \/ ? [ A - Z ] [ a - z A - Z 0 - 9 ] * [ ^ > ] * > / g, '' ) ;
258288 }
259-
289+
260290 // Restore code blocks
261291 codeBlocks . forEach ( ( block , index ) => {
262292 cleaned = cleaned . replace ( `${ codeBlockPlaceholder } ${ index } ` , block ) ;
263293 } ) ;
264-
265- return cleaned
266- // Remove import/export statements
267- . replace ( / ^ i m p o r t \s + .* $ / gm, '' )
268- . replace ( / ^ e x p o r t \s + .* $ / gm, '' )
269-
270- // Remove HTML comments
271- . replace ( / < ! - - [ \s \S ] * ?- - > / g, '' )
272-
273- // Clean up whitespace and formatting
274- . replace ( / \n { 3 , } / g, '\n\n' )
275- . replace ( / ^ \s * \n / gm, '\n' )
276- . replace ( / \n \s * \n \s * \n / g, '\n\n' )
277- . trim ( ) ;
294+
295+ return (
296+ cleaned
297+ // Remove import/export statements
298+ . replace ( / ^ i m p o r t \s + .* $ / gm, '' )
299+ . replace ( / ^ e x p o r t \s + .* $ / gm, '' )
300+
301+ // Remove HTML comments
302+ . replace ( / < ! - - [ \s \S ] * ?- - > / g, '' )
303+
304+ // Clean up whitespace and formatting
305+ . replace ( / \n { 3 , } / g, '\n\n' )
306+ . replace ( / ^ \s * \n / gm, '\n' )
307+ . replace ( / \n \s * \n \s * \n / g, '\n\n' )
308+ . trim ( )
309+ ) ;
278310}
279311
280- async function resolvePlatformIncludes ( content : string , pathSegments : string [ ] ) : Promise < string > {
312+ async function resolvePlatformIncludes (
313+ content : string ,
314+ pathSegments : string [ ]
315+ ) : Promise < string > {
281316 // Detect platform and guide from path segments
282317 let platform = '' ;
283318 let guide = '' ;
284-
319+
285320 if ( pathSegments . length >= 2 && pathSegments [ 0 ] === 'platforms' ) {
286321 platform = pathSegments [ 1 ] ; // e.g., 'javascript'
287-
322+
288323 if ( pathSegments . length >= 4 && pathSegments [ 2 ] === 'guides' ) {
289324 guide = pathSegments [ 3 ] ; // e.g., 'react'
290325 }
291326 }
292-
327+
293328 // Build platform identifier for include files
294329 let platformId = platform ;
295330 if ( guide && guide !== platform ) {
296331 platformId = `${ platform } .${ guide } ` ;
297332 }
298-
333+
299334 // Replace PlatformContent includes with actual content
300335 const includePattern = / < P l a t f o r m C o n t e n t [ ^ > ] * i n c l u d e P a t h = " ( [ ^ " ] * ) " [ ^ > ] * \/ > / g;
301336 let result = content ;
302337 let match ;
303-
338+
304339 while ( ( match = includePattern . exec ( content ) ) !== null ) {
305340 const includePath = match [ 1 ] ;
306341 const fullMatch = match [ 0 ] ;
307-
342+
308343 try {
309344 // Try to load the platform-specific include file
310345 const possiblePaths = [
@@ -313,7 +348,7 @@ async function resolvePlatformIncludes(content: string, pathSegments: string[]):
313348 // Fallback to generic if platform-specific doesn't exist
314349 path . join ( process . cwd ( ) , `platform-includes/${ includePath } /index.mdx` ) ,
315350 ] ;
316-
351+
317352 let includeContent = '' ;
318353 for ( const filePath of possiblePaths ) {
319354 if ( fs . existsSync ( filePath ) ) {
@@ -323,20 +358,26 @@ async function resolvePlatformIncludes(content: string, pathSegments: string[]):
323358 break ;
324359 }
325360 }
326-
361+
327362 if ( includeContent ) {
328363 result = result . replace ( fullMatch , `\n${ includeContent } \n` ) ;
329364 } else {
330365 // Fallback placeholder with more descriptive text
331366 const sectionName = includePath . split ( '/' ) . pop ( ) || 'content' ;
332- result = result . replace ( fullMatch , `\n*[${ sectionName . charAt ( 0 ) . toUpperCase ( ) + sectionName . slice ( 1 ) } instructions would appear here for ${ platformId || platform || 'this platform' } ]*\n` ) ;
367+ result = result . replace (
368+ fullMatch ,
369+ `\n*[${ sectionName . charAt ( 0 ) . toUpperCase ( ) + sectionName . slice ( 1 ) } instructions would appear here for ${ platformId || platform || 'this platform' } ]*\n`
370+ ) ;
333371 }
334372 } catch ( error ) {
335373 console . error ( `Error loading include ${ includePath } :` , error ) ;
336374 const sectionName = includePath . split ( '/' ) . pop ( ) || 'content' ;
337- result = result . replace ( fullMatch , `\n*[${ sectionName . charAt ( 0 ) . toUpperCase ( ) + sectionName . slice ( 1 ) } instructions would appear here]*\n` ) ;
375+ result = result . replace (
376+ fullMatch ,
377+ `\n*[${ sectionName . charAt ( 0 ) . toUpperCase ( ) + sectionName . slice ( 1 ) } instructions would appear here]*\n`
378+ ) ;
338379 }
339380 }
340-
381+
341382 return result ;
342- }
383+ }
0 commit comments