@@ -573,6 +573,20 @@ Changelog entry:`;
573573 resolvedPath = `fern/${ resolvedPath } ` ;
574574 }
575575
576+ // Determine the effective slug for URL generation
577+ let effectiveSlug = null ;
578+ if ( product [ 'skip-slug' ] === true ) {
579+ // Skip slug entirely - pages will be at root level
580+ effectiveSlug = '' ;
581+ } else if ( product . slug ) {
582+ // Use explicit slug
583+ effectiveSlug = product . slug ;
584+ } else {
585+ // Derive slug from path or display-name
586+ const pathBasename = product . path . split ( '/' ) . pop ( ) . replace ( / \. y m l $ / , '' ) ;
587+ effectiveSlug = pathBasename ;
588+ }
589+
576590 // Load product config
577591 const productContent = await this . fetchFileContent ( resolvedPath ) ;
578592 if ( ! productContent ) continue ;
@@ -581,7 +595,7 @@ Changelog entry:`;
581595 if ( ! productConfig . navigation ) continue ;
582596
583597 // Process navigation structure
584- await this . processNavigation ( productConfig , product . slug , resolvedPath ) ;
598+ await this . processNavigation ( productConfig , effectiveSlug , resolvedPath ) ;
585599 }
586600
587601 this . isPathMappingLoaded = true ;
@@ -591,24 +605,64 @@ Changelog entry:`;
591605 }
592606 }
593607
594- async processNavigation ( config , productSlug , configPath ) {
608+ async processNavigation ( config , productSlug , configPath , parentSections = [ ] ) {
595609 if ( ! config . navigation ) return ;
596610
597- const basePath = configPath . replace ( / \/ [ ^ \/ ] + \. y m l $ / , '' ) . replace ( 'fern/' , '' ) ;
611+ // Extract the directory path from the config file path
612+ // e.g., "fern/products/fern-def.yml" -> "products/fern-def"
613+ const basePath = configPath . replace ( / \. y m l $ / , '' ) . replace ( 'fern/' , '' ) ;
598614
599615 for ( const navItem of config . navigation ) {
600616 if ( navItem . page ) {
601- // It's a page
602- const pageFilePath = `fern/${ basePath } /pages/${ navItem . page } ` ;
603- const pageUrl = `/${ productSlug } /${ navItem . page } ` ;
617+ // It's a page - check if it has a custom path
618+ let pageFilePath ;
619+ if ( navItem . path ) {
620+ // Use custom path
621+ pageFilePath = `fern/${ basePath } /pages/${ navItem . path } ` ;
622+ } else {
623+ // Default path based on page name
624+ pageFilePath = `fern/${ basePath } /pages/${ navItem . page } ` ;
625+ }
626+
627+ // Build URL with parent sections
628+ const urlParts = [ productSlug , ...parentSections , navItem . page ] . filter ( Boolean ) ;
629+ const pageUrl = `/${ urlParts . join ( '/' ) } ` ;
604630 this . dynamicPathMapping . set ( pageUrl , pageFilePath ) ;
605631 } else if ( navItem . section ) {
606- // It's a section with pages
632+ // It's a section with pages - create section-based URLs
633+ const sectionSlug = navItem . section . toLowerCase ( ) . replace ( / \s + / g, '-' ) ;
634+ const newParentSections = [ ...parentSections , sectionSlug ] ;
635+
607636 for ( const pageItem of navItem . contents || [ ] ) {
608637 if ( pageItem . page ) {
609- const pageFilePath = `fern/${ basePath } /pages/${ pageItem . page } ` ;
610- const pageUrl = `/${ productSlug } /${ pageItem . page } ` ;
611- this . dynamicPathMapping . set ( pageUrl , pageFilePath ) ;
638+ let pageFilePath ;
639+ if ( pageItem . path ) {
640+ // Use custom path
641+ pageFilePath = `fern/${ basePath } /pages/${ pageItem . path } ` ;
642+ } else {
643+ // Default path based on page name
644+ pageFilePath = `fern/${ basePath } /pages/${ pageItem . page } ` ;
645+ }
646+
647+ // Build URL with all parent sections
648+ const urlParts = [ productSlug , ...newParentSections , pageItem . page ] . filter ( Boolean ) ;
649+ const sectionUrl = `/${ urlParts . join ( '/' ) } ` ;
650+ this . dynamicPathMapping . set ( sectionUrl , pageFilePath ) ;
651+
652+ // Also create direct URL for backwards compatibility (without parent sections)
653+ const directUrlParts = [ productSlug , pageItem . page ] . filter ( Boolean ) ;
654+ const directUrl = `/${ directUrlParts . join ( '/' ) } ` ;
655+ if ( ! this . dynamicPathMapping . has ( directUrl ) ) {
656+ this . dynamicPathMapping . set ( directUrl , pageFilePath ) ;
657+ }
658+ } else if ( pageItem . section ) {
659+ // Handle nested sections recursively
660+ const nestedSectionSlug = pageItem . section . toLowerCase ( ) . replace ( / \s + / g, '-' ) ;
661+ const nestedParentSections = [ ...newParentSections , nestedSectionSlug ] ;
662+
663+ // Create a temporary config object for recursive processing
664+ const nestedConfig = { navigation : pageItem . contents || [ ] } ;
665+ await this . processNavigation ( nestedConfig , productSlug , configPath , nestedParentSections ) ;
612666 }
613667 }
614668 }
@@ -620,7 +674,21 @@ Changelog entry:`;
620674 // Remove /learn prefix and clean up trailing slashes
621675 let urlPath = turbopufferUrl . replace ( '/learn' , '' ) . replace ( / \/ $ / , '' ) ;
622676
623- // Extract product and path
677+ // First try to use dynamic mapping
678+ if ( this . dynamicPathMapping . has ( urlPath ) ) {
679+ const mappedPath = this . dynamicPathMapping . get ( urlPath ) ;
680+ // Add .mdx extension if not present and not already a complete path
681+ if ( ! mappedPath . endsWith ( '.mdx' ) && ! mappedPath . endsWith ( '/' ) ) {
682+ // Check if it's a known directory case (like changelog)
683+ if ( mappedPath . endsWith ( '/changelog' ) || ( mappedPath . includes ( '/changelog' ) && ! mappedPath . includes ( '.' ) ) ) {
684+ return mappedPath ; // Return as directory
685+ }
686+ return `${ mappedPath } .mdx` ;
687+ }
688+ return mappedPath ;
689+ }
690+
691+ // Fallback to hardcoded logic for backwards compatibility
624692 const pathParts = urlPath . split ( '/' ) . filter ( p => p ) ;
625693 if ( pathParts . length === 0 ) return null ;
626694
@@ -662,7 +730,7 @@ Changelog entry:`;
662730 // Ensure dynamic mapping is loaded
663731 await this . loadDynamicPathMapping ( ) ;
664732
665- // Use the new transformation logic
733+ // Use the improved transformation logic that prioritizes dynamic mapping
666734 return this . transformTurbopufferUrlToPath ( turbopufferPath ) || turbopufferPath ;
667735 }
668736
0 commit comments