@@ -53,6 +53,7 @@ export function diagnoseCustomPublic(context) {
5353
5454 issues . push ( ...diagnoseHtmlHeadAssets ( context ) ) ;
5555 issues . push ( ...diagnoseSharedAssets ( context ) ) ;
56+ issues . push ( ...diagnoseProductPages ( context ) ) ;
5657 issues . push ( ...diagnoseBranding ( context ) ) ;
5758
5859 const supported = new Set ( languages ) ;
@@ -95,9 +96,10 @@ export function reconcileCustomPublic(context, options = {}) {
9596 fs . rmSync ( customPublicDir , { recursive : true , force : true } ) ;
9697 copyDirectory ( defaultPublicDir , customPublicDir ) ;
9798 pruneUnsupportedLanguageDirs ( context ) ;
98- rewriteHtmlFiles ( customPublicDir , ( html , filePath ) =>
99- normalizeHtmlHead ( applyBranding ( html , context , languageForPublicFile ( context , filePath ) ) )
100- ) ;
99+ rewriteHtmlFiles ( customPublicDir , ( html , filePath ) => {
100+ if ( isProductPublicFile ( context , filePath ) ) return html ;
101+ return normalizeHtmlHead ( applyBranding ( html , context , languageForPublicFile ( context , filePath ) ) ) ;
102+ } ) ;
101103 actions . push ( "recreated custom/public from defaults" ) ;
102104 } else {
103105 if ( options . languages ) {
@@ -110,15 +112,24 @@ export function reconcileCustomPublic(context, options = {}) {
110112 actions . push ( "synced shared CSS, script, logo, icon, font, and badge assets from defaults" ) ;
111113 }
112114
115+ if ( options . productPages ) {
116+ syncProductPages ( context ) ;
117+ actions . push ( "synced product dashboard and QA pages from defaults" ) ;
118+ }
119+
113120 if ( options . branding ) {
114- rewriteHtmlFiles ( customPublicDir , ( html , filePath ) =>
115- applyBranding ( html , context , languageForPublicFile ( context , filePath ) )
116- ) ;
121+ rewriteHtmlFiles ( customPublicDir , ( html , filePath ) => {
122+ if ( isProductPublicFile ( context , filePath ) ) return html ;
123+ return applyBranding ( html , context , languageForPublicFile ( context , filePath ) ) ;
124+ } ) ;
117125 actions . push ( "refreshed branding in custom/public HTML" ) ;
118126 }
119127
120128 if ( options . headAssets ) {
121- rewriteHtmlFiles ( customPublicDir , ( html ) => normalizeHtmlHead ( html ) ) ;
129+ rewriteHtmlFiles ( customPublicDir , ( html , filePath ) => {
130+ if ( isProductPublicFile ( context , filePath ) ) return html ;
131+ return normalizeHtmlHead ( html ) ;
132+ } ) ;
122133 actions . push ( "normalized favicon and theme head assets in custom/public HTML" ) ;
123134 }
124135 }
@@ -134,6 +145,7 @@ export function parseReconcileArgs(argv) {
134145 dryRun : false ,
135146 headAssets : false ,
136147 languages : false ,
148+ productPages : false ,
137149 public : false
138150 } ;
139151
@@ -143,6 +155,7 @@ export function parseReconcileArgs(argv) {
143155 options . branding = true ;
144156 options . headAssets = true ;
145157 options . languages = true ;
158+ options . productPages = true ;
146159 } else if ( arg === "--assets" ) {
147160 options . assets = true ;
148161 } else if ( arg === "--branding" ) {
@@ -153,6 +166,8 @@ export function parseReconcileArgs(argv) {
153166 options . headAssets = true ;
154167 } else if ( arg === "--languages" ) {
155168 options . languages = true ;
169+ } else if ( arg === "--product-pages" ) {
170+ options . productPages = true ;
156171 } else if ( arg === "--public" ) {
157172 options . public = true ;
158173 } else {
@@ -165,6 +180,7 @@ export function parseReconcileArgs(argv) {
165180
166181function diagnoseHtmlHeadAssets ( context ) {
167182 return listHtmlFiles ( context . customPublicDir )
183+ . filter ( ( filePath ) => ! isProductPublicFile ( context , filePath ) )
168184 . filter ( ( filePath ) => normalizeHtmlHead ( fs . readFileSync ( filePath , "utf8" ) ) !== fs . readFileSync ( filePath , "utf8" ) )
169185 . map ( ( filePath ) => ( {
170186 code : "html-head-assets-stale" ,
@@ -193,10 +209,29 @@ function diagnoseSharedAssets(context) {
193209 } ) ;
194210}
195211
212+ function diagnoseProductPages ( context ) {
213+ return listProductDefaultPages ( context )
214+ . filter ( ( defaultPath ) => {
215+ const customPath = path . join ( context . customPublicDir , path . relative ( context . defaultPublicDir , defaultPath ) ) ;
216+ return fs . existsSync ( customPath ) && ! sameFile ( defaultPath , customPath ) ;
217+ } )
218+ . map ( ( defaultPath ) => {
219+ const customPath = path . join ( context . customPublicDir , path . relative ( context . defaultPublicDir , defaultPath ) ) ;
220+ return {
221+ code : "product-page-stale" ,
222+ severity : "warn" ,
223+ fix : "product-pages" ,
224+ path : relativePath ( context , customPath ) ,
225+ message : "Product-managed dashboard or QA page differs from defaults."
226+ } ;
227+ } ) ;
228+ }
229+
196230function diagnoseBranding ( context ) {
197231 if ( ! hasBrandingConfig ( context . siteConfig ) ) return [ ] ;
198232
199233 return listHtmlFiles ( context . customPublicDir )
234+ . filter ( ( filePath ) => ! isProductPublicFile ( context , filePath ) )
200235 . filter ( ( filePath ) => {
201236 const html = fs . readFileSync ( filePath , "utf8" ) ;
202237 const brandedHtml = applyBranding ( html , context , languageForPublicFile ( context , filePath ) ) ;
@@ -242,6 +277,28 @@ function syncSharedAssets(context) {
242277 }
243278}
244279
280+ function syncProductPages ( context ) {
281+ for ( const defaultPath of listProductDefaultPages ( context ) ) {
282+ const relative = path . relative ( context . defaultPublicDir , defaultPath ) ;
283+ const customPath = path . join ( context . customPublicDir , relative ) ;
284+ if ( ! fs . existsSync ( customPath ) ) continue ;
285+ fs . mkdirSync ( path . dirname ( customPath ) , { recursive : true } ) ;
286+ fs . copyFileSync ( defaultPath , customPath ) ;
287+ }
288+ }
289+
290+ function listProductDefaultPages ( context ) {
291+ return [
292+ path . join ( context . defaultPublicDir , "_stats" , "index.html" ) ,
293+ path . join ( context . defaultPublicDir , "_tests" , "index.html" )
294+ ] . filter ( ( filePath ) => fs . existsSync ( filePath ) ) ;
295+ }
296+
297+ function isProductPublicFile ( context , filePath ) {
298+ const relative = path . relative ( context . customPublicDir , filePath ) . split ( path . sep ) . join ( "/" ) ;
299+ return relative === "_stats/index.html" || relative === "_tests/index.html" ;
300+ }
301+
245302function listSharedDefaultAssets ( context ) {
246303 const supported = new Set ( context . languages ) ;
247304 return listFiles ( context . defaultPublicDir ) . filter ( ( filePath ) => {
0 commit comments