@@ -353,100 +353,132 @@ function convertFrontmatter(content, filePath) {
353353
354354// ─── SUMMARY.md → docs.json navigation ─────────────────────────────────────
355355
356- function parseSummary ( summaryPath ) {
356+ /**
357+ * Parse a single SUMMARY.md file into sections.
358+ * Returns an array of { name: string, pages: string[] } where each section
359+ * corresponds to a `## Header` in the SUMMARY.md. Pages before the first
360+ * `## Header` go into a section named "Getting Started".
361+ *
362+ * Page paths have .md stripped but README is preserved
363+ * (e.g. "use-cases/granting-access/README").
364+ */
365+ function parseSummary ( summaryPath , spacePrefix ) {
357366 const content = fs . readFileSync ( summaryPath , "utf-8" ) ;
358367 const lines = content . split ( "\n" ) ;
359368
360- const sections = [ ] ; // [{name, items : [{title, path, indent, children} ] }]
369+ const sections = [ ] ; // [{name, pages : [string ]}]
361370 let currentSection = null ;
362371
363372 for ( const line of lines ) {
364373 // Section headers like ## Core Concepts
365374 const sectionMatch = line . match ( / ^ # # ( .+ ?) (?: \s * < a [ ^ > ] * > .* < \/ a > ) ? $ / ) ;
366375 if ( sectionMatch ) {
367- currentSection = { name : sectionMatch [ 1 ] . trim ( ) , items : [ ] } ;
376+ currentSection = { name : sectionMatch [ 1 ] . trim ( ) , pages : [ ] } ;
368377 sections . push ( currentSection ) ;
369378 continue ;
370379 }
371380
372- // Page entries: * [Title](path.md)
373- const pageMatch = line . match ( / ^ ( \s * ) \* \s + \[ ( [ ^ \] ] + ) \] \( ( [ ^ ) ] + ) \) / ) ;
381+ // Page entries at any indent level : * [Title](path.md)
382+ const pageMatch = line . match ( / ^ \s * \* \s + \[ ( [ ^ \] ] + ) \] \( ( [ ^ ) ] + ) \) / ) ;
374383 if ( pageMatch ) {
375- const indent = pageMatch [ 1 ] . length ;
376- const title = pageMatch [ 2 ] ;
377- let pagePath = pageMatch [ 3 ] ;
384+ let pagePath = pageMatch [ 2 ] ;
378385
386+ // Skip external links and broken references
379387 if ( pagePath . startsWith ( "http" ) ) continue ;
380388 if ( pagePath . includes ( "broken-reference" ) || pagePath . includes ( "/broken/" ) ) continue ;
381389
382- pagePath = pagePath
383- . replace ( / \. m d $ / , "" )
384- . replace ( / \/ R E A D M E $ / , "" ) ;
390+ // Strip .md extension (Mintlify uses .mdx, referenced without extension)
391+ pagePath = pagePath . replace ( / \. m d $ / , "" ) ;
392+
393+ // Prefix with space name
394+ pagePath = `${ spacePrefix } /${ pagePath } ` ;
385395
386396 if ( ! currentSection ) {
387- currentSection = { name : "Getting Started" , items : [ ] } ;
397+ currentSection = { name : "Getting Started" , pages : [ ] } ;
388398 sections . unshift ( currentSection ) ;
389399 }
390400
391- currentSection . items . push ( { title , path : pagePath , indent } ) ;
401+ currentSection . pages . push ( pagePath ) ;
392402 }
393403 }
394404
395- // Build nested groups from items based on indent levels
396- function buildGroups ( items ) {
397- const groups = [ ] ;
398- let currentGroup = null ;
399-
400- for ( let i = 0 ; i < items . length ; i ++ ) {
401- const item = items [ i ] ;
402-
403- if ( item . indent <= 2 ) {
404- // Check if next items are children (indent > 2)
405- const children = [ ] ;
406- let j = i + 1 ;
407- while ( j < items . length && items [ j ] . indent > 2 ) {
408- children . push ( items [ j ] ) ;
409- j ++ ;
410- }
405+ return sections ;
406+ }
411407
412- if ( children . length > 0 ) {
413- // This is a group parent
414- const subPages = [ item . path ] ;
415- for ( const child of children ) {
416- subPages . push ( child . path ) ;
417- }
418- groups . push ( {
419- group : item . title ,
420- pages : subPages ,
421- } ) ;
422- i = j - 1 ; // skip children
423- } else {
424- // Standalone page — add to a catch-all group if needed
425- if ( ! currentGroup || currentGroup . group !== "__standalone__" ) {
426- currentGroup = { group : "__standalone__" , pages : [ ] } ;
427- groups . push ( currentGroup ) ;
428- }
429- currentGroup . pages . push ( item . path ) ;
430- }
431- }
408+ /**
409+ * Parse an API-style SUMMARY.md (api-reference or brand-guides) where
410+ * top-level items with children become groups, and top-level items
411+ * without children go into an "Overview" group.
412+ *
413+ * Returns an array of { group: string, pages: string[] }.
414+ */
415+ function parseSummaryAsGroups ( summaryPath , spacePrefix ) {
416+ const content = fs . readFileSync ( summaryPath , "utf-8" ) ;
417+ const lines = content . split ( "\n" ) ;
418+
419+ // Parse into a flat list with indent levels
420+ const items = [ ] ; // [{title, path, indent}]
421+ for ( const line of lines ) {
422+ const pageMatch = line . match ( / ^ ( \s * ) \* \s + \[ ( [ ^ \] ] + ) \] \( ( [ ^ ) ] + ) \) / ) ;
423+ if ( ! pageMatch ) continue ;
424+
425+ const indent = pageMatch [ 1 ] . length ;
426+ const title = pageMatch [ 2 ] ;
427+ let pagePath = pageMatch [ 3 ] ;
428+
429+ if ( pagePath . startsWith ( "http" ) ) continue ;
430+ if ( pagePath . includes ( "broken-reference" ) || pagePath . includes ( "/broken/" ) ) continue ;
431+
432+ pagePath = pagePath . replace ( / \. m d $ / , "" ) ;
433+ pagePath = `${ spacePrefix } /${ pagePath } ` ;
434+
435+ items . push ( { title, path : pagePath , indent } ) ;
436+ }
437+
438+ // Group: top-level items (indent 0) with children become named groups.
439+ // Top-level items without children go into "Overview".
440+ const groups = [ ] ;
441+ let overviewGroup = null ;
442+
443+ for ( let i = 0 ; i < items . length ; i ++ ) {
444+ const item = items [ i ] ;
445+ if ( item . indent > 0 ) continue ; // skip — already consumed as child
446+
447+ // Collect all children (anything with indent > 0 that follows)
448+ const children = [ ] ;
449+ let j = i + 1 ;
450+ while ( j < items . length && items [ j ] . indent > 0 ) {
451+ children . push ( items [ j ] ) ;
452+ j ++ ;
432453 }
433454
434- // Merge standalone groups and rename
435- return groups . map ( ( g ) => {
436- if ( g . group === "__standalone__" ) {
437- return { ...g , group : "Overview" } ;
455+ if ( children . length > 0 ) {
456+ // This top-level item + its children form a group
457+ const pages = [ item . path , ...children . map ( ( c ) => c . path ) ] ;
458+ groups . push ( { group : item . title , pages } ) ;
459+ i = j - 1 ; // skip past children
460+ } else {
461+ // Standalone top-level page → "Overview" group
462+ if ( ! overviewGroup ) {
463+ overviewGroup = { group : "Overview" , pages : [ ] } ;
464+ // Insert Overview at the front
465+ groups . unshift ( overviewGroup ) ;
438466 }
439- return g ;
440- } ) ;
467+ overviewGroup . pages . push ( item . path ) ;
468+ }
441469 }
442470
443- return { sections , buildGroups } ;
471+ return groups ;
444472}
445473
446- function buildDocsJson ( parsedSummary ) {
447- const { sections, buildGroups } = parsedSummary ;
448-
449- const docsJson = {
474+ /**
475+ * Build the full docs.json object with 3 tabs:
476+ * - Guides (default tab, sidebar groups from ## sections)
477+ * - API Reference (groups per API resource)
478+ * - Brand Guides (groups per brand)
479+ */
480+ function buildDocsJson ( guidesSections , apiGroups , brandGroups ) {
481+ return {
450482 $schema : "https://mintlify.com/docs.json" ,
451483 name : "Seam" ,
452484 theme : "mint" ,
@@ -460,7 +492,25 @@ function buildDocsJson(parsedSummary) {
460492 light : "#60A5FA" ,
461493 dark : "#1D4ED8" ,
462494 } ,
463- navigation : { } ,
495+ navigation : {
496+ tabs : [
497+ {
498+ tab : "Guides" ,
499+ groups : guidesSections . map ( ( s ) => ( {
500+ group : s . name ,
501+ pages : s . pages ,
502+ } ) ) ,
503+ } ,
504+ {
505+ tab : "API Reference" ,
506+ groups : apiGroups ,
507+ } ,
508+ {
509+ tab : "Brand Guides" ,
510+ groups : brandGroups ,
511+ } ,
512+ ] ,
513+ } ,
464514 topbar : {
465515 links : [
466516 {
@@ -479,27 +529,6 @@ function buildDocsJson(parsedSummary) {
479529 baseUrl : "https://connect.getseam.com" ,
480530 } ,
481531 } ;
482-
483- if ( sections . length <= 1 ) {
484- // Simple: just groups
485- const groups = sections . length > 0 ? buildGroups ( sections [ 0 ] . items ) : [ ] ;
486- docsJson . navigation . groups = groups ;
487- } else {
488- // First section becomes the main sidebar groups, rest become tabs
489- const firstSection = sections [ 0 ] ;
490- docsJson . navigation . groups = buildGroups ( firstSection . items ) ;
491-
492- docsJson . navigation . tabs = [ ] ;
493- for ( let i = 1 ; i < sections . length ; i ++ ) {
494- const section = sections [ i ] ;
495- docsJson . navigation . tabs . push ( {
496- tab : section . name ,
497- groups : buildGroups ( section . items ) ,
498- } ) ;
499- }
500- }
501-
502- return docsJson ;
503532}
504533
505534// ─── Main ───────────────────────────────────────────────────────────────────
@@ -596,61 +625,23 @@ function main() {
596625
597626 // 4. Generate docs.json from all SUMMARY.md files
598627 console . log ( "📋 Generating docs.json..." ) ;
599- const allSections = [ ] ;
600- for ( const space of spaces ) {
601- const summaryPath = path . join ( SRC , space , "SUMMARY.md" ) ;
602- if ( fs . existsSync ( summaryPath ) ) {
603- const parsed = parseSummary ( summaryPath ) ;
604- // Prefix all page paths with the space name
605- for ( const section of parsed . sections ) {
606- for ( const item of section . items ) {
607- item . path = `${ space } /${ item . path } ` ;
608- }
609- }
610- allSections . push ( ...parsed . sections ) ;
611- }
612- }
613-
614- const docsJson = buildDocsJson ( {
615- sections : allSections ,
616- buildGroups : function ( items ) {
617- const groups = [ ] ;
618- let currentGroup = null ;
619-
620- for ( let i = 0 ; i < items . length ; i ++ ) {
621- const item = items [ i ] ;
622-
623- if ( item . indent <= 2 ) {
624- const children = [ ] ;
625- let j = i + 1 ;
626- while ( j < items . length && items [ j ] . indent > 2 ) {
627- children . push ( items [ j ] ) ;
628- j ++ ;
629- }
630-
631- if ( children . length > 0 ) {
632- const subPages = [ item . path ] ;
633- for ( const child of children ) {
634- subPages . push ( child . path ) ;
635- }
636- groups . push ( { group : item . title , pages : subPages } ) ;
637- i = j - 1 ;
638- } else {
639- if ( ! currentGroup || currentGroup . group !== "__standalone__" ) {
640- currentGroup = { group : "__standalone__" , pages : [ ] } ;
641- groups . push ( currentGroup ) ;
642- }
643- currentGroup . pages . push ( item . path ) ;
644- }
645- }
646- }
647628
648- return groups . map ( ( g ) => {
649- if ( g . group === "__standalone__" ) return { ...g , group : "Overview" } ;
650- return g ;
651- } ) ;
652- } ,
653- } ) ;
629+ // Parse each space's SUMMARY.md with the appropriate strategy
630+ const guidesSummary = path . join ( SRC , "guides" , "SUMMARY.md" ) ;
631+ const apiSummary = path . join ( SRC , "api-reference" , "SUMMARY.md" ) ;
632+ const brandSummary = path . join ( SRC , "brand-guides" , "SUMMARY.md" ) ;
633+
634+ const guidesSections = fs . existsSync ( guidesSummary )
635+ ? parseSummary ( guidesSummary , "guides" )
636+ : [ ] ;
637+ const apiGroups = fs . existsSync ( apiSummary )
638+ ? parseSummaryAsGroups ( apiSummary , "api-reference" )
639+ : [ ] ;
640+ const brandGroups = fs . existsSync ( brandSummary )
641+ ? parseSummaryAsGroups ( brandSummary , "brand-guides" )
642+ : [ ] ;
643+
644+ const docsJson = buildDocsJson ( guidesSections , apiGroups , brandGroups ) ;
654645 fs . writeFileSync (
655646 path . join ( DEST , "docs.json" ) ,
656647 JSON . stringify ( docsJson , null , 2 )
0 commit comments