From 61e2990a8856b2e39cb214326c5e0b5edc9beb96 Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Wed, 20 Aug 2025 09:55:30 -0400 Subject: [PATCH 1/4] fix(tools): correct lightdom css routing, improve trailing slash behavior --- .../dev-server/plugins/dev-server-router.ts | 68 +++++++++++++++++++ .../plugins/dev-server-templates.ts | 9 ++- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/tools/pfe-tools/dev-server/plugins/dev-server-router.ts b/tools/pfe-tools/dev-server/plugins/dev-server-router.ts index 3ad9422646..3b9ac1d8dc 100644 --- a/tools/pfe-tools/dev-server/plugins/dev-server-router.ts +++ b/tools/pfe-tools/dev-server/plugins/dev-server-router.ts @@ -38,6 +38,62 @@ const cacheBustingMiddleware: PfeMiddleware = () => async function(ctx, next) { return next(); }; +/** + * Redirects both /elements and /elements/ to the root + * Handles both cases: /elements → / and /elements/ → / + */ +const elementsToRootRedirectMiddleware: PfeMiddleware = () => ctx => ctx.redirect('/'); + +/** + * Ensures trailing slash for component URLs + * FROM: `/elements/footer` + * TO: `/elements/footer/` + * @param config normalized PFE dev server config + */ +const ensureTrailingSlashMiddleware: PfeMiddleware = () => (ctx, next) => { + // Only add trailing slash if path doesn't already end with one + if (!ctx.path.endsWith('/')) { + return ctx.redirect(`${ctx.path}/`); + } + // If it already ends with slash, continue to next middleware + return next(); +}; + +/** + * Handles lightdom CSS files that are missing the tag name in the path + * FROM: `elements/pf-jazz-hands-lightdom.css` or `elements/pf-jazz-hands-lightdom-shim.css` + * TO: `elements/pf-jazz-hands/pf-jazz-hands-lightdom.css` or `elements/pf-jazz-hands/pf-jazz-hands-lightdom-shim.css` + * @param config normalized PFE dev server config + */ +const lightdomShortPathMiddleware: PfeMiddleware = config => (ctx, next) => { + const { sheetName, suffix } = ctx.params; + // Extract tag name from sheet name (e.g., "rh-footer-lightdom" -> "rh-footer") + const tagName = sheetName.replace(/-lightdom$/, ''); + // Keep the full sheetName including -lightdom part for the redirect + const redirect = `/${config.elementsDir}/${tagName}/${tagName}-lightdom${suffix ?? ''}.css`; + if (ctx.path !== redirect) { + return ctx.redirect(redirect); + } else { + return next(); + } +}; + +/** + * Handles lightdom CSS files accessed from demo directory + * FROM: `components/pf-jazz-hands/demo/pf-jazz-hands-lightdom.css` + * TO: `elements/pf-jazz-hands/pf-jazz-hands-lightdom.css` + * @param config normalized PFE dev server config + */ +const demoLightdomMiddleware: PfeMiddleware = config => (ctx, next) => { + const { tagName, sheetName, suffix } = ctx.params; + const redirect = `/${config.elementsDir}/${tagName}/${sheetName}${suffix ?? ''}.css`; + if (ctx.path !== redirect) { + return ctx.redirect(redirect); + } else { + return next(); + } +}; + /** * Loads the typescript sources for element declaration source requests * This is useful when the typescript build runs in parallel. @@ -96,6 +152,10 @@ export function pfeDevServerRouterMiddleware( const router = new Router(); const shim = lightdomShimMiddleware(config); const demo = demoSubresourceMiddleware(config); + const shortPath = lightdomShortPathMiddleware(config); + const demoLightdom = demoLightdomMiddleware(config); + const trailingSlash = ensureTrailingSlashMiddleware(config); + const elementsRedirect = elementsToRootRedirectMiddleware(config); return router .get('/tools/pfe-tools/environment.js(.js)?', environmentMiddleware(config)) .get(`/core/pfe-core/:splatPath*.js`, coreMiddleware(config)) @@ -107,5 +167,13 @@ export function pfeDevServerRouterMiddleware( .get(`/${componentSubpath}/:unprefixedElementSlug/:sheetName-lightdom.css`, shim) .get(`/${componentSubpath}/:unprefixedElementSlug/demo/:demoName/:fileName.:ext`, demo) .get(`/${componentSubpath}/:unprefixedElementSlug/demo/:fileName.:ext`, demo) + .get(`/${componentSubpath}/:sheetName-lightdom:suffix.css`, shortPath) + .get(`/${componentSubpath}/:sheetName-lightdom.css`, shortPath) + .get(`/${elementsDir}/:tagName/demo/:sheetName-lightdom:suffix.css`, demoLightdom) + .get(`/${elementsDir}/:tagName/demo/:sheetName-lightdom.css`, demoLightdom) + .get(`/${componentSubpath}/:unprefixedElementSlug/demo`, trailingSlash) + .get(`/${componentSubpath}/:unprefixedElementSlug`, trailingSlash) + .get(`/${componentSubpath}`, elementsRedirect) + .get(`/${componentSubpath}/`, elementsRedirect) .routes(); } diff --git a/tools/pfe-tools/dev-server/plugins/dev-server-templates.ts b/tools/pfe-tools/dev-server/plugins/dev-server-templates.ts index 5c49258dfb..c1e9a460ef 100644 --- a/tools/pfe-tools/dev-server/plugins/dev-server-templates.ts +++ b/tools/pfe-tools/dev-server/plugins/dev-server-templates.ts @@ -69,7 +69,14 @@ export function pfeDevServerTemplateMiddleware(config: PfeDevServerInternalConfi if (config.loadDemo && !(method !== 'HEAD' && method !== 'GET' || path.includes('.'))) { const url = new URL(ctx.request.url, `http://${ctx.request.headers.host}`); const demos = await getDemos(config); - const demo = demos.find(x => x.permalink === url.pathname); + // const demo = demos.find(x => x.permalink === url.pathname); + let demo = demos.find(x => x.permalink === url.pathname); + // Handle case where URL ends with /demo/ but permalink was shortened to just / + // e.g., request for /compoennts/jazz-hands/demo/ should match demo with permalink /components/jazz-hands/ + if (!demo && url.pathname.endsWith('/demo/')) { + const alternativePathname = url.pathname.replace('/demo/', '/'); + demo = demos.find(x => x.permalink === alternativePathname); + } const manifest = demo?.manifest; const templateContent = await getTemplateContent(demo); ctx.cwd = process.cwd(); From b6cfdff8263a7d5ba7e21521c6add2bc49e663c1 Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Wed, 20 Aug 2025 11:06:26 -0400 Subject: [PATCH 2/4] fix(tools): build filePath using elementsDir if not present in demo source parts --- .../custom-elements-manifest/lib/Manifest.ts | 20 +++++++++++++------ .../plugins/dev-server-templates.ts | 9 +-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts b/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts index d70a27f199..4ff555f6f0 100644 --- a/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts +++ b/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts @@ -314,12 +314,20 @@ export class Manifest { // strict removes all special characters from slug slug = slugify(slug, { strict: true, lower: true }); const primaryElementName = deslugify(slug, options.rootDir); - const filePath = path.normalize( - path.posix.join( - options.rootDir, - options.elementsDir, - decodeURIComponent(demo.source?.href.replace(options.sourceControlURLPrefix, '') ?? ''), - )); + const demoSource = + decodeURIComponent(demo.source?.href.replace(options.sourceControlURLPrefix, '') ?? ''); + // split the demoSource into an array of parts + const demoSourceParts = demoSource.split('/'); + // if demoSourceParts contains options.elementsDir, then build the filePath from the rootDir and the demoSource + let filePath; + if (demoSourceParts.includes(options.elementsDir)) { + filePath = + path.normalize(path.posix.join(options.rootDir, demoSource)); + // otherwise, build the filePath from the rootDir, options.elementsDir, and the demoSource + } else { + filePath = + path.normalize(path.posix.join(options.rootDir, options.elementsDir, demoSource)); + } const [last = ''] = filePath.split(path.sep).reverse(); const filename = last.replace('.html', ''); const isMainElementDemo = filename === 'index'; diff --git a/tools/pfe-tools/dev-server/plugins/dev-server-templates.ts b/tools/pfe-tools/dev-server/plugins/dev-server-templates.ts index c1e9a460ef..5c49258dfb 100644 --- a/tools/pfe-tools/dev-server/plugins/dev-server-templates.ts +++ b/tools/pfe-tools/dev-server/plugins/dev-server-templates.ts @@ -69,14 +69,7 @@ export function pfeDevServerTemplateMiddleware(config: PfeDevServerInternalConfi if (config.loadDemo && !(method !== 'HEAD' && method !== 'GET' || path.includes('.'))) { const url = new URL(ctx.request.url, `http://${ctx.request.headers.host}`); const demos = await getDemos(config); - // const demo = demos.find(x => x.permalink === url.pathname); - let demo = demos.find(x => x.permalink === url.pathname); - // Handle case where URL ends with /demo/ but permalink was shortened to just / - // e.g., request for /compoennts/jazz-hands/demo/ should match demo with permalink /components/jazz-hands/ - if (!demo && url.pathname.endsWith('/demo/')) { - const alternativePathname = url.pathname.replace('/demo/', '/'); - demo = demos.find(x => x.permalink === alternativePathname); - } + const demo = demos.find(x => x.permalink === url.pathname); const manifest = demo?.manifest; const templateContent = await getTemplateContent(demo); ctx.cwd = process.cwd(); From 8511e4bbe2e48ba828ad18e6b40e95a1fb6a5643 Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Wed, 20 Aug 2025 11:13:27 -0400 Subject: [PATCH 3/4] fix(tools): simplify syntax with a ternary --- .../custom-elements-manifest/lib/Manifest.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts b/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts index 4ff555f6f0..feed47e379 100644 --- a/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts +++ b/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts @@ -319,15 +319,9 @@ export class Manifest { // split the demoSource into an array of parts const demoSourceParts = demoSource.split('/'); // if demoSourceParts contains options.elementsDir, then build the filePath from the rootDir and the demoSource - let filePath; - if (demoSourceParts.includes(options.elementsDir)) { - filePath = - path.normalize(path.posix.join(options.rootDir, demoSource)); - // otherwise, build the filePath from the rootDir, options.elementsDir, and the demoSource - } else { - filePath = - path.normalize(path.posix.join(options.rootDir, options.elementsDir, demoSource)); - } + const filePath = demoSourceParts.includes(options.elementsDir) ? + path.normalize(path.posix.join(options.rootDir, demoSource)) + : path.normalize(path.posix.join(options.rootDir, options.elementsDir, demoSource)); const [last = ''] = filePath.split(path.sep).reverse(); const filename = last.replace('.html', ''); const isMainElementDemo = filename === 'index'; From 21e0a14c41706033b359ac4fe091ec7ed0668e6a Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Wed, 27 Aug 2025 14:33:56 -0400 Subject: [PATCH 4/4] fix(tools): select first part of demoSourceParts for filePath --- tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts b/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts index feed47e379..a6247e4085 100644 --- a/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts +++ b/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts @@ -318,8 +318,8 @@ export class Manifest { decodeURIComponent(demo.source?.href.replace(options.sourceControlURLPrefix, '') ?? ''); // split the demoSource into an array of parts const demoSourceParts = demoSource.split('/'); - // if demoSourceParts contains options.elementsDir, then build the filePath from the rootDir and the demoSource - const filePath = demoSourceParts.includes(options.elementsDir) ? + // if first part of demoSourceParts contains options.elementsDir, then build the filePath from the rootDir and the demoSource + const filePath = (demoSourceParts.shift() === options.elementsDir) ? path.normalize(path.posix.join(options.rootDir, demoSource)) : path.normalize(path.posix.join(options.rootDir, options.elementsDir, demoSource)); const [last = ''] = filePath.split(path.sep).reverse();