@@ -13,11 +13,14 @@ async function checkFloatingPages(context, options) {
1313 const docsDir = path . resolve ( context . siteDir , 'docs' ) ;
1414
1515 let sidebarItems = [ ] ;
16+ let autogeneratedDirs = [ ] ;
1617 const sidebarsPath = path . join ( context . siteDir , 'sidebars.js' ) ;
1718 if ( fs . existsSync ( sidebarsPath ) ) {
1819 try {
1920 const sidebars = require ( sidebarsPath ) ;
20- sidebarItems = getSidebarItems ( sidebars ) ;
21+ const result = getSidebarItemsAndAutogenerated ( context , sidebars ) ;
22+ sidebarItems = result . items ;
23+ autogeneratedDirs = result . autogeneratedDirs ;
2124 } catch ( err ) {
2225 console . error ( "Error loading sidebars.js" , err ) ;
2326 throw err ; // Stop the build
@@ -28,58 +31,173 @@ async function checkFloatingPages(context, options) {
2831 const floatingPages = [ ] ;
2932
3033 for ( const filePath of markdownFiles ) {
34+ // directories to skip
35+ if ( filePath . includes ( '_' ) )
36+ continue ;
37+
3138 const fileContent = fs . readFileSync ( filePath , 'utf-8' ) ;
3239 const { data } = matter ( fileContent ) ;
3340
3441 const relativePath = path . relative ( docsDir , filePath ) . replace ( / \\ / g, '/' ) . replace ( / \. m d $ / , '' ) ;
3542
36- // Check 1: Explicit sidebar_position or id in frontmatter
37- if ( data . sidebar_position || data . id ) {
38- const idToCheck = data . id || relativePath ;
39- if ( ! sidebarItems . includes ( idToCheck ) ) {
40- floatingPages . push ( filePath ) ;
41- }
42- } else {
43- //Check 2: Implicit.
44- if ( ! sidebarItems . includes ( relativePath ) ) {
45- floatingPages . push ( filePath ) ;
46- }
43+ // Skip if the document has sidebar: false explicitly set
44+ if ( data . sidebar === false ) {
45+ continue ;
46+ }
47+
48+ const idToCheck = data . id || relativePath ;
49+
50+ // Check various ways this document could be included in the sidebar
51+ if ( isDocumentIncluded ( idToCheck , relativePath , sidebarItems , autogeneratedDirs ) ) {
52+ continue ;
4753 }
54+
55+ floatingPages . push ( filePath ) ;
4856 }
4957
5058 if ( floatingPages . length > 0 ) {
51- console . error ( '\x1b[31m%s\x1b[0m' , 'Floating pages found:' ) ;
52- floatingPages . forEach ( page => console . error ( ` - ${ page } ` ) ) ;
5359 if ( options && options . failBuild ) {
54- throw new Error ( 'Floating pages found. See above for details.' ) ;
60+ console . error ( '\x1b[31m%s\x1b[0m' , `${ floatingPages . length } floating pages found:` ) ;
61+ floatingPages . forEach ( page => console . error ( ` - ${ page } ` ) ) ;
62+ throw new Error ( '🚨 Floating pages found. See above for details.' ) ;
63+ } else {
64+ console . log ( '⚠️' , 'Found floating pages:' ) ;
65+ floatingPages . forEach ( page => console . log ( ` - ${ page } ` ) ) ;
5566 }
5667 } else {
57- console . log ( '\x1b[32m%s\x1b[0m ' , 'No floating pages found.' ) ;
68+ console . log ( '✅ ' , 'No floating pages found.' ) ;
5869 }
5970 }
6071 }
6172}
6273
63- function getSidebarItems ( sidebarConfig ) {
64- let items = [ ]
74+ // Helper function to check if a document is included in sidebar or autogenerated directories
75+ function isDocumentIncluded ( idToCheck , relativePath , sidebarItems , autogeneratedDirs ) {
76+ // Direct check if ID is in sidebar items
77+ if ( sidebarItems . includes ( idToCheck ) ) {
78+ return true ;
79+ }
80+
81+ // Check variations for index pages
82+ if ( relativePath . endsWith ( '/index' ) ) {
83+ const parentDir = relativePath . replace ( / \/ i n d e x $ / , '' ) ;
84+ if ( sidebarItems . includes ( parentDir ) ) {
85+ return true ;
86+ }
87+ }
88+
89+ // Check if any sidebar item is a string and matches the ID without the /index
90+ for ( const item of sidebarItems ) {
91+ if ( typeof item === 'string' ) {
92+ // If the item directly matches
93+ if ( item === idToCheck ) {
94+ return true ;
95+ }
96+
97+ // For index pages, check if the parent directory matches
98+ if ( relativePath . endsWith ( '/index' ) && item === relativePath . replace ( / \/ i n d e x $ / , '' ) ) {
99+ return true ;
100+ }
101+ } else if ( typeof item === 'object' && item !== null ) {
102+ // Handle link items
103+ if ( item . type === 'link' && item . href ) {
104+ const href = item . href ;
105+ // Extract the path from the href
106+ let linkPath = href . replace ( / ^ \/ d o c s \/ / , '' ) . replace ( / \/ $ / , '' ) ;
107+
108+ // Check if this link points to the current document
109+ if ( linkPath === idToCheck ||
110+ linkPath === relativePath ||
111+ ( relativePath . endsWith ( '/index' ) && linkPath === relativePath . replace ( / \/ i n d e x $ / , '' ) ) ) {
112+ return true ;
113+ }
114+ }
115+ }
116+ }
117+
118+ // Check if document is in an autogenerated directory
119+ for ( const dir of autogeneratedDirs ) {
120+ // Direct check
121+ if ( relativePath . startsWith ( dir + '/' ) || relativePath === dir ) {
122+ return true ;
123+ }
124+
125+ // Handle index files specifically
126+ if ( relativePath === `${ dir } /index` ) {
127+ return true ;
128+ }
129+ }
130+
131+ return false ;
132+ }
133+
134+ function getSidebarItemsAndAutogenerated ( context , sidebarConfig ) {
135+ let items = [ ] ;
136+ let autogeneratedDirs = [ ] ;
137+
65138 function traverse ( item ) {
66139 if ( item . type === 'doc' ) {
67140 items . push ( item . id ) ;
68- } else if ( item . type === 'category' || item . type === 'autogenerated' ) {
141+ } else if ( item . type === 'category' ) {
142+ // Add the category link itself if it has a link
143+ if ( item . link && item . link . type === 'doc' && item . link . id ) {
144+ items . push ( item . link . id ) ;
145+ }
146+
147+ // Process all items in the category
69148 ( item . items || [ ] ) . forEach ( traverse ) ;
149+ } else if ( item . type === 'autogenerated' ) {
150+ const dirPath = item . dirName ;
151+ let dir ;
152+
153+ // Handle both absolute and relative paths
154+ if ( path . isAbsolute ( dirPath ) ) {
155+ dir = path . relative ( path . resolve ( context . siteDir , 'docs' ) , dirPath ) ;
156+ } else {
157+ dir = dirPath ;
158+ }
159+
160+ dir = dir . replace ( / \\ / g, '/' ) ;
161+
162+ // Remove leading 'docs/' if present
163+ dir = dir . replace ( / ^ d o c s \/ / , '' ) ;
164+
165+ // Remove trailing slash if present
166+ dir = dir . replace ( / \/ $ / , '' ) ;
167+
168+ autogeneratedDirs . push ( dir ) ;
70169 } else if ( item . type === 'link' ) {
71- // no need to worry about links
170+ // Add the link item directly to check later
171+ items . push ( item ) ;
172+
173+ // Check if the link is to a local doc
174+ const href = item . href || '' ;
175+ if ( href . startsWith ( '/' ) && ! href . startsWith ( '//' ) ) {
176+ // Extract doc ID from local path
177+ const docPath = href . replace ( / ^ \/ d o c s \/ / , '' ) . replace ( / \/ $ / , '' ) ;
178+ if ( docPath ) {
179+ items . push ( docPath ) ;
180+ }
181+ }
182+ } else if ( typeof item === 'string' ) {
183+ items . push ( item ) ;
72184 }
73185 }
74- // sidebarConfig can be an array or an object
75- if ( Array . isArray ( sidebarConfig ) ) {
76- sidebarConfig . forEach ( traverse ) ;
77- } else {
78- Object . values ( sidebarConfig ) . forEach ( sidebar => {
79- sidebar . forEach ( traverse )
80- } )
186+
187+ const cleanedSidebarConfig = { ...sidebarConfig } ;
188+ // we don't check top level nav
189+ if ( cleanedSidebarConfig . dropdownCategories ) {
190+ delete cleanedSidebarConfig . dropdownCategories ;
81191 }
82- return items ;
192+
193+ Object . values ( cleanedSidebarConfig ) . forEach ( sidebar => {
194+ sidebar . forEach ( traverse ) ;
195+ } ) ;
196+
197+ return {
198+ items,
199+ autogeneratedDirs
200+ } ;
83201}
84202
85203module . exports = checkFloatingPages ;
0 commit comments